packages feed

polysemy-check 0.8.1.0 → 0.9.0.0

raw patch · 6 files changed

+214/−62 lines, 6 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Polysemy.Check: Law :: Sem r a -> Sem r a -> [Sem r ()] -> [Sem r z] -> Law r z a
+ Polysemy.Check: [lawLhs] :: Law r z a -> Sem r a
+ Polysemy.Check: [lawPostlude] :: Law r z a -> [Sem r z]
+ Polysemy.Check: [lawPrelude] :: Law r z a -> [Sem r ()]
+ Polysemy.Check: [lawRhs] :: Law r z a -> Sem r a
+ Polysemy.Check: constructorLabel :: Data a => a -> String
+ Polysemy.Check: data Law r z a
+ Polysemy.Check: simpleLaw :: Sem r a -> Sem r a -> Law r () a
- Polysemy.Check: prepropLaw :: forall effs r a f. (forall z. Eq z => Eq (f z), forall z. Show z => Show (f z)) => (Eq a, Show a, ArbitraryEff effs r) => Gen (Sem r a, Sem r a) -> (forall z. Sem r (a, z) -> IO (f (a, z))) -> Property
+ Polysemy.Check: prepropLaw :: forall effs x r a f. (forall z. Eq z => Eq (f z), forall z. Show z => Show (f z)) => (Eq a, Show a, Functor f, ArbitraryEff effs r, Eq x, Show x) => Gen (Law r x a) -> Maybe (f a -> String) -> (forall z. Sem r (a, z) -> IO (f (a, z))) -> Property
- Polysemy.Check: send :: forall e (r :: [(Type -> Type) -> Type -> Type]) a. Member e r => e (Sem r) a -> Sem r a
+ Polysemy.Check: send :: forall e (r :: EffectRow) a. Member e r => e (Sem r) a -> Sem r a

Files

