packages feed

polysemy-check 0.5.0.0 → 0.6.0.0

raw patch · 6 files changed

+153/−27 lines, 6 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Polysemy.Check: prepropEquivalent :: forall effs x r1 r2 f. (forall a. Show a => Show (f a), forall a. Eq a => Eq (f a)) => (Eq x, Show x, Inject effs r1, Inject effs r2, Members effs effs) => (forall a. Sem r1 a -> IO (f a)) -> (forall a. Sem r2 a -> IO (f a)) -> (forall r. Proxy r -> Members effs r => Gen (Sem r x)) -> Property
+ Polysemy.Check: prepropEquivalent :: forall effs x r1 r2 f. (forall a. Show a => Show (f a), forall a. Eq a => Eq (f a)) => (Eq x, Show x, Inject effs r1, Inject effs r2, Arbitrary (Sem effs x)) => (forall a. Sem r1 a -> IO (f a)) -> (forall a. Sem r2 a -> IO (f a)) -> Property

Files

ChangeLog.md view
@@ -1,5 +1,13 @@ # Changelog for polysemy-check +## v0.6.0.0 (2021-10-16)++- Changed the frequencies of `Sem`'s `Arbitrary` instance, to create more+    interesting programs.+- Removed the program generator parameter of `prepropEquivalent`. It now just+    uses the `Arbitrary` instance for `Sem`.+- Added some new examples to the test suite.+ ## v0.5.0.0 (2021-10-14)  - Flattened the module structure of `Polysemy.Check.Arbitrary`.
polysemy-check.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           polysemy-check-version:        0.5.0.0+version:        0.6.0.0 synopsis:       QuickCheck for Polysemy description:    Please see the README on GitHub at <https://github.com/polysemy-research/polysemy-check#readme> category:       Polysemy@@ -71,6 +71,7 @@       CommutativeSpec       CoverageSpec       EquivSpec+      ExampleSpec       LawSpec       WeirdEffectsSpec       Paths_polysemy_check
src/Polysemy/Check.hs view
@@ -33,7 +33,6 @@   ) where  import Control.Monad (void)-import Data.Proxy import Generics.Kind (GenericK) import Generics.Kind.TH (deriveGenericK) import Polysemy@@ -142,27 +141,27 @@   --------------------------------------------------------------------------------- | Prove that two interpreters are equivalent. For the given generator, this--- property ensures that the two interpreters give the same result for every--- arbitrary program.+-- | Prove that two interpreters are equivalent. This property ensures that the+-- two interpreters give the same result for every arbitrary program. prepropEquivalent     :: forall effs x r1 r2 f      . ( forall a. Show a => Show (f a)        , forall a. Eq a => Eq (f a)        )-    => (Eq x, Show x, Inject effs r1, Inject effs r2, Members effs effs)+    => ( Eq x+       , Show x+       , Inject effs r1+       , Inject effs r2+       , Arbitrary (Sem effs x)+       )     => (forall a. Sem r1 a -> IO (f a))        -- ^ The first interpreter for the effect stack.Pure effect stacks can        -- be lifted into 'IO' via 'pure' after the final 'run'.     -> (forall a. Sem r2 a -> IO (f a))        -- ^ The second interpreter to prove equivalence for.-    -> (forall r. Proxy r -> Members effs r => Gen (Sem r x))-       -- ^ A generator producing arbitrary programs in @r@. The property will-       -- hold true if both interpreters produce the same output for every-       -- program generated by this.     -> Property-prepropEquivalent int1 int2 mksem = property $ do-  SomeSem sem <- liftGen @effs @x $ mksem Proxy+prepropEquivalent int1 int2 = property $ do+  SomeSem sem <- liftGen @effs @x   pure $ ioProperty $ do     a1 <- int1 sem     a2 <- int2 sem@@ -176,10 +175,9 @@  liftGen     :: forall effs a-     . Members effs effs-    => (forall r. Members effs r => Gen (Sem r a))-    -> Gen (SomeSem effs a)-liftGen g = do-  a <- g @effs+     . Arbitrary (Sem effs a)+    => Gen (SomeSem effs a)+liftGen = do+  a <- arbitrary @(Sem effs a)   pure $ SomeSem $ inject a 
src/Polysemy/Check/Arbitrary.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE TupleSections #-}+ {-# OPTIONS_GHC -Wno-orphans #-}  module Polysemy.Check.Arbitrary where@@ -85,15 +87,15 @@      in sized $ \n ->           case n <= 1 of             True -> oneof terminal-            False -> oneof $-              [ do+            False -> frequency $+              [ (2,) $ do                   SomeEffOfType e <- arbitraryActionFromRowOfType @r @r @a                   pure $ send e-              , do+              , (8,) $ do                   SomeEff e <- arbitraryActionFromRow @r @r                   k <- liftArbitrary $ scale (`div` 2) arbitrary                   pure $ send e >>= k-              ] <> terminal+              ] <> fmap (1,) terminal  ------------------------------------------------------------------------------ -- | @genEff \@e \@r \@a@ gets a generator capable of producing every
test/EquivSpec.hs view
@@ -6,7 +6,6 @@ module EquivSpec where  import Data.IORef (newIORef, readIORef)-import Data.Proxy (Proxy) import Polysemy import Polysemy.Check import Polysemy.State@@ -23,11 +22,6 @@       prepropEquivalent @'[State Int] @Int         (runPureState s0)         (runIOState s0)-        $ \(_ :: Proxy r) -> do-            SomeAction e1 <- arbitraryAction @(State Int) @r-            SomeAction e2 <- arbitraryAction @(State Int) @r-            e3 <- arbitraryActionOfType @(State Int) @Int @r-            pure $ send e1 >> send e2 >> send e3   runPureState :: Int -> Sem '[State Int] a -> IO (Int, a)
+ test/ExampleSpec.hs view
@@ -0,0 +1,123 @@+{-# OPTIONS_GHC -fplugin=Polysemy.Plugin #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE DeriveGeneric #-}++module ExampleSpec where++import Control.Monad+import Data.Maybe (listToMaybe)+import Polysemy+import Polysemy.Check+import Polysemy.State+import Test.Hspec+import Test.Hspec.QuickCheck+import Test.QuickCheck+++data Stack s m a where+  Push      :: s -> Stack s m ()+  Pop       :: Stack s m (Maybe s)+  RemoveAll :: Stack s m ()+  Size      :: Stack s m Int++deriving instance Show s => Show (Stack s m a)+deriveGenericK ''Stack++makeSem ''Stack+++spec :: Spec+spec = do+  describe "Laws" $ do+    let law x = prepropLaw @'[Stack Int] x $ pure . run . runStack []++    prop "push >> pop is pure" $ do+      law $ do+        s <- arbitrary+        pure+          ( push s >> pop+          , pure $ Just s+          )++    prop "pop >> push is id" $ do+      law $+        pure+          ( pop >>= maybe (pure ()) push+          , pure ()+          )++    prop "removeAll sets size to 0" $ do+      law $+        pure+          ( 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+          )++    prop "pop decreases size by 1" $ do+      law $ do+        pure+          ( pop >> size+          , fmap (max 0 . subtract 1) size <* pop+          )+++  describe "Equivalence" $ do+    let equiv b1 b2 =+          prepropEquivalent @'[Stack Int] @Int+            (pure . run . runStack b1)+            (pure . run . runStack b2)++    prop "All interpreters are equivalent to itself" $ do+      bugs <- arbitrary+      pure $ equiv bugs bugs++    prop "DontRemove bug interpreter isn't equivalent to the correct version" $+      expectFailure $ equiv [DontRemove] []++    prop "PushTwice bug interpreter isn't equivalent to the correct version" $+      expectFailure $ equiv [PushTwice] []++    prop "The buggy interpreters are not equivalent to one another" $+      expectFailure $ equiv [PushTwice] [DontRemove]+++data Bug+  = PushTwice+  | DontRemove+  deriving stock (Eq, Ord, Show, Enum, Bounded)++instance Arbitrary Bug where+  arbitrary = elements [minBound..maxBound]+++hasBug :: [Bug] -> Bug -> Bool+hasBug = flip elem+++runStack+    :: [Bug]+    -> Sem (Stack s ': r) a+    -> Sem r ([s], a)+runStack bugs =+  (runState [] .) $ reinterpret $ \case+    Push s -> do+      modify (s :)+      when (hasBug bugs PushTwice) $+        modify (s :)+    Pop -> do+      r <- gets listToMaybe+      modify (drop 1)+      pure r+    RemoveAll ->+      unless (hasBug bugs DontRemove) $+        put []+    Size -> gets length+