Skip to content

Commit ea2e47c

Browse files
Merge branch 'master' into resurrect-shrinking
2 parents 6305f29 + 7d7e190 commit ea2e47c

File tree

12 files changed

+193
-86
lines changed

12 files changed

+193
-86
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
1+
12
# constrained-generators
3+
[![Coverage Status](https://coveralls.io/repos/github/input-output-hk/constrained-generators/badge.svg?branch=master)](https://coveralls.io/github/input-output-hk/constrained-generators?branch=master)
4+
25
Framework for generating constrained random data using a subset of first order logic

constrained-generators.cabal

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ library
7979
-Wunused-packages
8080

8181
build-depends:
82-
QuickCheck >=2.14 && <2.18,
82+
QuickCheck >=2.15.0.1 && <2.18,
8383
base >=4.18 && <5,
8484
base-orphans,
8585
containers,
@@ -114,7 +114,7 @@ library examples
114114
-Wunused-packages
115115

116116
build-depends:
117-
QuickCheck >=2.14,
117+
QuickCheck >=2.15.0.1,
118118
base >=4.18 && <5,
119119
constrained-generators,
120120
containers,
@@ -125,7 +125,9 @@ test-suite constrained-tests
125125
type: exitcode-stdio-1.0
126126
main-is: Tests.hs
127127
hs-source-dirs: test
128-
other-modules: Constrained.Tests
128+
other-modules:
129+
Constrained.Tests
130+
Constrained.GraphSpec
129131
default-language: Haskell2010
130132
ghc-options:
131133
-Wall
@@ -138,7 +140,7 @@ test-suite constrained-tests
138140
-rtsopts
139141

140142
build-depends:
141-
QuickCheck,
143+
QuickCheck >= 2.15.0.1,
142144
base,
143145
constrained-generators,
144146
containers,

examples/Constrained/Examples/CheatSheet.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ tightFit0 = constrained' $ \x y ->
364364
-- TypeSpec (Cartesian TrueSpec (MemberSpec [0])) []
365365
-- ---
366366
-- assert $ Equal (Fst (ToGeneric v_3)) v_1
367-
-- Env {unEnv = fromList [(v_0,EnvValue 0)]}
367+
-- Env (fromList [(v_0,EnvValue 0)])
368368
-- genFromSpecT ErrorSpec{} with explanation:
369369
-- [1..-1]
370370

src/Constrained/Base.hs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,6 @@ import Constrained.GenT
101101
import Constrained.Generic
102102
import Constrained.List hiding (toList)
103103
import Constrained.TypeErrors
104-
import Control.Monad.Writer (
105-
Writer,
106-
tell,
107-
)
108104
import Data.Foldable (
109105
toList,
110106
)
@@ -527,12 +523,6 @@ class
527523
alternateShow :: TypeSpec a -> BinaryShow
528524
alternateShow _ = NonBinary
529525

530-
monadConformsTo :: a -> TypeSpec a -> Writer [String] Bool
531-
monadConformsTo x spec =
532-
if conformsTo @a x spec
533-
then pure True
534-
else tell ["Fails by " ++ show spec] >> pure False
535-
536526
-- | For some types (especially finite ones) there may be much better ways to construct
537527
-- a Specification than the default method of just adding a large 'bad' list to TypSpec. This
538528
-- function allows each HasSpec instance to decide.

src/Constrained/Conformance.hs

Lines changed: 4 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ module Constrained.Conformance (
1414
conformsToSpec,
1515
conformsToSpecE,
1616
satisfies,
17-
checkPred,
1817
checkPredsE,
1918
) where
2019

@@ -34,59 +33,9 @@ import Data.Semigroup (sconcat)
3433
import Prettyprinter hiding (cat)
3534
import Test.QuickCheck (Property, Testable, property)
3635

37-
-- =========================================================================
38-
39-
-- | Does the Pred evaluate to true under the given Env.
40-
-- If it doesn't, some explanation appears in the failure of the monad 'm'
41-
checkPred :: forall m. MonadGenError m => Env -> Pred -> m Bool
42-
checkPred env = \case
43-
p@(ElemPred bool term xs) -> do
44-
v <- runTerm env term
45-
case (elem v xs, bool) of
46-
(True, True) -> pure True
47-
(True, False) -> fatalErrorNE ("notElemPred reduces to True" :| [show p])
48-
(False, True) -> fatalErrorNE ("elemPred reduces to False" :| [show p])
49-
(False, False) -> pure True
50-
Monitor {} -> pure True
51-
Subst x t p -> checkPred env $ substitutePred x t p
52-
Assert t -> runTerm env t
53-
GenHint {} -> pure True
54-
p@(Reifies t' t f) -> do
55-
val <- runTerm env t
56-
val' <- runTerm env t'
57-
explainNE (NE.fromList ["Reification:", " " ++ show p]) $ pure (f val == val')
58-
ForAll t (x :-> p) -> do
59-
set <- runTerm env t
60-
and
61-
<$> sequence
62-
[ checkPred env' p
63-
| v <- forAllToList set
64-
, let env' = Env.extend x v env
65-
]
66-
Case t bs -> do
67-
v <- runTerm env t
68-
runCaseOn v (mapList thing bs) (\x val ps -> checkPred (Env.extend x val env) ps)
69-
When bt p -> do
70-
b <- runTerm env bt
71-
if b then checkPred env p else pure True
72-
TruePred -> pure True
73-
FalsePred es -> explainNE es $ pure False
74-
DependsOn {} -> pure True
75-
And ps -> checkPreds env ps
76-
Let t (x :-> p) -> do
77-
val <- runTerm env t
78-
checkPred (Env.extend x val env) p
79-
Exists k (x :-> p) -> do
80-
a <- runGE $ k (errorGE . explain "checkPred: Exists" . runTerm env)
81-
checkPred (Env.extend x a env) p
82-
Explain es p -> explainNE es $ checkPred env p
83-
84-
checkPreds :: (MonadGenError m, Traversable t) => Env -> t Pred -> m Bool
85-
checkPreds env ps = and <$> mapM (checkPred env) ps
86-
8736
-- ==========================================================
8837

89-
-- | Like checkPred, But it takes [Pred] rather than a single Pred,
38+
-- | Like checkPredE, But it takes [Pred] rather than a single Pred,
9039
-- and it builds a much more involved explanation if it fails.
9140
-- Does the Pred evaluate to True under the given Env?
9241
-- If it doesn't, an involved explanation appears in the (Just message)
@@ -101,8 +50,9 @@ checkPredsE msgs env ps =
10150
[] -> Nothing
10251
(x : xs) -> Just (NE.nub (sconcat (x NE.:| xs)))
10352

104-
-- | An involved explanation for a single Pred
105-
-- The most important explanations come when an assertion fails.
53+
-- | Does the Pred evaluate to true under the given Env. An involved
54+
-- explanation for a single Pred in case of failure and `Nothing` otherwise.
55+
-- The most important explanations come when an assertion fails.
10656
checkPredE :: Env -> NE.NonEmpty String -> Pred -> Maybe (NE.NonEmpty String)
10757
checkPredE env msgs = \case
10858
p@(ElemPred bool t xs) ->

src/Constrained/Env.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import Prettyprinter
2727
import Prelude hiding (lookup)
2828

2929
-- | Typed environments for mapping @t`Var` a@ to @a@
30-
newtype Env = Env {unEnv :: Map EnvKey EnvValue}
30+
newtype Env = Env (Map EnvKey EnvValue)
3131
deriving newtype (Semigroup, Monoid)
3232
deriving stock (Show)
3333

src/Constrained/Graph.hs

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
-- | This module provides a dependency graph implementation.
66
module Constrained.Graph (
77
Graph,
8+
edges,
9+
opEdges,
810
opGraph,
11+
mkGraph,
912
nodes,
1013
deleteNode,
1114
subtractGraph,
@@ -22,20 +25,21 @@ module Constrained.Graph (
2225

2326
import Control.Monad
2427
import Data.Foldable
25-
import Data.List (sortOn)
28+
import Data.List (sortOn, nub)
2629
import Data.Map (Map)
2730
import Data.Map qualified as Map
2831
import Data.Maybe
2932
import Data.Set (Set)
3033
import Data.Set qualified as Set
3134
import Prettyprinter
35+
import Test.QuickCheck
3236

3337
-- | A graph with unlabeled edges for keeping track of dependencies
3438
data Graph node = Graph
3539
{ edges :: !(Map node (Set node))
3640
, opEdges :: !(Map node (Set node))
3741
}
38-
deriving (Show)
42+
deriving (Show, Eq)
3943

4044
instance Ord node => Semigroup (Graph node) where
4145
Graph e o <> Graph e' o' =
@@ -55,6 +59,37 @@ instance Pretty n => Pretty (Graph n) where
5559
| (n, ns) <- Map.toList (edges gr)
5660
]
5761

62+
-- | Construct a graph
63+
mkGraph :: Ord node => Map node (Set node) -> Graph node
64+
mkGraph e0 = Graph e $ Map.unionsWith (<>)
65+
[ Map.fromList $ (p, mempty) : [ (c, Set.singleton p)
66+
| c <- Set.toList cs
67+
]
68+
| (p, cs) <- Map.toList e
69+
]
70+
where e = Map.unionWith (<>) e0 (Map.fromList [ (c, mempty) | (_, cs) <- Map.toList e0
71+
, c <- Set.toList cs
72+
])
73+
74+
instance (Arbitrary node, Ord node) => Arbitrary (Graph node) where
75+
arbitrary =
76+
frequency [ (1, mkGraph <$> arbitrary)
77+
, (3, do order <- nub <$> arbitrary
78+
mkGraph <$> buildGraph order
79+
)
80+
]
81+
where buildGraph [] = return mempty
82+
buildGraph [n] = return (Map.singleton n mempty)
83+
buildGraph (n:ns) = do
84+
deps <- listOf (elements ns)
85+
Map.insert n (Set.fromList deps) <$> buildGraph ns
86+
shrink g =
87+
[ mkGraph e'
88+
| e <- shrink (edges g)
89+
-- If we don't do this it's very easy to introduce a shrink-loop
90+
, let e' = fmap (\ xs -> Set.filter (`Map.member` e) xs) e
91+
]
92+
5893
-- | Get all the nodes of a graph
5994
nodes :: Graph node -> Set node
6095
nodes (Graph e _) = Map.keysSet e
@@ -102,11 +137,12 @@ irreflexiveDependencyOn xs ys =
102137

103138
-- | Get all down-stream dependencies of a node
104139
transitiveDependencies :: Ord node => node -> Graph node -> Set node
105-
transitiveDependencies x (Graph e _) = go (Set.singleton x) x
140+
transitiveDependencies x (Graph e _) = go mempty (Set.toList $ fromMaybe mempty $ Map.lookup x e)
106141
where
107-
go seen y = ys <> foldMap (go $ Set.insert y seen) (Set.difference ys seen)
108-
where
109-
ys = fromMaybe mempty (Map.lookup y e)
142+
go deps [] = deps
143+
go deps (y:ys)
144+
| y `Set.member` deps = go deps ys
145+
| otherwise = go (Set.insert y deps) (ys ++ Set.toList (fromMaybe mempty $ Map.lookup y e))
110146

111147
-- | Take the transitive closure of the graph
112148
transitiveClosure :: Ord node => Graph node -> Graph node
@@ -133,10 +169,11 @@ topsort gr@(Graph e _) = go [] e
133169
else Left . concat . take 1 . sortOn length . filter (not . null) . map (findCycle gr) $ Map.keys e
134170

135171
-- | Simple DFS cycle finding
136-
-- TODO: tests for this, currently it can produce a stem with a cycle after it
137172
findCycle :: Ord node => Graph node -> node -> [node]
138-
findCycle (Graph e _) node = concat . take 1 $ go mempty node
173+
findCycle g@(Graph e _) node = concat . take 1 $ filter loopy $ go mempty node
139174
where
175+
loopy [] = False
176+
loopy c@(x:_) = dependsOn (last c) x g
140177
go seen n
141178
| n `Set.member` seen = [[]]
142179
| otherwise = do

src/Constrained/Syntax.hs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ module Constrained.Syntax (
5151
computeDependencies,
5252
solvableFrom,
5353
respecting,
54-
dependency,
5554
applyNameHints,
5655
envFromPred,
5756
isLit,
@@ -834,10 +833,6 @@ applyNameHints spec = spec
834833
-- | `Graph` specialized to dependencies for variables
835834
type DependGraph = Graph.Graph Name
836835

837-
-- | A variable depends on a thing witha buch of other variables
838-
dependency :: HasVariables t => Name -> t -> DependGraph
839-
dependency x (freeVarSet -> xs) = Graph.dependency x xs
840-
841836
-- | Everything to the left depends on everything from the right, except themselves
842837
irreflexiveDependencyOn ::
843838
forall t t'. (HasVariables t, HasVariables t') => t -> t' -> DependGraph

stack.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ snapshot: lts-22.44
22
packages:
33
- .
44
system-ghc: true
5+
extra-deps:
6+
- QuickCheck-2.15.0.1

stack.yaml.lock

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@
33
# For more information, please see the documentation at:
44
# https://docs.haskellstack.org/en/stable/topics/lock_files
55

6-
packages: []
6+
packages:
7+
- completed:
8+
hackage: QuickCheck-2.15.0.1@sha256:0cfd337bb9e6fbf09255bd24bb498a156f1d9bcd465396ac8657b25034b9ee31,9046
9+
pantry-tree:
10+
sha256: 27dddf0b64cc7d7e154326059c89b70da61530c25623af7ef576dd9db0c53821
11+
size: 2437
12+
original:
13+
hackage: QuickCheck-2.15.0.1
714
snapshots:
815
- completed:
916
sha256: 238fa745b64f91184f9aa518fe04bdde6552533d169b0da5256670df83a0f1a9

0 commit comments

Comments
 (0)