ChangeLog.md view
@@ -1,5 +1,11 @@ # Changelog for polysemy-check +## v0.9.0.0 (2021-12-03)++- `prepropLaw` now generates a new `Law` record, allowing for better control+    over the program prelude and postludes+- `prepropLaw` now takes a labeler, for running QuickCheck coverage+ ## v0.8.1.0 (2021-10-21)  - Added a new function, `prepropAllCommutative` which ensures every effect
polysemy-check.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           polysemy-check-version:        0.8.1.0+version:        0.9.0.0 synopsis:       QuickCheck for Polysemy description:    Please see the README on GitHub at <https://github.com/polysemy-research/polysemy-check#readme> category:       Polysemy@@ -68,6 +68,7 @@   type: exitcode-stdio-1.0   main-is: Main.hs   other-modules:+      BigKeySpec       CommutativeSpec       CoverageSpec       EquivSpec
src/Polysemy/Check.hs view
@@ -7,6 +7,10 @@   , prepropEquivalent   , prepropLaw +    -- * Law Constructors+  , Law (..)+  , simpleLaw+     -- * Generators for Effects   , arbitraryAction   , arbitraryActionOfType@@ -18,6 +22,9 @@   , SomeEff (..)   , SomeEffOfType (..) +  -- * Common labeling functions+  , constructorLabel+     -- * Support for Existential Types   , ExistentialFor @@ -43,6 +50,7 @@ import Polysemy.Internal import Polysemy.Internal.Union.Inject (Inject, inject) import Test.QuickCheck+import Data.Data (Data, showConstr, toConstr)   ------------------------------------------------------------------------------@@ -121,6 +129,34 @@   ------------------------------------------------------------------------------+-- | Data structure containing programs that should be equal, and under which+-- circumstances.+--+-- @since 0.9.0.0+data Law r z a = Law+  { lawLhs      :: Sem r a+    -- ^ 'lawLhs' and 'lawRhs' are being asserted as equal.+  , lawRhs      :: Sem r a+    -- ^ 'lawLhs' and 'lawRhs' are being asserted as equal.+  , lawPrelude  :: [Sem r ()]+    -- ^ A set of actions to possibly run before checking equality. Useful for+    -- ensuring the existence of something being tested.+  , lawPostlude :: [Sem r z]+    -- ^ A set of actions to possibly run after checking equality. Useful for+    -- checking the existence after something was created.+  }+++------------------------------------------------------------------------------+-- | Like 'Law', but for the common case when you don't need a custom prelude+-- or postlude.+--+-- @since 0.9.0.0+simpleLaw :: Sem r a -> Sem r a -> Law r () a+simpleLaw lhs rhs = Law lhs rhs [] []+++------------------------------------------------------------------------------ -- | Prove that two programs in @r@ are equivalent under a given -- interpretation. This is useful for proving laws about particular effects (or -- stacks of effects).@@ -128,41 +164,80 @@ -- For example, any lawful interpretation of @State@ must satisfy the @put s1 -- >> put s2 = put s2@ law. prepropLaw-    :: forall effs r a f+    :: forall effs x r a f      . ( (forall z. Eq z => Eq (f z))        , (forall z. Show z => Show (f z))        )     => ( Eq a        , Show a+       , Functor f        , ArbitraryEff effs r+       , Eq x+       , Show x        )-    => Gen (Sem r a, Sem r a)+    => Gen (Law r x a)        -- ^ A generator for two equivalent programs.+    -> Maybe (f a -> String)+       -- ^ How to label the results for QuickCheck coverage.     -> (forall z. Sem r (a, z) -> IO (f (a, z)))        -- ^ An interpreter for the effect stack down to 'IO'. Pure effect        -- stacks can be lifted into 'IO' via 'pure' after the final 'run'.     -> Property-prepropLaw g lower = property @(Gen Property) $ do-  SomeEff pre <- arbitraryActionFromRow @effs @r-  (m1, m2) <- g-  SomeEff post <- arbitraryActionFromRow @effs @r+prepropLaw g labeler lower = property @(Gen Property) $ do+  Law lhs rhs mprel mpost <- g+  SomeEff pre1 <- arbitraryActionFromRow @effs @r+  prel <- maybeOneof mprel+  SomeEff pre2 <- arbitraryActionFromRow @effs @r+  SomeEff post1 <- arbitraryActionFromRow @effs @r+  post <- maybeOneof mpost+  SomeEff post2 <- arbitraryActionFromRow @effs @r   pure $-    counterexample ("before = " <> show pre) $-    counterexample ("after  = " <> show post) $+    counterexample ("before1 = " <> show pre1) $+    counterexample ("before2 = " <> show pre2) $+    counterexample ("after1  = " <> show post1) $+    counterexample ("after2  = " <> show post2) $       ioProperty $ do         a1 <-           lower $ do-            void $ send pre-            a1 <- m1-            r <- send post-            pure (a1, r)+            void $ send pre1+            void $ prel+            void $ send pre2+            a1 <- lhs+            void $ send post1+            z <- post+            r <- send post2+            pure (a1, (z, r))         a2 <-           lower $ do-            void $ send pre-            a2 <- m2-            r <- send post-            pure (a2, r)-        pure $ a1 === a2+            void $ send pre1+            void prel+            void $ send pre2+            a2 <- rhs+            void $ send post1+            z <- post+            r <- send post2+            pure (a2, (z, r))+        pure+          $ maybe property (\lbl -> label $ lbl $ fmap fst a1) labeler+          $ a1 === a2+++maybeOneof :: [Sem r a] -> Gen (Sem r (Maybe a))+maybeOneof [] = pure $ pure Nothing+maybeOneof res = do+  chance <- elements @Int [0..9]+  case chance < 8 of+    True -> fmap (fmap Just) $ elements res+    False -> pure $ pure Nothing++++------------------------------------------------------------------------------+-- | Label an example with its data constructor.+--+-- @since 0.9.0.0+constructorLabel :: Data a => a -> String+constructorLabel = showConstr . toConstr   ------------------------------------------------------------------------------
+ test/BigKeySpec.hs view
@@ -0,0 +1,74 @@+{-# OPTIONS_GHC -fplugin=Polysemy.Plugin #-}++module BigKeySpec where++import qualified Data.Map as M+import Data.Map (Map)+import Polysemy+import Polysemy.Check+import Test.Hspec+import Test.QuickCheck+import Test.Hspec.QuickCheck+import Polysemy.State+import Data.Functor.Identity+import Data.Word++data KVStore k v m a where+  Store :: k -> v -> KVStore k v m ()+  Delete :: k -> KVStore k v m ()+  Retrieve :: k -> KVStore k v m (Maybe v)++deriving instance (Show k, Show v) => Show (KVStore k v m a)++makeSem ''KVStore+deriveGenericK ''KVStore+++data Bug = HasBug | NoBug++runKVStore :: forall k v r a. Ord k => Bug -> Sem (KVStore k v ': r) a -> Sem r a+runKVStore bug = (evalState (mempty @(Map k v)) .) $  reinterpret $ \case+  Store k v -> modify $ M.insert k v+  Delete k ->+    case bug of+      HasBug -> pure ()+      NoBug -> modify $ M.delete k+  Retrieve k -> gets $ M.lookup k+++deleteGetLaw+    :: forall r+     . r ~ '[KVStore Word64 Bool]+    => Bug+    -> (Word64 -> Bool -> [Sem r ()])+    -> Property+deleteGetLaw bug prel =+  prepropLaw @r+    (do+      k <- arbitrary+      v <- arbitrary+      pure $ Law+        { lawLhs = do+            delete k+            retrieve k+        , lawRhs = do+            delete k+            pure Nothing+        , lawPrelude = prel k v+        , lawPostlude = [] :: [Sem r ()]+        }+    )+    Nothing  -- (Just $ constructorLabel . runIdentity)+    (pure . Identity . run . runKVStore @Word64 @Bool bug)+++spec :: Spec+spec = do+  describe "without prelude" $ do+    -- prop "with bug"    $ deleteGetLaw HasBug $ \_ _ -> []+    prop "without bug" $ deleteGetLaw NoBug $ \_ _ -> []++  describe "with prelude" $ do+    prop "with bug"    $ expectFailure $ deleteGetLaw HasBug $ \k v -> [store k v]+    prop "without bug" $ deleteGetLaw NoBug $ \k v -> [store k v]+
test/ExampleSpec.hs view
@@ -29,44 +29,40 @@ spec :: Spec spec = do   describe "Laws" $ do-    let law x = prepropLaw @'[Stack Int] x $ pure . run . runStack []+    let law x = prepropLaw @'[Stack Int] x (Just $ constructorLabel . fst) $ pure . run . runStack []      prop "push >> pop is pure" $ do       law $ do         s <- arbitrary-        pure-          ( push s >> pop-          , pure $ Just s-          )+        pure $ simpleLaw+          (push s >> pop)+          (pure $ Just s)      prop "pop >> push is id" $ do       law $-        pure-          ( pop >>= maybe (pure ()) push-          , pure ()-          )+        pure $ simpleLaw+          (pop >>= maybe (pure ()) push)+          (pure ()) +     prop "removeAll sets size to 0" $ do       law $-        pure-          ( removeAll >> size-          , removeAll >> pure 0-          )+        pure $ simpleLaw+          (removeAll >> size)+          (removeAll >> pure 0)      prop "push increases size by 1" $ do       law $ do         s <- arbitrary-        pure-          ( push s >> size-          , fmap (+1) size <* push s-          )+        pure $ simpleLaw+          (push s >> size)+          (fmap (+1) size <* push s)      prop "pop decreases size by 1" $ do       law $ do-        pure-          ( pop >> size-          , fmap (max 0 . subtract 1) size <* pop-          )+        pure $ simpleLaw+          (pop >> size)+          (fmap (max 0 . subtract 1) size <* pop)     describe "Equivalence" $ do
test/LawSpec.hs view
@@ -16,24 +16,24 @@   describe "pure state" $ do     prop "put >> put = put" $ do       s <- arbitrary-      pure $ putPutLaw $ runPureState s+      pure $ putPutLaw Nothing $ runPureState s     prop "get >>= put = pure ()" $ do       s <- arbitrary-      pure $ getPutLaw $ runPureState s+      pure $ getPutLaw Nothing $ runPureState s     prop "put >> get = put >> pure" $ do       s <- arbitrary-      pure $ putGetLaw $ runPureState s+      pure $ putGetLaw Nothing $ runPureState s    describe "io state" $ do     prop "put >> put = put" $ do       s <- arbitrary-      pure $ putPutLaw $ runIOState s+      pure $ putPutLaw Nothing $ runIOState s     prop "get >>= put = pure ()" $ do       s <- arbitrary-      pure $ getPutLaw $ runIOState s+      pure $ getPutLaw Nothing $ runIOState s     prop "put >> get = put >> pure" $ do       s <- arbitrary-      pure $ putGetLaw $ runIOState s+      pure $ putGetLaw Nothing $ runIOState s   type LawConstraints f effs s r =@@ -52,18 +52,18 @@      . ( res ~ ()        , LawConstraints f effs s r        )-    => (forall a. Sem r (res, a) -> IO (f (res, a)))+    => Maybe (f res -> String)+    -> (forall a. Sem r (res, a) -> IO (f (res, a)))     -> Property putPutLaw = prepropLaw @effs $ do   s1 <- arbitrary   s2 <- arbitrary-  pure+  pure $ simpleLaw     ( do         put s1-        put s2-    , do-        put s2-    )+        put s2)+    ( do+        put s2)   getPutLaw@@ -71,13 +71,13 @@      . ( res ~ ()        , LawConstraints f effs s r        )-    => (forall a. Sem r (res, a) -> IO (f (res, a)))+    => Maybe (f res -> String)+    -> (forall a. Sem r (res, a) -> IO (f (res, a)))     -> Property getPutLaw = prepropLaw @effs $ do-  pure-    ( get >>= put-    , pure ()-    )+  pure $ simpleLaw+    (get >>= put)+    (pure ())   putGetLaw@@ -85,18 +85,18 @@      . ( res ~ s        , LawConstraints f effs s r        )-    => (forall a. Sem r (res, a) -> IO (f (res, a)))+    => Maybe (f res -> String)+    -> (forall a. Sem r (res, a) -> IO (f (res, a)))     -> Property putGetLaw = prepropLaw @effs $ do   s <- arbitrary-  pure+  pure $ simpleLaw     ( do         put s-        get-    , do+        get)+    ( do         put s-        pure s-    )+        pure s)   runPureState :: Int -> Sem '[State Int] a -> IO (Int, a)