packages feed

genvalidity-mergeless 0.0.0.0 → 0.1.0.0

raw patch · 8 files changed

+761/−560 lines, 8 filesdep +criteriondep +genvalidity-criteriondep +genvalidity-uuiddep −genvalidity-typed-uuiddep −typed-uuiddep ~mergelessPVP ok

version bump matches the API change (PVP)

Dependencies added: criterion, genvalidity-criterion, genvalidity-uuid, pretty-show

Dependencies removed: genvalidity-typed-uuid, typed-uuid

Dependency ranges changed: mergeless

API changes (from Hackage documentation)

- Data.GenValidity.Mergeless: instance (Data.GenValidity.GenInvalid i, Data.GenValidity.GenInvalid a) => Data.GenValidity.GenInvalid (Data.Mergeless.StoreItem i a)
- Data.GenValidity.Mergeless: instance (Data.GenValidity.GenInvalid i, Data.GenValidity.GenInvalid a) => Data.GenValidity.GenInvalid (Data.Mergeless.Synced i a)
- Data.GenValidity.Mergeless: instance (Data.GenValidity.GenInvalid i, Data.GenValidity.GenInvalid a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenInvalid (Data.Mergeless.CentralStore i a)
- Data.GenValidity.Mergeless: instance (Data.GenValidity.GenInvalid i, Data.GenValidity.GenInvalid a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenInvalid (Data.Mergeless.Store i a)
- Data.GenValidity.Mergeless: instance (Data.GenValidity.GenInvalid i, Data.GenValidity.GenInvalid a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenInvalid (Data.Mergeless.SyncRequest i a)
- Data.GenValidity.Mergeless: instance (Data.GenValidity.GenInvalid i, Data.GenValidity.GenInvalid a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenInvalid (Data.Mergeless.SyncResponse i a)
- Data.GenValidity.Mergeless: instance (Data.GenValidity.GenUnchecked i, Data.GenValidity.GenUnchecked a) => Data.GenValidity.GenUnchecked (Data.Mergeless.StoreItem i a)
- Data.GenValidity.Mergeless: instance (Data.GenValidity.GenUnchecked i, Data.GenValidity.GenUnchecked a) => Data.GenValidity.GenUnchecked (Data.Mergeless.Synced i a)
- Data.GenValidity.Mergeless: instance (Data.GenValidity.GenUnchecked i, Data.GenValidity.GenUnchecked a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenUnchecked (Data.Mergeless.CentralStore i a)
- Data.GenValidity.Mergeless: instance (Data.GenValidity.GenUnchecked i, Data.GenValidity.GenUnchecked a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenUnchecked (Data.Mergeless.Store i a)
- Data.GenValidity.Mergeless: instance (Data.GenValidity.GenUnchecked i, Data.GenValidity.GenUnchecked a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenUnchecked (Data.Mergeless.SyncRequest i a)
- Data.GenValidity.Mergeless: instance (Data.GenValidity.GenUnchecked i, Data.GenValidity.GenUnchecked a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenUnchecked (Data.Mergeless.SyncResponse i a)
- Data.GenValidity.Mergeless: instance (Data.GenValidity.GenValid i, Data.GenValidity.GenValid a) => Data.GenValidity.GenValid (Data.Mergeless.StoreItem i a)
- Data.GenValidity.Mergeless: instance (Data.GenValidity.GenValid i, Data.GenValidity.GenValid a) => Data.GenValidity.GenValid (Data.Mergeless.Synced i a)
- Data.GenValidity.Mergeless: instance (Data.GenValidity.GenValid i, Data.GenValidity.GenValid a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenValid (Data.Mergeless.CentralStore i a)
- Data.GenValidity.Mergeless: instance (Data.GenValidity.GenValid i, Data.GenValidity.GenValid a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenValid (Data.Mergeless.Store i a)
- Data.GenValidity.Mergeless: instance (Data.GenValidity.GenValid i, Data.GenValidity.GenValid a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenValid (Data.Mergeless.SyncRequest i a)
- Data.GenValidity.Mergeless: instance (Data.GenValidity.GenValid i, Data.GenValidity.GenValid a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenValid (Data.Mergeless.SyncResponse i a)
- Data.GenValidity.Mergeless: instance Data.GenValidity.GenInvalid a => Data.GenValidity.GenInvalid (Data.Mergeless.Added a)
- Data.GenValidity.Mergeless: instance Data.GenValidity.GenInvalid a => Data.GenValidity.GenInvalid (Data.Mergeless.CentralItem a)
- Data.GenValidity.Mergeless: instance Data.GenValidity.GenUnchecked a => Data.GenValidity.GenUnchecked (Data.Mergeless.Added a)
- Data.GenValidity.Mergeless: instance Data.GenValidity.GenUnchecked a => Data.GenValidity.GenUnchecked (Data.Mergeless.CentralItem a)
- Data.GenValidity.Mergeless: instance Data.GenValidity.GenValid a => Data.GenValidity.GenValid (Data.Mergeless.Added a)
- Data.GenValidity.Mergeless: instance Data.GenValidity.GenValid a => Data.GenValidity.GenValid (Data.Mergeless.CentralItem a)
+ Data.GenValidity.Mergeless.Collection: genUnsyncedStore :: forall i a. (Ord i, Ord a, GenValid i, GenValid a) => Gen (ClientStore i a)
+ Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenUnchecked i, Data.GenValidity.GenUnchecked a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenUnchecked (Data.Mergeless.Collection.ServerStore i a)
+ Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenUnchecked i, Data.GenValidity.GenUnchecked a, GHC.Show.Show i, GHC.Show.Show a, Data.GenValidity.GenInvalid i, Data.GenValidity.GenInvalid a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenInvalid (Data.Mergeless.Collection.ClientStore i a)
+ Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenUnchecked i, Data.GenValidity.GenUnchecked a, GHC.Show.Show i, GHC.Show.Show a, Data.GenValidity.GenInvalid i, Data.GenValidity.GenInvalid a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenInvalid (Data.Mergeless.Collection.ServerStore i a)
+ Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenUnchecked i, Data.GenValidity.GenUnchecked a, GHC.Show.Show i, GHC.Show.Show a, Data.GenValidity.GenInvalid i, Data.GenValidity.GenInvalid a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenInvalid (Data.Mergeless.Collection.SyncRequest i a)
+ Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenUnchecked i, Data.GenValidity.GenUnchecked a, GHC.Show.Show i, GHC.Show.Show a, Data.GenValidity.GenInvalid i, Data.GenValidity.GenInvalid a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenInvalid (Data.Mergeless.Collection.SyncResponse i a)
+ Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenUnchecked i, Data.GenValidity.GenUnchecked a, GHC.Show.Show i, GHC.Show.Show a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenUnchecked (Data.Mergeless.Collection.ClientStore i a)
+ Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenUnchecked i, Data.GenValidity.GenUnchecked a, GHC.Show.Show i, GHC.Show.Show a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenUnchecked (Data.Mergeless.Collection.SyncRequest i a)
+ Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenUnchecked i, Data.GenValidity.GenUnchecked a, GHC.Show.Show i, GHC.Show.Show a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenUnchecked (Data.Mergeless.Collection.SyncResponse i a)
+ Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenValid i, Data.GenValidity.GenValid a, GHC.Show.Show i, GHC.Show.Show a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenValid (Data.Mergeless.Collection.ClientStore i a)
+ Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenValid i, Data.GenValidity.GenValid a, GHC.Show.Show i, GHC.Show.Show a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenValid (Data.Mergeless.Collection.ServerStore i a)
+ Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenValid i, Data.GenValidity.GenValid a, GHC.Show.Show i, GHC.Show.Show a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenValid (Data.Mergeless.Collection.SyncRequest i a)
+ Data.GenValidity.Mergeless.Collection: instance (Data.GenValidity.GenValid i, Data.GenValidity.GenValid a, GHC.Show.Show i, GHC.Show.Show a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.GenValidity.GenValid (Data.Mergeless.Collection.SyncResponse i a)
+ Data.GenValidity.Mergeless.Collection: instance Data.GenValidity.GenUnchecked Data.Mergeless.Collection.ClientId
+ Data.GenValidity.Mergeless.Collection: instance Data.GenValidity.GenValid Data.Mergeless.Collection.ClientId
+ Data.GenValidity.Mergeless.Collection: mapWithIds :: (Ord i, GenValid a) => Set i -> Gen (Map i a)
+ Data.GenValidity.Mergeless.Collection: splitSet :: Ord i => Set i -> Gen (Set i, Set i)
+ Data.GenValidity.Mergeless.Item: instance Data.GenValidity.GenUnchecked a => Data.GenValidity.GenUnchecked (Data.Mergeless.Item.ClientItem a)
+ Data.GenValidity.Mergeless.Item: instance Data.GenValidity.GenUnchecked a => Data.GenValidity.GenUnchecked (Data.Mergeless.Item.ItemSyncRequest a)
+ Data.GenValidity.Mergeless.Item: instance Data.GenValidity.GenUnchecked a => Data.GenValidity.GenUnchecked (Data.Mergeless.Item.ItemSyncResponse a)
+ Data.GenValidity.Mergeless.Item: instance Data.GenValidity.GenUnchecked a => Data.GenValidity.GenUnchecked (Data.Mergeless.Item.ServerItem a)
+ Data.GenValidity.Mergeless.Item: instance Data.GenValidity.GenValid a => Data.GenValidity.GenValid (Data.Mergeless.Item.ClientItem a)
+ Data.GenValidity.Mergeless.Item: instance Data.GenValidity.GenValid a => Data.GenValidity.GenValid (Data.Mergeless.Item.ItemSyncRequest a)
+ Data.GenValidity.Mergeless.Item: instance Data.GenValidity.GenValid a => Data.GenValidity.GenValid (Data.Mergeless.Item.ItemSyncResponse a)
+ Data.GenValidity.Mergeless.Item: instance Data.GenValidity.GenValid a => Data.GenValidity.GenValid (Data.Mergeless.Item.ServerItem a)

Files

+ bench/Bench.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Main where++import Criterion.Main as Criterion++import Data.GenValidity.Criterion+import Data.GenValidity.Mergeless ()++import Data.Mergeless.Collection+import Data.Mergeless.Item++main :: IO ()+main =+  Criterion.defaultMain+    [ bgroup+        "Utils"+        [ genValidBench @(Added Bool)+        , genValidBench @(Synced Bool)+        , genValidBench @(ClientAddition Int)+        ]+    , bgroup+        "Item"+        [ genValidBench @(ClientItem Bool)+        , genValidBench @(ItemSyncRequest Bool)+        , genValidBench @(ItemSyncResponse Bool)+        , genValidBench @(ServerItem Bool)+        ]+    , bgroup+        "Collection"+        [ genValidBench @ClientId+        , genValidBench @(ClientStore Int Bool)+        , genValidBench @(SyncRequest Int Bool)+        , genValidBench @(ClientAddition Int)+        , genValidBench @(SyncResponse Int Bool)+        , genValidBench @(ServerStore Int Bool)+        ]+    ]
genvalidity-mergeless.cabal view
@@ -1,11 +1,13 @@--- This file has been generated from package.yaml by hpack version 0.28.2.+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.33.0. -- -- see: https://github.com/sol/hpack ----- hash: 6e70257c58f9b084549d6af348ed63f6b22a88fc476bc7ccf0ccc1aa729f5891+-- hash: 13b8526e5aa6ded49e51ebf12a1008ee6f2e8f428b081a7766bfc17ddeef7848  name:           genvalidity-mergeless-version:        0.0.0.0+version:        0.1.0.0 description:    Please see the README on GitHub at <https://github.com/NorfairKing/mergeless#readme> homepage:       https://github.com/NorfairKing/mergeless#readme bug-reports:    https://github.com/NorfairKing/mergeless/issues@@ -14,10 +16,9 @@ copyright:      Copyright: (c) 2018 Tom Sydney Kerckhove license:        MIT build-type:     Simple-cabal-version:  >= 1.10 extra-source-files:-    ChangeLog.md     README.md+    ChangeLog.md  source-repository head   type: git@@ -26,6 +27,8 @@ library   exposed-modules:       Data.GenValidity.Mergeless+      Data.GenValidity.Mergeless.Collection+      Data.GenValidity.Mergeless.Item   other-modules:       Paths_genvalidity_mergeless   hs-source-dirs:@@ -33,17 +36,19 @@   build-depends:       QuickCheck     , base >=4.7 && <5+    , containers     , genvalidity     , genvalidity-containers     , genvalidity-time-    , mergeless+    , mergeless >=0.2.0.0   default-language: Haskell2010 -test-suite genvalidity-mergeless-test+test-suite mergeless-test   type: exitcode-stdio-1.0   main-is: Spec.hs   other-modules:-      Data.MergelessSpec+      Data.Mergeless.CollectionSpec+      Data.Mergeless.ItemSpec       Paths_genvalidity_mergeless   hs-source-dirs:       test@@ -55,12 +60,28 @@     , genvalidity-hspec     , genvalidity-hspec-aeson     , genvalidity-mergeless-    , genvalidity-typed-uuid+    , genvalidity-uuid     , hspec     , mergeless     , mtl+    , pretty-show     , random     , time-    , typed-uuid     , uuid+  default-language: Haskell2010++benchmark genvalidity-mergeless-bench+  type: exitcode-stdio-1.0+  main-is: Bench.hs+  other-modules:+      Paths_genvalidity_mergeless+  hs-source-dirs:+      bench+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base >=4.7 && <5+    , criterion+    , genvalidity-criterion+    , genvalidity-mergeless+    , mergeless   default-language: Haskell2010
src/Data/GenValidity/Mergeless.hs view
@@ -1,82 +1,7 @@ {-# OPTIONS_GHC -fno-warn-orphans #-} -module Data.GenValidity.Mergeless where--import Test.QuickCheck--import Data.GenValidity-import Data.GenValidity.Containers ()-import Data.GenValidity.Time ()--import Data.Mergeless--instance (GenUnchecked i, GenUnchecked a, Ord i, Ord a) =>-         GenUnchecked (Store i a)--instance (GenValid i, GenValid a, Ord i, Ord a) => GenValid (Store i a) where-    genValid = (Store <$> genValid) `suchThat` isValid--instance (GenInvalid i, GenInvalid a, Ord i, Ord a) =>-         GenInvalid (Store i a)--instance (GenUnchecked i, GenUnchecked a) => GenUnchecked (StoreItem i a)--instance (GenValid i, GenValid a) => GenValid (StoreItem i a)--instance (GenInvalid i, GenInvalid a) => GenInvalid (StoreItem i a)--instance GenUnchecked a => GenUnchecked (Added a)--instance GenValid a => GenValid (Added a) where-    genValid = (Added <$> genValid <*> genValid) `suchThat` isValid--instance GenInvalid a => GenInvalid (Added a)--instance (GenUnchecked i, GenUnchecked a) => GenUnchecked (Synced i a)--instance (GenValid i, GenValid a) => GenValid (Synced i a) where-    genValid =-        (Synced <$> genValid <*> genValid <*> genValid <*> genValid) `suchThat`-        isValid--instance (GenInvalid i, GenInvalid a) => GenInvalid (Synced i a)--instance (GenUnchecked i, GenUnchecked a, Ord i, Ord a) =>-         GenUnchecked (SyncRequest i a)--instance (GenValid i, GenValid a, Ord i, Ord a) =>-         GenValid (SyncRequest i a) where-    genValid =-        (SyncRequest <$> genValid <*> genValid <*> genValid) `suchThat` isValid--instance (GenInvalid i, GenInvalid a, Ord i, Ord a) =>-         GenInvalid (SyncRequest i a)--instance (GenUnchecked i, GenUnchecked a, Ord i, Ord a) =>-         GenUnchecked (SyncResponse i a)--instance (GenValid i, GenValid a, Ord i, Ord a) =>-         GenValid (SyncResponse i a) where-    genValid =-        (SyncResponse <$> genValid <*> genValid <*> genValid) `suchThat` isValid--instance (GenInvalid i, GenInvalid a, Ord i, Ord a) =>-         GenInvalid (SyncResponse i a)--instance GenUnchecked a => GenUnchecked (CentralItem a)--instance GenValid a => GenValid (CentralItem a) where-    genValid =-        (CentralItem <$> genValid <*> genValid <*> genValid) `suchThat` isValid--instance GenInvalid a => GenInvalid (CentralItem a)--instance (GenUnchecked i, GenUnchecked a, Ord i, Ord a) =>-         GenUnchecked (CentralStore i a)--instance (GenValid i, GenValid a, Ord i, Ord a) =>-         GenValid (CentralStore i a) where-    genValid = (CentralStore <$> genValid) `suchThat` isValid+module Data.GenValidity.Mergeless+  ( module Data.GenValidity.Mergeless.Collection+  ) where -instance (GenInvalid i, GenInvalid a, Ord i, Ord a) =>-         GenInvalid (CentralStore i a)+import Data.GenValidity.Mergeless.Collection
+ src/Data/GenValidity/Mergeless/Collection.hs view
@@ -0,0 +1,109 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Data.GenValidity.Mergeless.Collection where++import qualified Data.Map as M+import Data.Map (Map)+import qualified Data.Set as S+import Data.Set (Set)++import Control.Monad++import Data.GenValidity+import Data.GenValidity.Containers ()+import Data.GenValidity.Time ()+import Test.QuickCheck++import Data.GenValidity.Mergeless.Item ()+import Data.Mergeless++instance GenUnchecked ClientId++instance GenValid ClientId where+  genValid = genValidStructurallyWithoutExtraChecking+  shrinkValid = shrinkValidStructurallyWithoutExtraFiltering++instance (GenUnchecked i, GenUnchecked a, Show i, Show a, Ord i, Ord a) =>+         GenUnchecked (ClientStore i a)++instance (GenValid i, GenValid a, Show i, Show a, Ord i, Ord a) => GenValid (ClientStore i a) where+  genValid =+    sized $ \n -> do+      (a, b, c) <- genSplit3 n+      (s1, s2) <- resize (a + b) (genValid >>= splitSet)+      clientStoreAdded <- resize c genValid+      clientStoreSynced <- mapWithIds s1+      let clientStoreDeleted = s2+      pure ClientStore {..}+  shrinkValid = shrinkValidStructurally++instance (GenUnchecked i, GenUnchecked a, Show i, Show a, GenInvalid i, GenInvalid a, Ord i, Ord a) =>+         GenInvalid (ClientStore i a)++instance (GenUnchecked i, GenUnchecked a, Show i, Show a, Ord i, Ord a) =>+         GenUnchecked (SyncRequest i a)++instance (GenValid i, GenValid a, Show i, Show a, Ord i, Ord a) => GenValid (SyncRequest i a) where+  genValid =+    sized $ \n -> do+      (a, b, c) <- genSplit3 n+      (s1, s2) <- resize (a + b) (genValid >>= splitSet)+      syncRequestAdded <- resize c genValid+      let syncRequestSynced = s1+      let syncRequestDeleted = s2+      pure SyncRequest {..}+  shrinkValid = shrinkValidStructurally++instance (GenUnchecked i, GenUnchecked a, Show i, Show a, GenInvalid i, GenInvalid a, Ord i, Ord a) =>+         GenInvalid (SyncRequest i a)++instance (GenUnchecked i, GenUnchecked a, Show i, Show a, Ord i, Ord a) =>+         GenUnchecked (SyncResponse i a)++instance (GenValid i, GenValid a, Show i, Show a, Ord i, Ord a) => GenValid (SyncResponse i a) where+  genValid = do+    (s1, s2) <- genValid >>= splitSet+    (s3, s4) <- splitSet s1+    (s5, s6) <- splitSet s2+    syncResponseClientAdded <-+      fmap M.fromList $+      forM (S.toList s3) $ \i -> do+        cid <- genValid -- TODO maybe we can find a way to not generate duplicate client ids and speed up this generator, but it's fine for now.+        pure (cid, i)+    let syncResponseClientDeleted = s4+    syncResponseServerAdded <- mapWithIds s5+    let syncResponseServerDeleted = s6+    pure SyncResponse {..}+  shrinkValid = shrinkValidStructurally++splitSet :: Ord i => Set i -> Gen (Set i, Set i)+splitSet s =+  if S.null s+    then pure (S.empty, S.empty)+    else do+      a <- elements $ S.toList s+      pure $ S.split a s++mapWithIds :: (Ord i, GenValid a) => Set i -> Gen (Map i a)+mapWithIds s = fmap M.fromList $ forM (S.toList s) $ \i -> (,) i <$> genValid++instance (GenUnchecked i, GenUnchecked a, Show i, Show a, GenInvalid i, GenInvalid a, Ord i, Ord a) =>+         GenInvalid (SyncResponse i a)++instance (GenUnchecked i, GenUnchecked a, Ord i, Ord a) => GenUnchecked (ServerStore i a)++instance (GenValid i, GenValid a, Show i, Show a, Ord i, Ord a) => GenValid (ServerStore i a) where+  genValid = genValidStructurallyWithoutExtraChecking+  shrinkValid = shrinkValidStructurallyWithoutExtraFiltering++instance (GenUnchecked i, GenUnchecked a, Show i, Show a, GenInvalid i, GenInvalid a, Ord i, Ord a) =>+         GenInvalid (ServerStore i a)++genUnsyncedStore ::+     forall i a. (Ord i, Ord a, GenValid i, GenValid a)+  => Gen (ClientStore i a)+genUnsyncedStore = do+  as <- genValid+  pure $ emptyClientStore {clientStoreAdded = as}
+ src/Data/GenValidity/Mergeless/Item.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Data.GenValidity.Mergeless.Item where++import Data.GenValidity+import Data.GenValidity.Containers ()+import Data.GenValidity.Time ()++import Data.Mergeless.Item++instance GenUnchecked a => GenUnchecked (ClientItem a)++instance GenValid a => GenValid (ClientItem a) where+  genValid = genValidStructurallyWithoutExtraChecking+  shrinkValid = shrinkValidStructurallyWithoutExtraFiltering++instance GenUnchecked a => GenUnchecked (ItemSyncRequest a)++instance GenValid a => GenValid (ItemSyncRequest a) where+  genValid = genValidStructurallyWithoutExtraChecking+  shrinkValid = shrinkValidStructurallyWithoutExtraFiltering++instance GenUnchecked a => GenUnchecked (ItemSyncResponse a)++instance GenValid a => GenValid (ItemSyncResponse a) where+  genValid = genValidStructurallyWithoutExtraChecking+  shrinkValid = shrinkValidStructurallyWithoutExtraFiltering++instance GenUnchecked a => GenUnchecked (ServerItem a)++instance GenValid a => GenValid (ServerItem a) where+  genValid = genValidStructurallyWithoutExtraChecking+  shrinkValid = shrinkValidStructurallyWithoutExtraFiltering
+ test/Data/Mergeless/CollectionSpec.hs view
@@ -0,0 +1,439 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}++module Data.Mergeless.CollectionSpec+  ( spec+  ) where++import Data.Int (Int)+import Data.List+import qualified Data.Map.Strict as M+import Data.Ord+import qualified Data.Set as S+import GHC.Generics (Generic)+import System.Random++import Control.Monad.State++import Test.Hspec+import Test.QuickCheck+import Test.Validity+import Test.Validity.Aeson++import Data.GenValidity.Mergeless.Collection ()+import Data.GenValidity.UUID ()+import Data.Mergeless.Collection+import Data.UUID++{-# ANN module ("HLint: ignore Reduce duplication" :: String) #-}++spec :: Spec+spec = do+  genValidSpec @(ClientStore Int Int)+  jsonSpecOnValid @(ClientStore Int Int)+  genValidSpec @(SyncRequest Int Int)+  jsonSpecOnValid @(SyncRequest Int Int)+  genValidSpec @(SyncResponse Int Int)+  jsonSpecOnValid @(SyncResponse Int Int)+  genValidSpec @(ServerStore Int Int)+  jsonSpecOnValid @(ServerStore Int Int)+  describe "emptyStore" $ it "is valid" $ shouldBeValid (emptyClientStore @Int @Int)+  describe "storeSize" $ do+    it "does not crash" $ producesValidsOnValids (storeSize @Int @Int)+    specify "adding an item makes the store bigger" $+      forAllValid $ \store ->+        forAllValid $ \added -> do+          let size1 = storeSize (store :: ClientStore Int Int)+          let store' = addItemToClientStore added store+          let size2 = storeSize store'+          size2 `shouldBe` (size1 + 1)+    specify "deleting an unsynced item after adding it leaves the store with the original size" $+      forAllValid $ \store ->+        forAllValid $ \added ->+          let size1 = storeSize (store :: ClientStore Int Int)+              store' = addItemToClientStore added store+           in case sortOn (Down . fst) $ M.toList (clientStoreAdded store') of+                [] -> expectationFailure "Expected a nonempty list"+                (i, _):_ ->+                  let store'' = deleteUnsyncedFromClientStore i store'+                      size2 = storeSize store''+                   in size2 `shouldBe` size1+    specify "deleting a synced item after adding it leaves the store with the original size" $+      forAllValid $ \store ->+        forAll (genValid `suchThat` (\uuid -> not $ M.member uuid $ clientStoreSynced store)) $ \uuid ->+          forAllValid $ \synced ->+            let size1 = storeSize (store :: ClientStore Int Int)+                store' = store {clientStoreSynced = M.insert uuid synced $ clientStoreSynced store}+                store'' = deleteSyncedFromClientStore uuid store'+                size2 = storeSize store''+             in size2 `shouldBe` size1+  describe "addItemToClientStore" $+    it "produces valid stores" $ producesValidsOnValids2 (addItemToClientStore @Int @Int)+  describe "deleteUnsyncedFromClientStore" $+    it "produces valid stores" $ producesValidsOnValids2 (deleteUnsyncedFromClientStore @Int @Int)+  describe "deleteSyncedFromClientStore" $+    it "produces valid stores" $ producesValidsOnValids2 (deleteSyncedFromClientStore @Int @Int)+  describe "makeSyncRequest" $+    it "produces valid sync requests" $ producesValidsOnValids (makeSyncRequest @Int @Int)+  describe "mergeSyncResponse" $ do+    it "produces valid sync stores" $ producesValidsOnValids2 (mergeSyncResponse @Int @Int)+    it "adds the single item that the server tells it to add to an empty client store" $+      forAllValid $ \cid ->+        forAllValid $ \a ->+          forAllValid $ \u -> do+            let cstore1 = emptyClientStore {clientStoreAdded = M.singleton cid (a :: Int)}+                resp = emptySyncResponse {syncResponseClientAdded = M.singleton cid (u :: Int)}+                cstore2 = mergeSyncResponse cstore1 resp+            clientStoreSynced cstore2 `shouldBe` M.singleton u a+    it "deletes items that the server instructed to be deleted" $+      forAllValid $ \cs ->+        forAllValid $ \sr -> do+          let cs' = mergeSyncResponse @Int @Int cs sr+          clientStoreDeleted cs' `shouldBe`+            (clientStoreDeleted cs `S.difference` syncResponseClientDeleted sr)+  describe "processServerSync" $+    describe "deterministic UUIDs" $ serverSyncSpec @Int evalDM $ processServerSync genD++serverSyncSpec ::+     forall a i m. (Show i, Ord i, GenValid i, Show a, Ord a, GenValid a, MonadIO m)+  => (forall r. m r -> IO r)+  -> (ServerStore i a -> SyncRequest i a -> m (SyncResponse i a, ServerStore i a))+  -> Spec+serverSyncSpec eval func = do+  describe "Single client" $ do+    describe "Single-item" $ do+      it "succesfully downloads a single item from the server for an empty client" $+        forAllValid $ \u ->+          forAllValid $ \i ->+            eval $ do+              let sstore1 = emptyServerStore {serverStoreItems = M.singleton u i}+              let cstore1 = emptyClientStore+              let req = makeSyncRequest cstore1+              (resp, sstore2) <- func sstore1 req+              let cstore2 = mergeSyncResponse cstore1 resp+              liftIO $ do+                sstore2 `shouldBe` sstore1+                clientStoreSynced cstore2 `shouldBe` serverStoreItems sstore2+      it "succesfully uploads a single item to the server for an empty server" $+        forAllValid $ \c ->+          forAllValid $ \i ->+            eval $ do+              let cstore1 = emptyClientStore {clientStoreAdded = M.singleton c i}+              let sstore1 = emptyServerStore+              let req = makeSyncRequest cstore1+              (resp, sstore2) <- func sstore1 req+              let cstore2 = mergeSyncResponse cstore1 resp+              liftIO $ do+                clientStoreSynced cstore2 `shouldBe` serverStoreItems sstore2+                sort (M.elems (clientStoreSynced cstore2)) `shouldBe`+                  sort (M.elems $ M.singleton c i)+    describe "Multi-item" $ do+      it "succesfully downloads everything from the server for an empty client" $+        forAllValid $ \sstore1 ->+          eval $ do+            let cstore1 = emptyClientStore+            let req = makeSyncRequest cstore1+            (resp, sstore2) <- func sstore1 req+            let cstore2 = mergeSyncResponse cstore1 resp+            liftIO $ do+              sstore2 `shouldBe` sstore1+              clientStoreSynced cstore2 `shouldBe` serverStoreItems sstore2+      it "succesfully uploads everything to the server for an empty server" $+        forAllValid $ \items ->+          eval $ do+            let cstore1 = emptyClientStore {clientStoreAdded = items}+            let sstore1 = emptyServerStore+            let req = makeSyncRequest cstore1+            (resp, sstore2) <- func sstore1 req+            let cstore2 = mergeSyncResponse cstore1 resp+            liftIO $ do+              clientStoreSynced cstore2 `shouldBe` serverStoreItems sstore2+              sort (M.elems (clientStoreSynced cstore2)) `shouldBe` sort (M.elems items)+      it "is idempotent with one client" $+        forAllValid $ \cstore1 ->+          forAllValid $ \sstore1 ->+            eval $ do+              let req1 = makeSyncRequest cstore1+              (resp1, sstore2) <- func sstore1 req1+              let cstore2 = mergeSyncResponse cstore1 resp1+                  req2 = makeSyncRequest cstore2+              (resp2, sstore3) <- func sstore2 req2+              let cstore3 = mergeSyncResponse cstore2 resp2+              liftIO $ do+                cstore2 `shouldBe` cstore3+                sstore2 `shouldBe` sstore3+  describe "Multiple clients" $ do+    describe "Single-item" $ do+      it "successfully syncs an addition accross to a second client" $+        forAllValid $ \i ->+          eval $ do+            let cAstore1 = emptyClientStore {clientStoreAdded = M.singleton (ClientId 0) i}+              -- Client B is empty+            let cBstore1 = emptyClientStore+              -- The server is empty+            let sstore1 = emptyServerStore+              -- Client A makes sync request 1+            let req1 = makeSyncRequest cAstore1+              -- The server processes sync request 1+            (resp1, sstore2) <- func sstore1 req1+            let addedItems = syncResponseClientAdded resp1+            case M.toList addedItems of+              [(ClientId 0, clientAdditionId)] -> do+                let items = M.singleton clientAdditionId i+                liftIO $ sstore2 `shouldBe` (ServerStore {serverStoreItems = items})+                  -- Client A merges the response+                let cAstore2 = mergeSyncResponse cAstore1 resp1+                liftIO $ cAstore2 `shouldBe` (emptyClientStore {clientStoreSynced = items})+                  -- Client B makes sync request 2+                let req2 = makeSyncRequest cBstore1+                  -- The server processes sync request 2+                (resp2, sstore3) <- func sstore2 req2+                liftIO $ do+                  resp2 `shouldBe` (emptySyncResponse {syncResponseServerAdded = items})+                  sstore3 `shouldBe` sstore2+                --  pPrint cBstore2+                  -- Client B merges the response+                let cBstore2 = mergeSyncResponse cBstore1 resp2+                liftIO $ cBstore2 `shouldBe` (emptyClientStore {clientStoreSynced = items})+                  -- Client A and Client B now have the same store+                liftIO $ cAstore2 `shouldBe` cBstore2+              _ -> liftIO $ expectationFailure "Should have found exactly one added item."+      it "succesfully syncs a deletion across to a second client" $+        forAllValid $ \uuid ->+          forAllValid $ \i ->+            eval $ do+              let cAstore1 = emptyClientStore {clientStoreSynced = M.singleton uuid i}+                  -- Client A has a synced item.+                  -- Client B had synced that same item, but has since deleted it.+              let cBstore1 = emptyClientStore {clientStoreDeleted = S.singleton uuid}+                  -- The server still has the undeleted item+              let sstore1 = ServerStore {serverStoreItems = M.singleton uuid i}+                  -- Client B makes sync request 1+              let req1 = makeSyncRequest cBstore1+                  -- The server processes sync request 1+              (resp1, sstore2) <- func sstore1 req1+              liftIO $ do+                resp1 `shouldBe` emptySyncResponse {syncResponseClientDeleted = S.singleton uuid}+                sstore2 `shouldBe` emptyServerStore+                  -- Client B merges the response+              let cBstore2 = mergeSyncResponse cBstore1 resp1+              liftIO $ cBstore2 `shouldBe` emptyClientStore+                  -- Client A makes sync request 2+              let req2 = makeSyncRequest cAstore1+                  -- The server processes sync request 2+              (resp2, sstore3) <- func sstore2 req2+              liftIO $ do+                resp2 `shouldBe` emptySyncResponse {syncResponseServerDeleted = S.singleton uuid}+                sstore3 `shouldBe` sstore2+                  -- Client A merges the response+              let cAstore2 = mergeSyncResponse cAstore1 resp2+              liftIO $ cAstore2 `shouldBe` emptyClientStore+                  -- Client A and Client B now have the same store+              liftIO $ cAstore2 `shouldBe` cBstore2+      it "does not run into a conflict if two clients both try to sync a deletion" $+        forAllValid $ \uuid ->+          forAllValid $ \i ->+            eval $ do+              let cAstore1 = emptyClientStore {clientStoreDeleted = S.singleton uuid}+                  -- Both client a and client b delete an item.+              let cBstore1 = emptyClientStore {clientStoreDeleted = S.singleton uuid}+                  -- The server still has the undeleted item+              let sstore1 = ServerStore {serverStoreItems = M.singleton uuid i}+                  -- Client A makes sync request 1+              let req1 = makeSyncRequest cAstore1+                  -- The server processes sync request 1+              (resp1, sstore2) <- func sstore1 req1+              liftIO $ do+                resp1 `shouldBe` (emptySyncResponse {syncResponseClientDeleted = S.singleton uuid})+                sstore2 `shouldBe` (ServerStore {serverStoreItems = M.empty})+                  -- Client A merges the response+              let cAstore2 = mergeSyncResponse cAstore1 resp1+              liftIO $ cAstore2 `shouldBe` emptyClientStore+                  -- Client B makes sync request 2+              let req2 = makeSyncRequest cBstore1+                  -- The server processes sync request 2+              (resp2, sstore3) <- func sstore2 req2+              liftIO $ do+                resp2 `shouldBe` (emptySyncResponse {syncResponseClientDeleted = S.singleton uuid})+                sstore3 `shouldBe` sstore2+                  -- Client B merges the response+              let cBstore2 = mergeSyncResponse cBstore1 resp2+              liftIO $ do+                cBstore2 `shouldBe` emptyClientStore+                    -- Client A and Client B now have the same store+                cAstore2 `shouldBe` cBstore2+    describe "Multiple items" $ do+      it+        "makes no change if the sync request reflects the same local state with an empty sync response" $+        forAllValid $ \sis -> do+          let cs = ServerStore sis+          (sr, cs') <-+            eval $+            func cs $+            SyncRequest+              { syncRequestAdded = M.empty+              , syncRequestSynced = M.keysSet sis+              , syncRequestDeleted = S.empty+              }+          cs' `shouldBe` cs+          sr `shouldBe`+            SyncResponse+              { syncResponseClientAdded = M.empty+              , syncResponseClientDeleted = S.empty+              , syncResponseServerAdded = M.empty+              , syncResponseServerDeleted = S.empty+              }+      it "successfully syncs additions accross to a second client" $+        forAllValid $ \is ->+          eval $ do+            let cAstore1 = emptyClientStore {clientStoreAdded = is}+              -- Client B is empty+            let cBstore1 = emptyClientStore+              -- The server is empty+            let sstore1 = emptyServerStore+              -- Client A makes sync request 1+            let req1 = makeSyncRequest cAstore1+              -- The server processes sync request 1+            (resp1, sstore2) <- func sstore1 req1+              -- Client A merges the response+            let cAstore2 = mergeSyncResponse cAstore1 resp1+            let items = clientStoreSynced cAstore2+            liftIO $ do+              clientStoreAdded cAstore2 `shouldBe` M.empty+              sstore2 `shouldBe` (ServerStore {serverStoreItems = items})+            liftIO $ cAstore2 `shouldBe` (emptyClientStore {clientStoreSynced = items})+              -- Client B makes sync request 2+            let req2 = makeSyncRequest cBstore1+              -- The server processes sync request 2+            (resp2, sstore3) <- func sstore2 req2+            liftIO $ do+              resp2 `shouldBe` (emptySyncResponse {syncResponseServerAdded = items})+              sstore3 `shouldBe` sstore2+              -- Client B merges the response+            let cBstore2 = mergeSyncResponse cBstore1 resp2+            liftIO $ cBstore2 `shouldBe` (emptyClientStore {clientStoreSynced = items})+              -- Client A and Client B now have the same store+            liftIO $ cAstore2 `shouldBe` cBstore2+      it "succesfully syncs deletions across to a second client" $+        forAllValid $ \syncedItems ->+          eval $ do+            let itemIds = M.keysSet syncedItems+            let cAstore1 = emptyClientStore {clientStoreSynced = syncedItems}+                -- Client A has synced items+                -- Client B had synced the same items, but has since deleted them.+            let cBstore1 = emptyClientStore {clientStoreDeleted = itemIds}+                -- The server still has the undeleted item+            let sstore1 = ServerStore {serverStoreItems = syncedItems}+                -- Client B makes sync request 1+            let req1 = makeSyncRequest cBstore1+                -- The server processes sync request 1+            (resp1, sstore2) <- func sstore1 req1+            liftIO $ do+              resp1 `shouldBe` emptySyncResponse {syncResponseClientDeleted = itemIds}+              sstore2 `shouldBe` emptyServerStore+                -- Client B merges the response+            let cBstore2 = mergeSyncResponse cBstore1 resp1+            liftIO $ cBstore2 `shouldBe` emptyClientStore+                -- Client A makes sync request 2+            let req2 = makeSyncRequest cAstore1+                -- The server processes sync request 2+            (resp2, sstore3) <- func sstore2 req2+            liftIO $ do+              resp2 `shouldBe` emptySyncResponse {syncResponseServerDeleted = itemIds}+              sstore3 `shouldBe` sstore2+                -- Client A merges the response+            let cAstore2 = mergeSyncResponse cAstore1 resp2+            liftIO $ cAstore2 `shouldBe` emptyClientStore+                -- Client A and Client B now have the same store+            liftIO $ cAstore2 `shouldBe` cBstore2+      it "does not run into a conflict if two clients both try to sync a deletion" $+        forAllValid $ \items ->+          eval $ do+            let cAstore1 = emptyClientStore {clientStoreDeleted = M.keysSet items}+                -- Both client a and client b delete their items.+            let cBstore1 = emptyClientStore {clientStoreDeleted = M.keysSet items}+                -- The server still has the undeleted items+            let sstore1 = ServerStore {serverStoreItems = items}+                -- Client A makes sync request 1+            let req1 = makeSyncRequest cAstore1+                -- The server processes sync request 1+            (resp1, sstore2) <- func sstore1 req1+            liftIO $ do+              resp1 `shouldBe` (emptySyncResponse {syncResponseClientDeleted = M.keysSet items})+              sstore2 `shouldBe` (ServerStore {serverStoreItems = M.empty}) -- TODO will probably need some sort of tombstoning.+                -- Client A merges the response+            let cAstore2 = mergeSyncResponse cAstore1 resp1+            liftIO $ cAstore2 `shouldBe` emptyClientStore+                -- Client B makes sync request 2+            let req2 = makeSyncRequest cBstore1+                -- The server processes sync request 2+            (resp2, sstore3) <- func sstore2 req2+            liftIO $ do+              resp2 `shouldBe` (emptySyncResponse {syncResponseClientDeleted = M.keysSet items})+              sstore3 `shouldBe` sstore2+                -- Client B merges the response+            let cBstore2 = mergeSyncResponse cBstore1 resp2+            liftIO $ do+              cBstore2 `shouldBe` emptyClientStore+                  -- Client A and Client B now have the same store+              cAstore2 `shouldBe` cBstore2+  describe "General properties" $ do+    it "produces valid results" $+      forAllValid $ \cs ->+        forAllValid $ \sr -> do+          res <- eval $ func cs sr+          shouldBeValid res+    it "successfully syncs two clients using a central store" $+      forAllValid $ \store1 ->+        eval $ do+          let central = ServerStore M.empty+          let store2 = emptyClientStore+          let sreq1 = makeSyncRequest store1+          (sresp1, central') <- func central sreq1+          let store1' = mergeSyncResponse store1 sresp1+          let sreq2 = makeSyncRequest store2+          (sresp2, central'') <- func central' sreq2+          let store2' = mergeSyncResponse store2 sresp2+          let sreq3 = makeSyncRequest store1'+          (sresp3, _) <- func central'' sreq3+          let store1'' = mergeSyncResponse store1' sresp3+          liftIO $ store1'' `shouldBe` store2'+    it "ensures that syncing is idempotent" $+      forAllValid $ \central1 ->+        forAllValid $ \local1 ->+          eval $ do+            let sreq1 = makeSyncRequest local1+            (sresp1, central2) <- func central1 sreq1+            let local2 = mergeSyncResponse local1 sresp1+            let sreq2 = makeSyncRequest local2+            (sresp2, central3) <- func central2 sreq2+            let local3 = mergeSyncResponse local2 sresp2+            liftIO $ do+              local2 `shouldBe` local3+              central2 `shouldBe` central3++newtype D m a =+  D+    { unD :: StateT StdGen m a+    }+  deriving (Generic, Functor, Applicative, Monad, MonadState StdGen, MonadTrans, MonadIO)++-- evalD :: D Identity a -> a+-- evalD = runIdentity . evalDM+-- runD :: D Identity a -> StdGen -> (a, StdGen)+-- runD = runState . unD+evalDM :: Functor m => D m a -> m a+evalDM d = fst <$> runDM d (mkStdGen 42)++runDM :: D m a -> StdGen -> m (a, StdGen)+runDM = runStateT . unD++genD :: Monad m => D m UUID+genD = do+  r <- get+  let (u, r') = random r+  put r'+  pure u
+ test/Data/Mergeless/ItemSpec.hs view
@@ -0,0 +1,104 @@+{-# LANGUAGE TypeApplications #-}++module Data.Mergeless.ItemSpec+  ( spec+  ) where++import Data.Int (Int)++import Test.Hspec+import Test.Validity+import Test.Validity.Aeson++import Data.GenValidity.Mergeless.Item ()+import Data.GenValidity.UUID ()+import Data.Mergeless.Item++{-# ANN module ("HLint: ignore Reduce duplication" :: String) #-}++spec :: Spec+spec = do+  eqSpecOnValid @(ClientItem Int)+  genValidSpec @(ClientItem Int)+  jsonSpecOnValid @(ClientItem Int)+  eqSpecOnValid @(ItemSyncRequest Int)+  genValidSpec @(ItemSyncRequest Int)+  jsonSpecOnValid @(ItemSyncRequest Int)+  eqSpecOnValid @(ItemSyncResponse Int)+  genValidSpec @(ItemSyncResponse Int)+  jsonSpecOnValid @(ItemSyncResponse Int)+  eqSpecOnValid @(ServerItem Int)+  genValidSpec @(ServerItem Int)+  jsonSpecOnValid @(ServerItem Int)+  describe "makeItemSyncrequest" $+    it "produces valid requests" $ producesValidsOnValids (makeItemSyncRequest @Int)+  describe "mergeItemSyncResponse" $+    it "produces valid client items" $ producesValidsOnValids2 (mergeItemSyncResponse @Int)+  describe "processItemSync" $+    it "produces valid tuples" $ producesValidsOnValids2 (processServerItemSync @Int)+  describe "processItemSync" $ do+    describe "Interaction" $ do+      it "makes no changes if the sync request reflects the state of the empty server" $ do+        let store1 = ServerItemEmpty+            req = ItemSyncRequestPoll+        let (resp, store2) = processServerItemSync @Int store1 req+        store2 `shouldBe` store1+        resp `shouldBe` ItemSyncResponseInSyncEmpty+      it "makes no changes if the sync request reflects the state of the full server" $+        forAllValid $ \s -> do+          let store1 = ServerItemFull s+              req = ItemSyncRequestKnown+          let (resp, store2) = processServerItemSync @Int store1 req+          store2 `shouldBe` store1+          resp `shouldBe` ItemSyncResponseInSyncFull+    describe "Client changes" $ do+      it "adds the item that the client tells the server to add" $+        forAllValid $ \a -> do+          let store1 = ServerItemEmpty+              req = ItemSyncRequestNew a+          let (resp, store2) = processServerItemSync @Int store1 req+          store2 `shouldBe` ServerItemFull a+          resp `shouldBe` ItemSyncResponseClientAdded+      it "deletes the item that the client tells the server to delete" $+        forAllValid $ \s -> do+          let store1 = ServerItemFull s+              req = ItemSyncRequestDeleted+          let (resp, store2) = processServerItemSync @Int store1 req+          store2 `shouldBe` ServerItemEmpty+          resp `shouldBe` ItemSyncResponseClientDeleted+    describe "Server changes" $ do+      it "tells the client that there is a new item at the server side" $+        forAllValid $ \s -> do+          let store1 = ServerItemFull s+              req = ItemSyncRequestPoll+          let (resp, store2) = processServerItemSync @Int store1 req+          store2 `shouldBe` store1+          resp `shouldBe` ItemSyncResponseServerAdded s+      it "tells the client that there is a deleted item at the server side" $ do+        let store1 = ServerItemEmpty+            req = ItemSyncRequestKnown+        let (resp, store2) = processServerItemSync @Int store1 req+        store2 `shouldBe` store1+        resp `shouldBe` ItemSyncResponseServerDeleted+    describe "High level properties" $ do+      it "it always possible to add an item from scratch" $+        forAllValid $ \a -> do+          let cstore1 = ClientAdded (a :: Int)+          let sstore1 = ServerItemEmpty+          let req1 = makeItemSyncRequest cstore1+              (resp1, sstore2) = processServerItemSync sstore1 req1+              cstore2 = mergeItemSyncResponse cstore1 resp1+          resp1 `shouldBe` ItemSyncResponseClientAdded+          sstore2 `shouldBe` ServerItemFull a+          cstore2 `shouldBe` ClientSynced a+      it "is idempotent with one client" $+        forAllValid $ \si1 ->+          forAllValid $ \ci1 -> do+            let req1 = makeItemSyncRequest @Int ci1+                (resp1, si2) = processServerItemSync si1 req1+                ci2 = mergeItemSyncResponse ci1 resp1+                req2 = makeItemSyncRequest ci2+                (resp2, si3) = processServerItemSync si2 req2+                ci3 = mergeItemSyncResponse ci2 resp2+            ci3 `shouldBe` ci2+            si3 `shouldBe` si2
− test/Data/MergelessSpec.hs
@@ -1,471 +0,0 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}--module Data.MergelessSpec-    ( spec-    ) where--import qualified Data.Map.Strict as M-import qualified Data.Set as S-import Data.Time-import qualified Data.UUID.Typed as Typed-import GHC.Generics (Generic)-import System.Random--import Control.Monad.State--import Test.Hspec-import Test.QuickCheck-import Test.Validity-import Test.Validity.Aeson--import Data.GenValidity.Mergeless ()-import Data.GenValidity.UUID.Typed ()-import Data.Mergeless-import Data.UUID.Typed--{-# ANN module ("HLint: ignore Reduce duplication" :: String) #-}--spec :: Spec-spec = do-    eqSpecOnValid @(Store Double Double)-    ordSpecOnValid @(Store Double Double)-    genValiditySpec @(Store Double Double)-    jsonSpecOnValid @(Store Double Double)-    eqSpecOnValid @(StoreItem Double Double)-    ordSpecOnValid @(StoreItem Double Double)-    genValiditySpec @(StoreItem Double Double)-    jsonSpecOnValid @(StoreItem Double Double)-    eqSpecOnValid @(Added Double)-    ordSpecOnValid @(Added Double)-    genValiditySpec @(Added Double)-    jsonSpecOnValid @(Added Double)-    eqSpecOnValid @(Synced Double Double)-    ordSpecOnValid @(Synced Double Double)-    genValiditySpec @(Synced Double Double)-    jsonSpecOnValid @(Synced Double Double)-    eqSpecOnValid @(SyncRequest Double Double)-    ordSpecOnValid @(SyncRequest Double Double)-    genValiditySpec @(SyncRequest Double Double)-    jsonSpecOnValid @(SyncRequest Double Double)-    eqSpecOnValid @(SyncResponse Double Double)-    ordSpecOnValid @(SyncResponse Double Double)-    genValiditySpec @(SyncResponse Double Double)-    jsonSpecOnValid @(SyncResponse Double Double)-    eqSpecOnValid @(CentralItem Double)-    ordSpecOnValid @(CentralItem Double)-    genValiditySpec @(CentralItem Double)-    jsonSpecOnValid @(CentralItem Double)-    eqSpecOnValid @(CentralStore Double Double)-    ordSpecOnValid @(CentralStore Double Double)-    genValiditySpec @(CentralStore Double Double)-    jsonSpecOnValid @(CentralStore Double Double)-    describe "makeSyncRequest" $-        it "produces valid sync requests" $-        producesValidsOnValids (makeSyncRequest @Double @Double)-    describe "mergeSyncResponse" $-        it "produces valid sync stores" $-        producesValidsOnValids2 (mergeSyncResponse @Double @Double)-    describe "processSyncWith" $ do-        it-            "makes no change if the sync request reflects the same local state with an empty sync response" $-            forAllValid $ \synct ->-                forAllValid $ \sis -> do-                    let cs = CentralStore sis-                    let (sr, cs') =-                            evalD $-                            processSyncWith @(UUID Double) @Double genD synct cs $-                            SyncRequest S.empty (M.keysSet sis) S.empty-                    cs' `shouldBe` cs-                    sr `shouldBe` SyncResponse S.empty S.empty S.empty-        it "deletes the deleted items" $-            forAllValid $ \synct ->-                forAllValid $ \cs ->-                    forAllValid $ \sreq -> do-                        let (_, cs') =-                                evalD $-                                processSyncWith-                                    @(UUID Double)-                                    @Double-                                    genD-                                    synct-                                    cs-                                    sreq-                        syncRequestUndeletedItems sreq `shouldSatisfy`-                            (not .-                             any-                                 (`S.member` (M.keysSet $ centralStoreItems cs')))-        it "returns the items that were added in the sync response" $-            forAllValid $ \synct ->-                forAllValid $ \cs ->-                    forAllValid $ \sreq -> do-                        let (sresp, _) =-                                evalD $-                                processSyncWith-                                    @(UUID Double)-                                    @Double-                                    genD-                                    synct-                                    cs-                                    sreq-                        S.map syncedValue (syncResponseAddedItems sresp) `shouldBe`-                            S.map addedValue (syncRequestAddedItems sreq)-        it "returns the single added item" $-            forAllValid $ \synct ->-                forAllValid $ \cs ->-                    forAllValid $ \ai -> do-                        let (sresp, _) =-                                evalD $-                                processSyncWith-                                    @(UUID Double)-                                    @Double-                                    genD-                                    synct-                                    cs-                                    SyncRequest-                                    { syncRequestAddedItems = S.singleton ai-                                    , syncRequestSyncedItems = S.empty-                                    , syncRequestUndeletedItems = S.empty-                                    }-                        S.map syncedValue (syncResponseAddedItems sresp) `shouldBe`-                            S.singleton (addedValue ai)-        it "adds the items that were added" $-            forAllValid $ \synct ->-                forAllValid $ \cs ->-                    forAllValid $ \sreq -> do-                        let (_, cs') =-                                evalD $-                                processSyncWith-                                    @(UUID Double)-                                    @Double-                                    genD-                                    synct-                                    cs-                                    sreq-                        S.map addedValue (syncRequestAddedItems sreq) `shouldSatisfy`-                            all-                                (`elem` (M.elems $-                                         M.map centralValue $-                                         centralStoreItems cs'))-        it-            "returns the single remotely added item if the sync request is empty and the central store has one item" $-            forAllValid $ \synct ->-                forAllValid $ \(uuid, ci) -> do-                    let (sresp, _) =-                            evalD $-                            processSyncWith-                                @(UUID Double)-                                @Double-                                genD-                                synct-                                (CentralStore $ M.singleton uuid ci)-                                SyncRequest-                                { syncRequestAddedItems = S.empty-                                , syncRequestSyncedItems = S.empty-                                , syncRequestUndeletedItems = S.empty-                                }-                    S.map syncedValue (syncResponseNewRemoteItems sresp) `shouldBe`-                        S.singleton (centralValue ci)-        it-            "returns all remotely added items when no items are locally added or deleted" $-            forAllValid $ \synct ->-                forAllValid $ \cs ->-                    forAllValid $ \sis -> do-                        let (sresp, _) =-                                evalD $-                                processSyncWith-                                    @(UUID Double)-                                    @Double-                                    genD-                                    synct-                                    cs-                                    SyncRequest-                                    { syncRequestAddedItems = S.empty-                                    , syncRequestSyncedItems = sis-                                    , syncRequestUndeletedItems = S.empty-                                    }-                        S.map syncedUuid (syncResponseNewRemoteItems sresp) `shouldBe`-                            S.difference (M.keysSet $ centralStoreItems cs) sis-        it "returns all remotely added items when no items are locally deleted " $-            forAllValid $ \synct ->-                forAllValid $ \cs ->-                    forAllValid $ \sis ->-                        forAllValid $ \ais -> do-                            let (sresp, _) =-                                    evalD $-                                    processSyncWith-                                        @(UUID Double)-                                        @Double-                                        genD-                                        synct-                                        cs-                                        SyncRequest-                                        { syncRequestAddedItems = ais-                                        , syncRequestSyncedItems = sis-                                        , syncRequestUndeletedItems = S.empty-                                        }-                            S.map syncedUuid (syncResponseNewRemoteItems sresp) `shouldBe`-                                S.difference-                                    (M.keysSet $ centralStoreItems cs)-                                    sis-        it-            "returns all remotely added items that weren't deleted when no items are locally added " $-            forAllValid $ \synct ->-                forAllValid $ \cs ->-                    forAllValid $ \sis ->-                        forAllValid $ \dis -> do-                            let (sresp, _) =-                                    evalD $-                                    processSyncWith-                                        @(UUID Double)-                                        @Double-                                        genD-                                        synct-                                        cs-                                        SyncRequest-                                        { syncRequestAddedItems = S.empty-                                        , syncRequestSyncedItems = sis-                                        , syncRequestUndeletedItems = dis-                                        }-                            S.map syncedUuid (syncResponseNewRemoteItems sresp) `shouldBe`-                                S.difference-                                    (S.difference-                                         (M.keysSet $ centralStoreItems cs)-                                         dis)-                                    sis-        it "returns all remotely added items that weren't deleted" $-            forAllValid $ \synct ->-                forAllValid $ \cs ->-                    forAllValid $ \sreq -> do-                        let (sresp, _) =-                                evalD $-                                processSyncWith-                                    @(UUID Double)-                                    @Double-                                    genD-                                    synct-                                    cs-                                    sreq-                        S.map syncedUuid (syncResponseNewRemoteItems sresp) `shouldBe`-                            S.difference-                                (S.difference-                                     (M.keysSet $ centralStoreItems cs)-                                     (syncRequestUndeletedItems sreq))-                                (syncRequestSyncedItems sreq)-        it-            "successfully syncs two clients using a central store when using incrementing words" $-            forAllValid $ \store1 ->-                forAllValid $ \(synct1, synct2, synct3) -> do-                    let (s1, s2) =-                            evalI $ do-                                let central = CentralStore M.empty-                                let store2 = Store S.empty-                                let sreq1 = makeSyncRequest @Word @Double store1-                                (sresp1, central') <--                                    processSyncWith genI synct1 central sreq1-                                let store1' = mergeSyncResponse store1 sresp1-                                let sreq2 = makeSyncRequest store2-                                (sresp2, central'') <--                                    processSyncWith genI synct2 central' sreq2-                                let store2' = mergeSyncResponse store2 sresp2-                                let sreq3 = makeSyncRequest store1'-                                (sresp3, _) <--                                    processSyncWith genI synct3 central'' sreq3-                                let store1'' = mergeSyncResponse store1' sresp3-                                pure (store1'', store2')-                    s1 `shouldBe` s2-        it-            "successfully syncs two clients using a central store when using deterministic UUIDs" $-            forAllValid $ \store1 ->-                forAllValid $ \(synct1, synct2, synct3) -> do-                    let (s1, s2) =-                            evalD $ do-                                let central = CentralStore M.empty-                                let store2 = Store S.empty-                                let sreq1 =-                                        makeSyncRequest-                                            @(UUID Double)-                                            @Double-                                            store1-                                (sresp1, central') <--                                    processSyncWith genD synct1 central sreq1-                                let store1' = mergeSyncResponse store1 sresp1-                                let sreq2 = makeSyncRequest store2-                                (sresp2, central'') <--                                    processSyncWith genD synct2 central' sreq2-                                let store2' = mergeSyncResponse store2 sresp2-                                let sreq3 = makeSyncRequest store1'-                                (sresp3, _) <--                                    processSyncWith genD synct3 central'' sreq3-                                let store1'' = mergeSyncResponse store1' sresp3-                                pure (store1'', store2')-                    s1 `shouldBe` s2-        it-            "produces valid results when building up a central store from nothing using incrementing words" $-            forAllValid $ \tups ->-                shouldBeValid $-                evalI $ do-                    let initCentralStore = CentralStore M.empty-                    let go cs (store, synct) = do-                            let sreq = makeSyncRequest @Word @Double store-                            (_, central') <- processSyncWith genI synct cs sreq-                            pure central'-                    foldM-                        go-                        initCentralStore-                        (tups :: [(Store Word Double, UTCTime)])-        it-            "produces valid results when building up a central store from nothing using deterministic UUIDs" $-            forAllValid $ \tups ->-                shouldBeValid $-                evalD $ do-                    let initCentralStore = CentralStore M.empty-                    let go cs (store, synct) = do-                            let sreq =-                                    makeSyncRequest @(UUID Double) @Double store-                            (_, central') <- processSyncWith genD synct cs sreq-                            pure central'-                    foldM-                        go-                        initCentralStore-                        (tups :: [(Store (UUID Double) Double, UTCTime)])-        -- This property does not hold.-        xit "produces valid results when using incrementing words" $-            producesValidsOnValids3 $ \synct cs sr ->-                evalI $ processSyncWith @Word @Double genI synct cs sr-        it "produces valid results when using determinisitic UUIDs" $-            producesValidsOnValids3 $ \synct cs sr ->-                evalD $ processSyncWith @(UUID Double) @Double genD synct cs sr-        it "makes syncing idempotent with incrementing words" $-            forAllValid $ \synct1 ->-                forAll (genValid `suchThat` (>= synct1)) $ \synct2 ->-                    forAllValid $ \central1 ->-                        forAllValid $ \local1 -> do-                            let d1 = 0-                            let sreq1 = makeSyncRequest @Word @Double local1-                            let ((sresp1, central2), d2) =-                                    runI-                                        (processSyncWith-                                             genI-                                             synct1-                                             central1-                                             sreq1)-                                        d1-                            let local2 = mergeSyncResponse local1 sresp1-                            let sreq2 = makeSyncRequest local2-                            let ((sresp2, central3), _) =-                                    runI-                                        (processSyncWith-                                             genI-                                             synct2-                                             central2-                                             sreq2)-                                        d2-                            let local3 = mergeSyncResponse local2 sresp2-                            local2 `shouldBe` local3-                            central2 `shouldBe` central3-        it "makes syncing idempotent with deterministic UUIDs" $-            forAllValid $ \synct1 ->-                forAll (genValid `suchThat` (>= synct1)) $ \synct2 ->-                    forAllValid $ \central1 ->-                        forAllValid $ \local1 -> do-                            let d1 = mkStdGen 42-                            let sreq1 =-                                    makeSyncRequest-                                        @(UUID Double)-                                        @Double-                                        local1-                            let ((sresp1, central2), d2) =-                                    runD-                                        (processSyncWith-                                             genD-                                             synct1-                                             central1-                                             sreq1)-                                        d1-                            let local2 = mergeSyncResponse local1 sresp1-                            let sreq2 = makeSyncRequest local2-                            let ((sresp2, central3), _) =-                                    runD-                                        (processSyncWith-                                             genD-                                             synct2-                                             central2-                                             sreq2)-                                        d2-                            let local3 = mergeSyncResponse local2 sresp2-                            local2 `shouldBe` local3-                            central2 `shouldBe` central3-        it "makes syncing idempotent with random UUIDs" $-            forAllValid $ \synct1 ->-                forAll (genValid `suchThat` (>= synct1)) $ \synct2 ->-                    forAllValid $ \central1 ->-                        forAllValid $ \local1 -> do-                            let sreq1 =-                                    makeSyncRequest-                                        @(UUID Double)-                                        @Double-                                        local1-                            (sresp1, central2) <--                                processSyncWith-                                    nextRandomUUID-                                    synct1-                                    central1-                                    sreq1-                            let local2 = mergeSyncResponse local1 sresp1-                            let sreq2 = makeSyncRequest local2-                            (sresp2, central3) <--                                processSyncWith-                                    nextRandomUUID-                                    synct2-                                    central2-                                    sreq2-                            let local3 = mergeSyncResponse local2 sresp2-                            local2 `shouldBe` local3-                            central2 `shouldBe` central3-    describe "processSync" $-        it "makes syncing idempotent when using random UUIDs" $-        forAllValid $ \central1 ->-            forAllValid $ \local1 -> do-                let sreq1 = makeSyncRequest @(UUID Double) @Double local1-                (sresp1, central2) <- processSync nextRandomUUID central1 sreq1-                let local2 = mergeSyncResponse local1 sresp1-                let sreq2 = makeSyncRequest local2-                (sresp2, central3) <- processSync nextRandomUUID central2 sreq2-                let local3 = mergeSyncResponse local2 sresp2-                local2 `shouldBe` local3-                central2 `shouldBe` central3--newtype D a = D-    { unD :: State StdGen a-    } deriving (Generic, Functor, Applicative, Monad, MonadState StdGen)--evalD :: D a -> a-evalD d = fst $ runD d $ mkStdGen 42--runD :: D a -> StdGen -> (a, StdGen)-runD = runState . unD--genD :: D (Typed.UUID a)-genD = do-    r <- get-    let (u, r') = random r-    put r'-    pure u--newtype I a = I-    { unI :: State Word a-    } deriving (Generic, Functor, Applicative, Monad, MonadState Word)--evalI :: I a -> a-evalI i = fst $ runI i 0--runI :: I a -> Word -> (a, Word)-runI = runState . unI--genI :: I Word-genI = do-    i <- get-    modify succ-    pure i