packages feed

generic-diff-instances (empty) → 0.1.0.0

raw patch · 11 files changed

+682/−0 lines, 11 filesdep +QuickCheckdep +basedep +containers

Dependencies added: QuickCheck, base, containers, generic-diff, generic-diff-instances, generics-sop, hspec, sop-core, text

Files

+ CHANGELOG.md view
@@ -0,0 +1,14 @@+# Changelog++All notable changes to `generic-diff-instances` will be documented in this file.++The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),+and this project adheres to [Haskell Package Versioning Policy](https://pvp.haskell.org).++## [Unreleased]++### Added++- `Diff` instances for `Map`, `Seq`, `Set` and `Tree` from `containers`.++[unreleased]: https://github.com/fpringle/generic-diff/compare/7dbc273...HEAD
+ README.md view
@@ -0,0 +1,12 @@+[![Haskell CI](https://github.com/fpringle/generic-diff/actions/workflows/haskell.yml/badge.svg)](https://github.com/fpringle/generic-diff/actions/workflows/haskell.yml)++# `generic-diff` instances++The [generic-diff](https://hackage.haskell.org/package/generic-diff) package+aims to be lightweight and not force any instances which might have more than+one interpretation.++This package provides a more comprehensive set of instances for types from a+range of common packages.++Currently we provide instances for [Map](https://hackage-content.haskell.org/package/containers-0.8/docs/Data-Map-Lazy.html#t:Map), [Seq](https://hackage-content.haskell.org/package/containers-0.8/docs/Data-Sequence.html#t:Seq), [Set](https://hackage-content.haskell.org/package/containers-0.8/docs/Data-Set.html#t:Set) and [Tree](https://hackage-content.haskell.org/package/containers-0.8/docs/Data-Tree.html#t:Tree) from the containers package.
+ generic-diff-instances.cabal view
@@ -0,0 +1,101 @@+cabal-version:      3.0+name:               generic-diff-instances+version:            0.1.0.0+synopsis:           Diff instances for common types+description:+  The [generic-diff](https://hackage.haskell.org/package/generic-diff) package+  aims to be lightweight and not force any instances which might have more than+  one interpretation.++  This package provides a more comprehensive set of instances for types from a+  range of common packages.+license:            BSD-3-Clause+author:             Frederick Pringle+maintainer:         freddyjepringle@gmail.com+copyright:          Copyright(c) Frederick Pringle 2025+homepage:           https://github.com/fpringle/generic-diff+category:           Generics, Test+build-type:         Simple+extra-doc-files:    CHANGELOG.md+                    README.md+tested-with:+  GHC == 9.12.2+  GHC == 9.10.1+  GHC == 9.8.2+  GHC == 9.6.5+  GHC == 9.4.8+  GHC == 9.2.8+  GHC == 9.0.2+  GHC == 8.10.7+  GHC == 8.6.5++common warnings+  ghc-options: -Wall++common deps+  build-depends:+    , base >= 4.12 && < 5+    , generic-diff >= 0.1 && < 0.2+    , sop-core >= 0.4.0.1 && < 0.6+    , generics-sop >= 0.4 && < 0.6+    , text >= 1.1 && < 2.2+    , containers >= 0.5.9.2 && < 0.9++common extensions+  default-extensions:+    AllowAmbiguousTypes+    ConstraintKinds+    DataKinds+    DefaultSignatures+    DeriveGeneric+    FlexibleContexts+    FlexibleInstances+    GADTs+    LambdaCase+    OverloadedStrings+    PolyKinds+    RankNTypes+    RecordWildCards+    ScopedTypeVariables+    StandaloneDeriving+    TypeApplications+    TypeFamilies+    TypeOperators+    UndecidableInstances+    ViewPatterns++library+  import:+      warnings+    , deps+    , extensions+  exposed-modules:+      Generics.Diff.Special.Seq+      Generics.Diff.Special.Map+      Generics.Diff.Special.Set+      Generics.Diff.Special.Tree++  hs-source-dirs:   src+  default-language: Haskell2010++test-suite generic-diff-instances-test+  import:+      warnings+    , deps+    , extensions+  default-language: Haskell2010+  type:             exitcode-stdio-1.0+  hs-source-dirs:   test+  main-is:          Spec.hs+  other-modules:+    Generics.Diff.UnitTestsSpec+    Generics.Diff.PropertyTestsSpec+    Util+  build-tool-depends:+      hspec-discover:hspec-discover+  ghc-options:      -Wno-orphans+  build-depends:+    , generic-diff+    , generic-diff-instances+    , QuickCheck+    , hspec
+ src/Generics/Diff/Special/Map.hs view
@@ -0,0 +1,68 @@+{-# OPTIONS_GHC -Wno-orphans #-}++{- | A worked example of implementing 'SpecialDiff' (and thereby 'Diff') for 'Map's.++We make the choice to prioritise speed over exhaustiveness: in other words we stop when we find+one difference between the two input maps. Alternatively, we could have gone the other way and+enumerated all the difference between the inputs, using some kind of intersection test. This is left+as an exercise for the reader.+-}+module Generics.Diff.Special.Map+  ( MapDiffError (..)+  )+where++import Control.Applicative ((<|>))+import Data.Map (Map)+import qualified Data.Map.Internal as Map+import Generics.Diff+import Generics.Diff.Render+import Generics.Diff.Special++-- | For 'Map's, we only pick out (maximum) one difference between the two inputs. There are three possibilities:+data MapDiffError k v+  = -- | A key is found in both maps, but they have different values.+    DiffAtKey k (DiffError v)+  | -- | The right set contains an element that isn't found in the left set+    LeftMissingKey k+  | -- | The left set contains an element that isn't found in the right set+    RightMissingKey k+  deriving (Show, Eq)++{- | Render a 'MapDiffError'. This is a top-level function because we'll use it in the implementations+of 'renderSpecialDiffError' for both 'Map' and 'Data.IntMap.IntMap'.+-}+mapDiffErrorDoc :: (Show k) => MapDiffError k v -> Doc+mapDiffErrorDoc = \case+  -- Since we have a nested 'DiffError' on the value, we use 'makeDoc'.+  DiffAtKey k err ->+    let lns = pure ("Both maps contain key " <> showB k <> " but the values differ:")+    in  makeDoc lns err+  LeftMissingKey k ->+    linesDoc $ pure $ "The right map contains key " <> showB k <> " but the left doesn't"+  RightMissingKey k ->+    linesDoc $ pure $ "The left map contains key " <> showB k <> " but the right doesn't"++instance (Show k, Ord k, Diff v) => SpecialDiff (Map k v) where+  type SpecialDiffError (Map k v) = MapDiffError k v++  -- base cases+  specialDiff Map.Tip Map.Tip = Nothing+  specialDiff Map.Tip (Map.Bin _ k _ _ _) = Just $ LeftMissingKey k+  specialDiff (Map.Bin _ k _ _ _) Map.Tip = Just $ RightMissingKey k+  -- recursive set, using Map.split+  specialDiff (Map.Bin _ k lVal left right) r = case Map.lookup k r of+    Nothing -> Just $ RightMissingKey k+    Just rVal ->+      -- first we check if the values are different (using the 'Diff' instance on v)+      case diff lVal rVal of+        Error err -> Just $ DiffAtKey k err+        Equal ->+          -- otherwise, split and recurse+          let (less, more) = Map.split k r+          in  specialDiff left less <|> specialDiff right more++  renderSpecialDiffError = mapDiffErrorDoc++instance (Show k, Ord k, Diff v) => Diff (Map k v) where+  diff = diffWithSpecial
+ src/Generics/Diff/Special/Seq.hs view
@@ -0,0 +1,22 @@+{-# OPTIONS_GHC -Wno-orphans #-}++-- | A worked example of implementing 'SpecialDiff' (and thereby 'Diff') for 'Seq's.+module Generics.Diff.Special.Seq () where++import Data.Foldable (toList)+import Data.Function (on)+import Data.Sequence (Seq)+import Generics.Diff+import Generics.Diff.Render+import Generics.Diff.Special++{- | Just as with the instance for lists or non-empty lists (see "Generics.Diff.Special.List"),+we can use 'ListDiffError', 'diffListWith' and 'listDiffErrorDoc'.+-}+instance (Diff a) => SpecialDiff (Seq a) where+  type SpecialDiffError (Seq a) = ListDiffError a+  specialDiff = diffListWith diff `on` toList+  renderSpecialDiffError = listDiffErrorDoc "sequence"++instance (Diff a) => Diff (Seq a) where+  diff = diffWithSpecial
+ src/Generics/Diff/Special/Set.hs view
@@ -0,0 +1,66 @@+{-# OPTIONS_GHC -Wno-orphans #-}++{- | A worked example of implementing 'SpecialDiff' (and thereby 'Diff') for 'Set's.++We make the choice to prioritise speed over exhaustiveness: in other words we stop when we find+one difference between the two input sets. Alternatively, we could have gone the other way and+enumerated all the difference between the inputs, using some kind of intersection test. This is left+as an exercise for the reader.++Note that the implementation for maps in "Generics.Diff.Special.Map" is very similar; this is since a+@'Set' k@ can be seen as equivalent to @Map k ()@.+-}+module Generics.Diff.Special.Set+  ( SetDiffError (..)+  )+where++import Control.Applicative ((<|>))+import Data.Set (Set)+import qualified Data.Set.Internal as Set+import Generics.Diff+import Generics.Diff.Render+import Generics.Diff.Special++-- | For 'Set's, we only pick out (maximum) one difference between the two inputs. There are two possibilities:+data SetDiffError k+  = -- | The right set contains an element that isn't found in the left set+    LeftMissingKey k+  | -- | The left set contains an element that isn't found in the right set+    RightMissingKey k+  deriving (Show, Eq)++{- | Render a 'SetDiffError'. This is a top-level function because we'll use it in the implementations+of 'renderSpecialDiffError' for both 'Set' and 'Data.IntSet.IntSet'.++There are no nested 'DiffError's here, so we use 'linesDoc'.+-}+setDiffErrorDoc :: (Show k) => SetDiffError k -> Doc+setDiffErrorDoc = \case+  LeftMissingKey k ->+    linesDoc $ pure $ "The right set contains key " <> showB k <> " but the left doesn't"+  RightMissingKey k ->+    linesDoc $ pure $ "The left set contains key " <> showB k <> " but the right doesn't"++-- First we define an instance of 'SpecialDiff'. We need 'Show' and 'Eq' so that 'SetDiffError'+-- also has these instances; we need 'Ord' to compare elements of the set.+instance (Show k, Eq k, Ord k) => SpecialDiff (Set k) where+  type SpecialDiffError (Set k) = SetDiffError k++  -- base cases+  specialDiff Set.Tip Set.Tip = Nothing+  specialDiff Set.Tip (Set.Bin _ k _ _) = Just $ LeftMissingKey k+  specialDiff (Set.Bin _ k _ _) Set.Tip = Just $ RightMissingKey k+  -- recursive step, using Set.split+  specialDiff (Set.Bin _ k left right) r =+    if Set.notMember k r+      then Just $ RightMissingKey k+      else+        let (less, more) = Set.split k r+        in  specialDiff left less <|> specialDiff right more++  renderSpecialDiffError = setDiffErrorDoc++-- Now we can implement 'Diff' using 'diffWithSpecial'.+instance (Show k, Ord k) => Diff (Set k) where+  diff = diffWithSpecial
+ src/Generics/Diff/Special/Tree.hs view
@@ -0,0 +1,89 @@+{-# LANGUAGE DerivingVia #-}+{-# OPTIONS_GHC -Wno-orphans #-}++{- | A worked example of implementing 'SpecialDiff' (and thereby 'Diff') for 'Tree.Tree's.++As with other 3rd-party types, there are different approaches we can take here. We'll show 2 of them:++- using 'gspecialDiffNested';+- using 'SpecialDiff' and a custom diff type.+-}+module Generics.Diff.Special.Tree where++import Control.Applicative+import Data.List.NonEmpty (NonEmpty (..))+import qualified Data.Text.Lazy.Builder as TB+import qualified Data.Tree as Tree+import Generics.Diff+import Generics.Diff.Render+import Generics.Diff.Special+import Generics.SOP.GGP++------------------------------------------------------------+-- Using gspecialDiffNested++-- | Generically-derived instance.+instance (Diff a) => SpecialDiff (Tree.Tree a) where+  type SpecialDiffError (Tree.Tree a) = DiffErrorNested (GCode (Tree.Tree a))+  specialDiff = gspecialDiffNested+  renderSpecialDiffError = diffErrorNestedDoc++instance (Diff a) => Diff (Tree.Tree a) where+  diff = diffWithSpecial++------------------------------------------------------------+-- Using SpecialDiff++{- | A newtype wrapper around 'Tree.Tree' to demonstrate one alternate way we could hand-write+a 'SpecialDiff' instance.+-}+newtype CustomTree a = CustomTree (Tree.Tree a)+  deriving (Show) via (Tree.Tree a)++-- | Where are we in the tree? Each element of the list says which child node we step to next.+newtype TreePath = TreePath [Int]+  deriving (Show, Eq) via [Int]++-- | A custom error type for 'CustomTree'.+data CustomTreeDiffError a+  = DiffAtNode TreePath (DiffError a)+  | WrongLengthsOfChildren TreePath Int Int+  deriving (Show, Eq)++-- | Render a tree path as a 'TB.Builder'+renderTreePath :: TreePath -> TB.Builder+renderTreePath (TreePath []) = "<root>"+renderTreePath (TreePath (x : xs)) = mconcat $ showB x : ["->" <> showB y | y <- xs]++instance (Diff a) => SpecialDiff (CustomTree a) where+  type SpecialDiffError (CustomTree a) = CustomTreeDiffError a++  renderSpecialDiffError = \case+    DiffAtNode path err ->+      let ls = pure $ "Diff between nodes at path " <> renderTreePath path+      in  makeDoc ls err+    WrongLengthsOfChildren path l r ->+      let ls =+            ("Child lists at path " <> renderTreePath path <> " are wrong lengths")+              :| [ "Length of left child list: " <> showB l+                 , "Length of right child list: " <> showB r+                 ]+      in  linesDoc ls++  specialDiff (CustomTree l) (CustomTree r) = go [] l r+    where+      go curPath (Tree.Node n1 f1) (Tree.Node n2 f2) =+        case diff n1 n2 of+          Error err -> Just $ DiffAtNode curTreePath err+          Equal ->+            let go' n = go (n : curPath)+                goChildren _ [] [] = Nothing+                goChildren n [] ys = Just $ WrongLengthsOfChildren curTreePath n (n + length ys)+                goChildren n xs [] = Just $ WrongLengthsOfChildren curTreePath (n + length xs) n+                goChildren n (x : xs) (y : ys) = go' n x y <|> goChildren (n + 1) xs ys+            in  goChildren 0 f1 f2+        where+          curTreePath = TreePath $ reverse curPath++instance (Diff a) => Diff (CustomTree a) where+  diff = diffWithSpecial
+ test/Generics/Diff/PropertyTestsSpec.hs view
@@ -0,0 +1,55 @@+{-# OPTIONS_GHC -Wno-partial-fields #-}++module Generics.Diff.PropertyTestsSpec where++import Data.Fixed+import Data.Map (Map)+import Data.Proxy+import Data.Sequence (Seq)+import Data.Set (Set)+import Data.Tree (Tree)+import Data.Version+import Foreign.C.Types+import Generics.Diff+import Generics.Diff.Instances ()+import Generics.Diff.Special.Map ()+import Generics.Diff.Special.Seq ()+import Generics.Diff.Special.Set ()+import Generics.Diff.Special.Tree ()+import qualified Test.Hspec as H+import qualified Test.Hspec.QuickCheck as H+import qualified Test.QuickCheck as Q+import Util++spec :: H.Spec+spec = do+  H.describe "x == y => x `diff` y == Equal" $+    manyTypes propEqualGivesEqual+  H.describe "x `diff` y == Equal => x == y" $+    manyTypes propEqualMeansEqual++-- | If the two inputs are equal, 'diff' should return 'Equal'.+propEqualGivesEqual :: forall a. (Q.Arbitrary a, Diff a, Show a) => Proxy a -> Q.Property+propEqualGivesEqual _ = Q.property $ \a -> propDiffResult @a a a Equal++-- | If the two inputs are not equal, 'diff' should never return 'Equal'.+propEqualMeansEqual :: forall a. (Q.Arbitrary a, Eq a, Diff a, Show a) => Proxy a -> Q.Property+propEqualMeansEqual _ = Q.property $ \leftValue rightValue ->+  leftValue /= rightValue Q.==>+    diff @a leftValue rightValue /= Equal++manyTypes :: (forall x. (Q.Arbitrary x, Eq x, Diff x, Show x) => Proxy x -> Q.Property) -> H.Spec+manyTypes prop = do+  H.prop "Set Char" $ prop $ Proxy @(Set Char)+  H.prop "Set Int" $ prop $ Proxy @(Set Int)++  H.prop "Seq Rational" $ prop $ Proxy @(Seq Rational)+  H.prop "Seq Version" $ prop $ Proxy @(Seq Version)+  H.prop "Seq CLong" $ prop $ Proxy @(Seq CLong)++  H.prop "Tree CChar" $ prop $ Proxy @(Tree CChar)+  H.prop "Tree Uni" $ prop $ Proxy @(Tree Uni)+  H.prop "Tree Deci" $ prop $ Proxy @(Tree Deci)++  H.prop "Map Int Char" $ prop $ Proxy @(Map Int Char)+  H.prop "Map Char Int" $ prop $ Proxy @(Map Char Int)
+ test/Generics/Diff/UnitTestsSpec.hs view
@@ -0,0 +1,231 @@+{-# OPTIONS_GHC -Wno-partial-fields #-}++module Generics.Diff.UnitTestsSpec where++import Data.Foldable+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Sequence (Seq)+import qualified Data.Sequence as Seq+import Data.Set (Set)+import qualified Data.Set as Set+import qualified Data.Text as T+import Data.Tree (Tree)+import qualified Data.Tree as Tree+import Generics.Diff+import Generics.Diff.Instances ()+import Generics.Diff.Special.Map as Map+import Generics.Diff.Special.Seq ()+import Generics.Diff.Special.Set as Set+import Generics.Diff.Special.Tree+import Generics.SOP+import Generics.SOP.GGP+import qualified Test.Hspec as H+import qualified Test.Hspec.QuickCheck as H+import Util++spec :: H.Spec+spec =+  H.describe "Unit tests" $ do+    H.describe "Map" $ traverse_ specTestSet mapTestSets+    H.describe "Set" $ traverse_ specTestSet setTestSets+    H.describe "Seq" $ traverse_ specTestSet seqTestSets+    H.describe "Tree" $ traverse_ specTestSet treeTestSets+    H.describe "CustomTree" $ traverse_ specTestSet customTreeTestSets++specTestSet :: (Diff a, Show a) => TestSet a -> H.Spec+specTestSet TestSet {..} =+  H.prop (T.unpack setName) $+    propDiffResult leftValue rightValue expectedDiffResult++data TestSet a = TestSet+  { setName :: T.Text+  , leftValue :: a+  , rightValue :: a+  , expectedDiffResult :: DiffResult a+  }+  deriving (Show)++setTestSets :: [TestSet (Set Int)]+setTestSets =+  [ TestSet+      { setName = "Equal"+      , leftValue = value1+      , rightValue = value1+      , expectedDiffResult = Equal+      }+  , TestSet+      { setName = "Diff, LeftMissingKey"+      , leftValue = value1+      , rightValue = value2+      , expectedDiffResult = Error error2+      }+  , TestSet+      { setName = "Diff, RightMissingKey"+      , leftValue = value1+      , rightValue = value3+      , expectedDiffResult = Error error3+      }+  ]+  where+    value1 = Set.fromList [1, 3]++    value2 = Set.fromList [1, 2, 3]+    error2 = DiffSpecial $ Set.LeftMissingKey 2++    value3 = Set.fromList [1]+    error3 = DiffSpecial $ Set.RightMissingKey 3++mapTestSets :: [TestSet (Map Int String)]+mapTestSets =+  [ TestSet+      { setName = "Equal"+      , leftValue = value1+      , rightValue = value1+      , expectedDiffResult = Equal+      }+  , TestSet+      { setName = "Diff, DiffAtKey"+      , leftValue = value1+      , rightValue = value2+      , expectedDiffResult = Error error2+      }+  , TestSet+      { setName = "Diff, LeftMissingKey"+      , leftValue = value1+      , rightValue = value3+      , expectedDiffResult = Error error3+      }+  , TestSet+      { setName = "Diff, RightMissingKey"+      , leftValue = value1+      , rightValue = value4+      , expectedDiffResult = Error error4+      }+  ]+  where+    value1 = Map.fromList [(1, "one"), (3, "three")]++    value2 = Map.fromList [(1, "one"), (3, "THREE")]+    error2 = DiffSpecial $ Map.DiffAtKey 3 TopLevelNotEqual++    value3 = Map.fromList [(1, "one"), (2, "two"), (3, "three")]+    error3 = DiffSpecial $ Map.LeftMissingKey 2++    value4 = Map.fromList [(1, "one")]+    error4 = DiffSpecial $ Map.RightMissingKey 3++seqTestSets :: [TestSet (Seq Int)]+seqTestSets =+  [ TestSet+      { setName = "Equal"+      , leftValue = value1+      , rightValue = value1+      , expectedDiffResult = Equal+      }+  , TestSet+      { setName = "Diff, WrongLengths"+      , leftValue = value1+      , rightValue = value2+      , expectedDiffResult = Error error2+      }+  , TestSet+      { setName = "Diff, DiffAtIndex"+      , leftValue = value1+      , rightValue = value3+      , expectedDiffResult = Error error3+      }+  ]+  where+    value1 = Seq.fromList [1, 3]++    value2 = Seq.fromList [1, 3, 4]+    error2 = DiffSpecial $ WrongLengths 2 3++    value3 = Seq.fromList [1, 2]+    error3 = DiffSpecial $ DiffAtIndex 1 TopLevelNotEqual++treeTestSets :: [TestSet (Tree Int)]+treeTestSets =+  [ TestSet+      { setName = "Equal"+      , leftValue = value1+      , rightValue = value1+      , expectedDiffResult = Equal+      }+  , TestSet+      { setName = "Diff, FieldMismatch, level 1"+      , leftValue = value1+      , rightValue = value2+      , expectedDiffResult = Error error2+      }+  , TestSet+      { setName = "Diff, FieldMismatch, level 2, WrongLengths"+      , leftValue = value1+      , rightValue = value3+      , expectedDiffResult = Error error3+      }+  , TestSet+      { setName = "Diff, FieldMismatch, level 2, DiffAtIndex"+      , leftValue = value1+      , rightValue = value4+      , expectedDiffResult = Error error4+      }+  ]+  where+    value1 = Tree.Node 1 [Tree.Node 2 [], Tree.Node 3 [Tree.Node 4 [], Tree.Node 5 []]]++    value2 = Tree.Node 2 []+    error2 = DiffSpecial $ FieldMismatch $ DiffAtField $ Z $ nodeInfo :*: Z TopLevelNotEqual++    value3 = Tree.Node 1 [Tree.Node 2 []]+    error3 =+      let e = DiffSpecial $ WrongLengths 2 1+      in  DiffSpecial $ FieldMismatch $ DiffAtField $ Z $ nodeInfo :*: S (Z e)++    value4 = Tree.Node 1 [Tree.Node 2 [], Tree.Node 4 []]+    error4 =+      let e = DiffSpecial $ DiffAtIndex 1 $ DiffSpecial $ FieldMismatch $ DiffAtField $ Z $ nodeInfo :*: Z TopLevelNotEqual+      in  DiffSpecial $ FieldMismatch $ DiffAtField $ Z $ nodeInfo :*: S (Z e)++    nodeInfo :: ConstructorInfo '[Int, [Tree Int]]+    nodeInfo :* _ = constructorInfo $ gdatatypeInfo $ Proxy @(Tree Int)++customTreeTestSets :: [TestSet (CustomTree Int)]+customTreeTestSets =+  [ TestSet+      { setName = "Equal"+      , leftValue = value1+      , rightValue = value1+      , expectedDiffResult = Equal+      }+  , TestSet+      { setName = "Diff, DiffAtNode, level 1"+      , leftValue = value1+      , rightValue = value2+      , expectedDiffResult = Error error2+      }+  , TestSet+      { setName = "Diff, WrongLengthsOfChildren, level 2"+      , leftValue = value1+      , rightValue = value3+      , expectedDiffResult = Error error3+      }+  , TestSet+      { setName = "Diff, DiffAtNode, level 2"+      , leftValue = value1+      , rightValue = value4+      , expectedDiffResult = Error error4+      }+  ]+  where+    value1 = CustomTree $ Tree.Node 1 [Tree.Node 2 [], Tree.Node 3 [Tree.Node 4 [], Tree.Node 5 []]]++    value2 = CustomTree $ Tree.Node 2 []+    error2 = DiffSpecial $ DiffAtNode (TreePath []) TopLevelNotEqual++    value3 = CustomTree $ Tree.Node 1 [Tree.Node 2 []]+    error3 = DiffSpecial $ WrongLengthsOfChildren (TreePath []) 2 1++    value4 = CustomTree $ Tree.Node 1 [Tree.Node 2 [], Tree.Node 4 []]+    error4 = DiffSpecial $ DiffAtNode (TreePath [1]) TopLevelNotEqual
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ test/Util.hs view
@@ -0,0 +1,23 @@+module Util where++import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Builder as TB+import Generics.Diff+import Generics.Diff.Instances ()+import Generics.Diff.Render+import qualified Test.QuickCheck as Q++propDiffResult :: (Diff a, Show a) => a -> a -> DiffResult a -> Q.Property+propDiffResult leftValue rightValue expectedDiffResult =+  let actualDiffResult = diff leftValue rightValue+      eq = expectedDiffResult == actualDiffResult+      showDiffResult = TL.unpack . TB.toLazyText . renderDiffResult+      addLabel =+        if eq+          then Q.property+          else+            Q.counterexample ("Expected DiffResult:\n" <> showDiffResult expectedDiffResult)+              . Q.counterexample ("Actual DiffResult:\n" <> showDiffResult actualDiffResult)+              . Q.counterexample ("Left value:\n" <> show leftValue)+              . Q.counterexample ("Right value:\n" <> show rightValue)+  in  addLabel eq