2 {-# LANGUAGE DeriveGeneric #-}
3 {-# LANGUAGE StandaloneDeriving #-}
4 {-# OPTIONS_GHC -fno-warn-orphans #-}
6 module UnitTests
.Distribution
.Solver
.Modular
.QuickCheck
(tests
) where
8 import Distribution
.Client
.Compat
.Prelude
11 import Control
.Arrow
((&&&))
12 import Data
.Either (lefts
)
13 import Data
.Hashable
(Hashable
(..))
14 import Data
.List
(groupBy, isInfixOf
)
16 import Text
.Show.Pretty
(parseValue
, valToStr
)
18 import Test
.QuickCheck
(Arbitrary
(..), Blind
(..), Gen
, Positive
(..), counterexample
, elements
, frequency
, listOf
, oneof
, shrinkList
, shrinkNothing
, shuffle
, sublistOf
, vectorOf
, (===), (==>))
19 import Test
.QuickCheck
.Instances
.Cabal
()
20 import Test
.Tasty
(TestTree
)
22 import Distribution
.Types
.Flag
(FlagName
)
23 import Distribution
.Utils
.ShortText
(ShortText
)
25 import Distribution
.Client
.Setup
(defaultMaxBackjumps
)
27 import Distribution
.Types
.LibraryVisibility
28 import Distribution
.Types
.PackageName
29 import Distribution
.Types
.UnqualComponentName
31 import Distribution
.Solver
.Types
.ComponentDeps
36 import qualified Distribution
.Solver
.Types
.ComponentDeps
as CD
37 import Distribution
.Solver
.Types
.OptionalStanza
38 import Distribution
.Solver
.Types
.PackageConstraint
39 import qualified Distribution
.Solver
.Types
.PackagePath
as P
40 import Distribution
.Solver
.Types
.PkgConfigDb
43 import Distribution
.Solver
.Types
.Settings
44 import Distribution
.Solver
.Types
.Variable
45 import Distribution
.Verbosity
46 import Distribution
.Version
48 import UnitTests
.Distribution
.Solver
.Modular
.DSL
49 import UnitTests
.Distribution
.Solver
.Modular
.QuickCheck
.Utils
50 ( testPropertyWithSeed
55 [ testPropertyWithSeed
"solver does not throw exceptions" $
56 \test goalOrder reorderGoals indepGoals prefOldest
->
59 (EnableBackjumping
True)
60 (FineGrainedConflicts
True)
65 (getBlind
<$> goalOrder
)
67 in resultPlan r `
seq`
()
68 , -- This test checks that certain solver parameters do not affect the
69 -- existence of a solution. It runs the solver twice, and only sets those
70 -- parameters on the second run. The test also applies parameters that
71 -- can affect the existence of a solution to both runs.
72 testPropertyWithSeed
"target and goal order do not affect solvability" $
73 \test targetOrder mGoalOrder1 mGoalOrder2 indepGoals
->
74 let r1
= solve
' mGoalOrder1 test
75 r2
= solve
' mGoalOrder2 test
{testTargets
= targets2
}
78 (EnableBackjumping
True)
79 (FineGrainedConflicts
True)
84 (getBlind
<$> goalOrder
)
85 targets
= testTargets test
86 targets2
= case targetOrder
of
88 ReverseOrder
-> reverse targets
89 in counterexample
(showResults r1 r2
) $
90 noneReachedBackjumpLimit
[r1
, r2
] ==>
91 isRight
(resultPlan r1
) === isRight
(resultPlan r2
)
92 , testPropertyWithSeed
93 "solvable without --independent-goals => solvable with --independent-goals"
94 $ \test reorderGoals
->
95 let r1
= solve
' (IndependentGoals
False) test
96 r2
= solve
' (IndependentGoals
True) test
99 (EnableBackjumping
True)
100 (FineGrainedConflicts
True)
102 (CountConflicts
True)
106 in counterexample
(showResults r1 r2
) $
107 noneReachedBackjumpLimit
[r1
, r2
] ==>
108 isRight
(resultPlan r1
) `implies` isRight
(resultPlan r2
)
109 , testPropertyWithSeed
"backjumping does not affect solvability" $
110 \test reorderGoals indepGoals
->
111 let r1
= solve
' (EnableBackjumping
True) test
112 r2
= solve
' (EnableBackjumping
False) test
116 (FineGrainedConflicts
False)
118 (CountConflicts
True)
122 in counterexample
(showResults r1 r2
) $
123 noneReachedBackjumpLimit
[r1
, r2
] ==>
124 isRight
(resultPlan r1
) === isRight
(resultPlan r2
)
125 , testPropertyWithSeed
"fine-grained conflicts does not affect solvability" $
126 \test reorderGoals indepGoals
->
127 let r1
= solve
' (FineGrainedConflicts
True) test
128 r2
= solve
' (FineGrainedConflicts
False) test
129 solve
' fineGrainedConflicts
=
131 (EnableBackjumping
True)
134 (CountConflicts
True)
138 in counterexample
(showResults r1 r2
) $
139 noneReachedBackjumpLimit
[r1
, r2
] ==>
140 isRight
(resultPlan r1
) === isRight
(resultPlan r2
)
141 , testPropertyWithSeed
"prefer oldest does not affect solvability" $
142 \test reorderGoals indepGoals
->
143 let r1
= solve
' (PreferOldest
True) test
144 r2
= solve
' (PreferOldest
False) test
147 (EnableBackjumping
True)
148 (FineGrainedConflicts
True)
150 (CountConflicts
True)
154 in counterexample
(showResults r1 r2
) $
155 noneReachedBackjumpLimit
[r1
, r2
] ==>
156 isRight
(resultPlan r1
) === isRight
(resultPlan r2
)
157 , -- The next two tests use --no-count-conflicts, because the goal order used
158 -- with --count-conflicts depends on the total set of conflicts seen by the
159 -- solver. The solver explores more of the tree and encounters more
160 -- conflicts when it doesn't backjump. The different goal orders can lead to
161 -- different solutions and cause the test to fail.
162 -- TODO: Find a faster way to randomly sort goals, and then use a random
163 -- goal order in these tests.
166 "backjumping does not affect the result (with static goal order)"
167 $ \test reorderGoals indepGoals
->
168 let r1
= solve
' (EnableBackjumping
True) test
169 r2
= solve
' (EnableBackjumping
False) test
173 (FineGrainedConflicts
False)
175 (CountConflicts
False)
179 in counterexample
(showResults r1 r2
) $
180 noneReachedBackjumpLimit
[r1
, r2
] ==>
181 resultPlan r1
=== resultPlan r2
182 , testPropertyWithSeed
183 "fine-grained conflicts does not affect the result (with static goal order)"
184 $ \test reorderGoals indepGoals
->
185 let r1
= solve
' (FineGrainedConflicts
True) test
186 r2
= solve
' (FineGrainedConflicts
False) test
187 solve
' fineGrainedConflicts
=
189 (EnableBackjumping
True)
192 (CountConflicts
False)
196 in counterexample
(showResults r1 r2
) $
197 noneReachedBackjumpLimit
[r1
, r2
] ==>
198 resultPlan r1
=== resultPlan r2
201 noneReachedBackjumpLimit
:: [Result
] -> Bool
202 noneReachedBackjumpLimit
=
203 not . any (\r -> resultPlan r
== Left BackjumpLimitReached
)
205 showResults
:: Result
-> Result
-> String
206 showResults r1 r2
= showResult
1 r1
++ showResult
2 r2
208 showResult
:: Int -> Result
-> String
209 showResult n result
=
211 ["", "Run " ++ show n
++ ":"]
213 ++ ["result: " ++ show (resultPlan result
)]
215 implies
:: Bool -> Bool -> Bool
216 implies x y
= not x || y
218 isRight
:: Either a b
-> Bool
219 isRight
(Right _
) = True
222 newtype VarOrdering
= VarOrdering
223 { unVarOrdering
:: Variable P
.QPN
-> Variable P
.QPN
-> Ordering
228 -> FineGrainedConflicts
236 solve enableBj fineGrainedConflicts reorder countConflicts indep prefOldest goalOrder test
=
240 (unTestDb
(testDb test
))
243 (Just
$ pkgConfigDbFromList
[])
244 (map unPN
(testTargets test
))
245 -- The backjump limit prevents individual tests from using
246 -- too much time and memory.
247 (Just defaultMaxBackjumps
)
250 (MinimizeConflictSet
False)
254 (AllowBootLibInstalls
False)
257 (SolveExecutables
True)
258 (unVarOrdering
<$> goalOrder
)
259 (testConstraints test
)
260 (testPreferences test
)
262 (EnableAllTests
False)
264 failure
:: String -> Failure
266 |
"Backjump limit reached" `isInfixOf` msg
= BackjumpLimitReached
267 |
otherwise = OtherFailure
271 -- Force the result so that we check for internal errors when we check
272 -- for success or failure. See D.C.Dependency.validateSolverResult.
273 force
$ either (Left
. failure
) (Right
. extractInstallPlan
) result
276 -- | How to modify the order of the input targets.
277 data TargetOrder
= SameOrder | ReverseOrder
280 instance Arbitrary TargetOrder
where
281 arbitrary
= elements
[SameOrder
, ReverseOrder
]
283 shrink SameOrder
= []
284 shrink ReverseOrder
= [SameOrder
]
287 { resultLog
:: [String]
288 , resultPlan
:: Either Failure
[(ExamplePkgName
, ExamplePkgVersion
)]
291 data Failure
= BackjumpLimitReached | OtherFailure
292 deriving (Eq
, Generic
, Show)
294 instance NFData Failure
297 newtype PN
= PN
{unPN
:: String}
298 deriving (Eq
, Ord
, Show)
300 instance Arbitrary PN
where
301 arbitrary
= PN
<$> elements
("base" : [[pn
] | pn
<- ['A
' .. 'G
']])
303 -- | Package version.
304 newtype PV
= PV
{unPV
:: Int}
305 deriving (Eq
, Ord
, Show)
307 instance Arbitrary PV
where
308 arbitrary
= PV
<$> elements
[1 .. 10]
310 type TestPackage
= Either ExampleInstalled ExampleAvailable
312 getName
:: TestPackage
-> PN
313 getName
= PN
. either exInstName exAvName
315 getVersion
:: TestPackage
-> PV
316 getVersion
= PV
. either exInstVersion exAvVersion
318 data SolverTest
= SolverTest
320 , testTargets
:: [PN
]
321 , testConstraints
:: [ExConstraint
]
322 , testPreferences
:: [ExPreference
]
325 -- | Pretty-print the test when quickcheck calls 'show'.
326 instance Show SolverTest
where
329 "SolverTest {testDb = "
330 ++ show (testDb test
)
331 ++ ", testTargets = "
332 ++ show (testTargets test
)
333 ++ ", testConstraints = "
334 ++ show (testConstraints test
)
335 ++ ", testPreferences = "
336 ++ show (testPreferences test
)
338 in maybe str valToStr
$ parseValue str
340 instance Arbitrary SolverTest
where
343 let pkgVersions
= nub $ map (getName
&&& getVersion
) (unTestDb db
)
344 pkgs
= nub $ map fst pkgVersions
345 Positive n
<- arbitrary
346 targets
<- randomSubset n pkgs
347 constraints
<- case pkgVersions
of
349 _
-> boundedListOf
1 $ arbitraryConstraint pkgVersions
350 prefs
<- case pkgVersions
of
352 _
-> boundedListOf
3 $ arbitraryPreference pkgVersions
353 return (SolverTest db targets constraints prefs
)
356 [test
{testDb
= db
} | db
<- shrink
(testDb test
)]
357 ++ [test
{testTargets
= targets
} | targets
<- shrink
(testTargets test
)]
358 ++ [test
{testConstraints
= cs
} | cs
<- shrink
(testConstraints test
)]
359 ++ [test
{testPreferences
= prefs
} | prefs
<- shrink
(testPreferences test
)]
361 -- | Collection of source and installed packages.
362 newtype TestDb
= TestDb
{unTestDb
:: ExampleDb
}
365 instance Arbitrary TestDb
where
367 -- Avoid cyclic dependencies by grouping packages by name and only
368 -- allowing each package to depend on packages in the groups before it.
370 shuffle
. groupBy ((==) `on`
fst) . nub . sort
371 =<< boundedListOf
10 arbitrary
372 db
<- foldM nextPkgs
(TestDb
[]) groupedPkgs
373 TestDb
<$> shuffle
(unTestDb db
)
375 nextPkgs
:: TestDb
-> [(PN
, PV
)] -> Gen TestDb
376 nextPkgs db pkgs
= TestDb
. (++ unTestDb db
) <$> traverse
(nextPkg db
) pkgs
378 nextPkg
:: TestDb
-> (PN
, PV
) -> Gen TestPackage
379 nextPkg db
(pn
, v
) = do
380 installed
<- arbitrary
382 then Left
<$> arbitraryExInst pn v
(lefts
$ unTestDb db
)
383 else Right
<$> arbitraryExAv pn v db
385 shrink
(TestDb pkgs
) = map TestDb
$ shrink pkgs
387 arbitraryExAv
:: PN
-> PV
-> TestDb
-> Gen ExampleAvailable
388 arbitraryExAv pn v db
=
389 (\cds
-> ExAv
(unPN pn
) (unPV v
) cds
[]) <$> arbitraryComponentDeps pn db
391 arbitraryExInst
:: PN
-> PV
-> [ExampleInstalled
] -> Gen ExampleInstalled
392 arbitraryExInst pn v pkgs
= do
393 pkgHash
<- vectorOf
10 $ elements
$ ['a
' .. 'z
'] ++ ['A
' .. 'Z
'] ++ ['0' .. '9']
394 numDeps
<- min 3 <$> arbitrary
395 deps
<- randomSubset numDeps pkgs
396 return $ ExInst
(unPN pn
) (unPV v
) pkgHash
(map exInstHash deps
)
398 arbitraryComponentDeps
:: PN
-> TestDb
-> Gen
(ComponentDeps Dependencies
)
399 arbitraryComponentDeps _
(TestDb
[]) = return $ CD
.fromLibraryDeps
(dependencies
[])
400 arbitraryComponentDeps pn db
= do
401 -- dedupComponentNames removes components with duplicate names, for example,
402 -- 'ComponentExe x' and 'ComponentTest x', and then CD.fromList combines
403 -- duplicate unnamed components.
405 CD
.fromList
. dedupComponentNames
. filter (isValid
. fst)
406 <$> boundedListOf
5 (arbitraryComponentDep db
)
408 if isCompleteComponentDeps cds
410 else -- Add a library if the ComponentDeps isn't complete.
411 CD
.fromLibraryDeps
(dependencies
[]) <> cds
413 isValid
:: Component
-> Bool
414 isValid
(ComponentSubLib name
) = name
/= mkUnqualComponentName
(unPN pn
)
417 dedupComponentNames
=
418 nubBy ((\x y
-> isJust x
&& isJust y
&& x
== y
) `on` componentName
. fst)
420 componentName
:: Component
-> Maybe UnqualComponentName
421 componentName ComponentLib
= Nothing
422 componentName ComponentSetup
= Nothing
423 componentName
(ComponentSubLib n
) = Just n
424 componentName
(ComponentFLib n
) = Just n
425 componentName
(ComponentExe n
) = Just n
426 componentName
(ComponentTest n
) = Just n
427 componentName
(ComponentBench n
) = Just n
429 -- | Returns true if the ComponentDeps forms a complete package, i.e., it
430 -- contains a library, exe, test, or benchmark.
431 isCompleteComponentDeps
:: ComponentDeps a
-> Bool
432 isCompleteComponentDeps
= any (completesPkg
. fst) . CD
.toList
434 completesPkg ComponentLib
= True
435 completesPkg
(ComponentExe _
) = True
436 completesPkg
(ComponentTest _
) = True
437 completesPkg
(ComponentBench _
) = True
438 completesPkg
(ComponentSubLib _
) = False
439 completesPkg
(ComponentFLib _
) = False
440 completesPkg ComponentSetup
= False
442 arbitraryComponentDep
:: TestDb
-> Gen
(ComponentDep Dependencies
)
443 arbitraryComponentDep db
= do
446 ComponentSetup
-> smallListOf
(arbitraryExDep db SetupDep
)
447 _
-> boundedListOf
5 (arbitraryExDep db NonSetupDep
)
451 { depsExampleDependencies
= deps
452 , -- TODO: Test different values for visibility and buildability.
453 depsVisibility
= LibraryVisibilityPublic
454 , depsIsBuildable
= True
458 -- | Location of an 'ExampleDependency'. It determines which values are valid.
459 data ExDepLocation
= SetupDep | NonSetupDep
461 arbitraryExDep
:: TestDb
-> ExDepLocation
-> Gen ExampleDependency
462 arbitraryExDep db
@(TestDb pkgs
) level
=
465 <$> arbitraryFlagName
469 -- Package checks require dependencies on "base" to have bounds.
470 let notBase
= filter ((/= PN
"base") . getName
) pkgs
471 in [ExAny
. unPN
<$> elements
(map getName notBase
) |
not (null notBase
)]
474 let fixed pkg
= ExFix
(unPN
$ getName pkg
) (unPV
$ getVersion pkg
)
475 in fixed
<$> elements pkgs
476 , -- random version of an existing package
477 ExFix
. unPN
. getName
<$> elements pkgs
<*> (unPV
<$> arbitrary
)
481 NonSetupDep
-> flag
: other
484 arbitraryDeps
:: TestDb
-> Gen Dependencies
487 [ (1, return unbuildableDependencies
)
488 , (20, dependencies
<$> smallListOf
(arbitraryExDep db NonSetupDep
))
491 arbitraryFlagName
:: Gen
String
492 arbitraryFlagName
= (: []) <$> elements
['A
' .. 'E
']
494 arbitraryConstraint
:: [(PN
, PV
)] -> Gen ExConstraint
495 arbitraryConstraint pkgs
= do
496 (PN pn
, v
) <- elements pkgs
497 let anyQualifier
= ScopeAnyQualifier
(mkPackageName pn
)
499 [ ExVersionConstraint anyQualifier
<$> arbitraryVersionRange v
500 , ExStanzaConstraint anyQualifier
<$> sublistOf
[TestStanzas
, BenchStanzas
]
503 arbitraryPreference
:: [(PN
, PV
)] -> Gen ExPreference
504 arbitraryPreference pkgs
= do
505 (PN pn
, v
) <- elements pkgs
507 [ ExStanzaPref pn
<$> sublistOf
[TestStanzas
, BenchStanzas
]
508 , ExPkgPref pn
<$> arbitraryVersionRange v
511 arbitraryVersionRange
:: PV
-> Gen VersionRange
512 arbitraryVersionRange
(PV v
) =
513 let version
= mkSimpleVersion v
515 [ thisVersion version
516 , notThisVersion version
517 , earlierVersion version
518 , orLaterVersion version
522 instance Arbitrary ReorderGoals
where
523 arbitrary
= ReorderGoals
<$> arbitrary
525 shrink
(ReorderGoals reorder
) = [ReorderGoals
False | reorder
]
527 instance Arbitrary IndependentGoals
where
528 arbitrary
= IndependentGoals
<$> arbitrary
530 shrink
(IndependentGoals indep
) = [IndependentGoals
False | indep
]
532 instance Arbitrary PreferOldest
where
533 arbitrary
= PreferOldest
<$> arbitrary
535 shrink
(PreferOldest prefOldest
) = [PreferOldest
False | prefOldest
]
537 instance Arbitrary Component
where
540 [ return ComponentLib
541 , ComponentSubLib
<$> arbitraryUQN
542 , ComponentExe
<$> arbitraryUQN
543 , ComponentFLib
<$> arbitraryUQN
544 , ComponentTest
<$> arbitraryUQN
545 , ComponentBench
<$> arbitraryUQN
546 , return ComponentSetup
549 shrink ComponentLib
= []
550 shrink _
= [ComponentLib
]
552 -- The "component-" prefix prevents component names and build-depends
553 -- dependency names from overlapping.
554 -- TODO: Remove the prefix once the QuickCheck tests support dependencies on
555 -- internal libraries.
556 arbitraryUQN
:: Gen UnqualComponentName
558 mkUnqualComponentName
<$> (\c
-> "component-" ++ [c
]) <$> elements
"ABC"
560 instance Arbitrary ExampleInstalled
where
561 arbitrary
= error "arbitrary not implemented: ExampleInstalled"
564 [ ei
{exInstBuildAgainst
= deps
}
565 | deps
<- shrinkList shrinkNothing
(exInstBuildAgainst ei
)
568 instance Arbitrary ExampleAvailable
where
569 arbitrary
= error "arbitrary not implemented: ExampleAvailable"
571 shrink ea
= [ea
{exAvDeps
= deps
} | deps
<- shrink
(exAvDeps ea
)]
573 instance (Arbitrary a
, Monoid a
) => Arbitrary
(ComponentDeps a
) where
574 arbitrary
= error "arbitrary not implemented: ComponentDeps"
576 shrink
= filter isCompleteComponentDeps
. map CD
.fromList
. shrink
. CD
.toList
578 instance Arbitrary ExampleDependency
where
579 arbitrary
= error "arbitrary not implemented: ExampleDependency"
581 shrink
(ExAny _
) = []
582 shrink
(ExFix
"base" _
) = [] -- preserve bounds on base
583 shrink
(ExFix pn _
) = [ExAny pn
]
584 shrink
(ExFlagged flag th el
) =
585 depsExampleDependencies th
586 ++ depsExampleDependencies el
587 ++ [ExFlagged flag th
' el | th
' <- shrink th
]
588 ++ [ExFlagged flag th el
' | el
' <- shrink el
]
589 shrink dep
= error $ "Dependency not handled: " ++ show dep
591 instance Arbitrary Dependencies
where
592 arbitrary
= error "arbitrary not implemented: Dependencies"
595 [deps
{depsVisibility
= v
} | v
<- shrink
$ depsVisibility deps
]
596 ++ [deps
{depsIsBuildable
= b
} | b
<- shrink
$ depsIsBuildable deps
]
597 ++ [deps
{depsExampleDependencies
= ds
} | ds
<- shrink
$ depsExampleDependencies deps
]
599 instance Arbitrary ExConstraint
where
600 arbitrary
= error "arbitrary not implemented: ExConstraint"
602 shrink
(ExStanzaConstraint scope stanzas
) =
603 [ExStanzaConstraint scope stanzas
' | stanzas
' <- shrink stanzas
]
604 shrink
(ExVersionConstraint scope vr
) =
605 [ExVersionConstraint scope vr
' | vr
' <- shrink vr
]
608 instance Arbitrary ExPreference
where
609 arbitrary
= error "arbitrary not implemented: ExPreference"
611 shrink
(ExStanzaPref pn stanzas
) =
612 [ExStanzaPref pn stanzas
' | stanzas
' <- shrink stanzas
]
613 shrink
(ExPkgPref pn vr
) = [ExPkgPref pn vr
' | vr
' <- shrink vr
]
615 instance Arbitrary OptionalStanza
where
616 arbitrary
= error "arbitrary not implemented: OptionalStanza"
618 shrink BenchStanzas
= [TestStanzas
]
619 shrink TestStanzas
= []
621 -- Randomly sorts solver variables using 'hash'.
622 -- TODO: Sorting goals with this function is very slow.
623 instance Arbitrary VarOrdering
where
625 f
<- arbitrary
:: Gen
(Int -> Int)
626 return $ VarOrdering
(comparing
(f
. hash
))
628 instance Hashable pn
=> Hashable
(Variable pn
)
629 instance Hashable a
=> Hashable
(P
.Qualified a
)
630 instance Hashable P
.PackagePath
631 instance Hashable P
.Qualifier
632 instance Hashable P
.Namespace
633 instance Hashable OptionalStanza
634 instance Hashable FlagName
635 instance Hashable PackageName
636 instance Hashable ShortText
638 deriving instance Generic
(Variable pn
)
639 deriving instance Generic
(P
.Qualified a
)
640 deriving instance Generic P
.PackagePath
641 deriving instance Generic P
.Namespace
642 deriving instance Generic P
.Qualifier
644 randomSubset
:: Int -> [a
] -> Gen
[a
]
645 randomSubset n xs
= take n
<$> shuffle xs
647 boundedListOf
:: Int -> Gen a
-> Gen
[a
]
648 boundedListOf n gen
= take n
<$> listOf gen
650 -- | Generates lists with average length less than 1.
651 smallListOf
:: Gen a
-> Gen
[a
]
654 [ (fr
, vectorOf n gen
)
655 |
(fr
, n
) <- [(3, 0), (5, 1), (2, 2)]