File: TestCaseUtils.hs

package info (click to toggle)
haskell-cabal-install 3.10.3.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, trixie
  • size: 3,400 kB
  • sloc: haskell: 52,202; sh: 80; makefile: 9
file content (290 lines) | stat: -rw-r--r-- 11,438 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
{-# LANGUAGE RecordWildCards #-}
-- | Utilities for creating HUnit test cases with the solver DSL.
module UnitTests.Distribution.Solver.Modular.DSL.TestCaseUtils (
    SolverTest
  , SolverResult(..)
  , maxBackjumps
  , disableFineGrainedConflicts
  , minimizeConflictSet
  , independentGoals
  , preferOldest
  , allowBootLibInstalls
  , onlyConstrained
  , disableBackjumping
  , disableSolveExecutables
  , goalOrder
  , constraints
  , preferences
  , setVerbose
  , enableAllTests
  , solverSuccess
  , solverFailure
  , anySolverFailure
  , mkTest
  , mkTestExts
  , mkTestLangs
  , mkTestPCDepends
  , mkTestExtLangPC
  , runTest
  ) where

import Prelude ()
import Distribution.Solver.Compat.Prelude

import Data.List (elemIndex)

-- test-framework
import Test.Tasty as TF
import Test.Tasty.HUnit (testCase, assertEqual, assertBool)

-- Cabal
import qualified Distribution.PackageDescription as C
import Language.Haskell.Extension (Extension(..), Language(..))
import Distribution.Verbosity

-- cabal-install
import qualified Distribution.Solver.Types.PackagePath as P
import Distribution.Solver.Types.PkgConfigDb (PkgConfigDb (..), pkgConfigDbFromList)
import Distribution.Solver.Types.Settings
import Distribution.Solver.Types.Variable
import Distribution.Client.Dependency (foldProgress)
import UnitTests.Distribution.Solver.Modular.DSL
import UnitTests.Options

maxBackjumps :: Maybe Int -> SolverTest -> SolverTest
maxBackjumps mbj test = test { testMaxBackjumps = mbj }

disableFineGrainedConflicts :: SolverTest -> SolverTest
disableFineGrainedConflicts test =
    test { testFineGrainedConflicts = FineGrainedConflicts False }

minimizeConflictSet :: SolverTest -> SolverTest
minimizeConflictSet test =
    test { testMinimizeConflictSet = MinimizeConflictSet True }

-- | Combinator to turn on --independent-goals behavior, i.e. solve
-- for the goals as if we were solving for each goal independently.
independentGoals :: SolverTest -> SolverTest
independentGoals test = test { testIndepGoals = IndependentGoals True }

-- | Combinator to turn on --prefer-oldest
preferOldest :: SolverTest -> SolverTest
preferOldest test = test { testPreferOldest = PreferOldest True }

allowBootLibInstalls :: SolverTest -> SolverTest
allowBootLibInstalls test =
    test { testAllowBootLibInstalls = AllowBootLibInstalls True }

onlyConstrained :: SolverTest -> SolverTest
onlyConstrained test =
    test { testOnlyConstrained = OnlyConstrainedAll }

disableBackjumping :: SolverTest -> SolverTest
disableBackjumping test =
    test { testEnableBackjumping = EnableBackjumping False }

disableSolveExecutables :: SolverTest -> SolverTest
disableSolveExecutables test =
    test { testSolveExecutables = SolveExecutables False }

goalOrder :: [ExampleVar] -> SolverTest -> SolverTest
goalOrder order test = test { testGoalOrder = Just order }

constraints :: [ExConstraint] -> SolverTest -> SolverTest
constraints cs test = test { testConstraints = cs }

preferences :: [ExPreference] -> SolverTest -> SolverTest
preferences prefs test = test { testSoftConstraints = prefs }

-- | Increase the solver's verbosity. This is necessary for test cases that
-- check the contents of the verbose log.
setVerbose :: SolverTest -> SolverTest
setVerbose test = test { testVerbosity = verbose }

enableAllTests :: SolverTest -> SolverTest
enableAllTests test = test { testEnableAllTests = EnableAllTests True }

{-------------------------------------------------------------------------------
  Solver tests
-------------------------------------------------------------------------------}

data SolverTest = SolverTest {
    testLabel                :: String
  , testTargets              :: [String]
  , testResult               :: SolverResult
  , testMaxBackjumps         :: Maybe Int
  , testFineGrainedConflicts :: FineGrainedConflicts
  , testMinimizeConflictSet  :: MinimizeConflictSet
  , testIndepGoals           :: IndependentGoals
  , testPreferOldest         :: PreferOldest
  , testAllowBootLibInstalls :: AllowBootLibInstalls
  , testOnlyConstrained      :: OnlyConstrained
  , testEnableBackjumping    :: EnableBackjumping
  , testSolveExecutables     :: SolveExecutables
  , testGoalOrder            :: Maybe [ExampleVar]
  , testConstraints          :: [ExConstraint]
  , testSoftConstraints      :: [ExPreference]
  , testVerbosity            :: Verbosity
  , testDb                   :: ExampleDb
  , testSupportedExts        :: Maybe [Extension]
  , testSupportedLangs       :: Maybe [Language]
  , testPkgConfigDb          :: PkgConfigDb
  , testEnableAllTests       :: EnableAllTests
  }

-- | Expected result of a solver test.
data SolverResult = SolverResult {
    -- | The solver's log should satisfy this predicate. Note that we also print
    -- the log, so evaluating a large log here can cause a space leak.
    resultLogPredicate            :: [String] -> Bool,

    -- | Fails with an error message satisfying the predicate, or succeeds with
    -- the given plan.
    resultErrorMsgPredicateOrPlan :: Either (String -> Bool) [(String, Int)]
  }

solverSuccess :: [(String, Int)] -> SolverResult
solverSuccess = SolverResult (const True) . Right

solverFailure :: (String -> Bool) -> SolverResult
solverFailure = SolverResult (const True) . Left

-- | Can be used for test cases where we just want to verify that
-- they fail, but do not care about the error message.
anySolverFailure :: SolverResult
anySolverFailure = solverFailure (const True)

-- | Makes a solver test case, consisting of the following components:
--
--      1. An 'ExampleDb', representing the package database (both
--         installed and remote) we are doing dependency solving over,
--      2. A 'String' name for the test,
--      3. A list '[String]' of package names to solve for
--      4. The expected result, either 'Nothing' if there is no
--         satisfying solution, or a list '[(String, Int)]' of
--         packages to install, at which versions.
--
-- See 'UnitTests.Distribution.Solver.Modular.DSL' for how
-- to construct an 'ExampleDb', as well as definitions of 'db1' etc.
-- in this file.
mkTest :: ExampleDb
       -> String
       -> [String]
       -> SolverResult
       -> SolverTest
mkTest = mkTestExtLangPC Nothing Nothing (Just [])

mkTestExts :: [Extension]
           -> ExampleDb
           -> String
           -> [String]
           -> SolverResult
           -> SolverTest
mkTestExts exts = mkTestExtLangPC (Just exts) Nothing (Just [])

mkTestLangs :: [Language]
            -> ExampleDb
            -> String
            -> [String]
            -> SolverResult
            -> SolverTest
mkTestLangs langs = mkTestExtLangPC Nothing (Just langs) (Just [])

mkTestPCDepends :: Maybe [(String, String)]
                -> ExampleDb
                -> String
                -> [String]
                -> SolverResult
                -> SolverTest
mkTestPCDepends mPkgConfigDb = mkTestExtLangPC Nothing Nothing mPkgConfigDb

mkTestExtLangPC :: Maybe [Extension]
                -> Maybe [Language]
                -> Maybe [(String, String)]
                -> ExampleDb
                -> String
                -> [String]
                -> SolverResult
                -> SolverTest
mkTestExtLangPC exts langs mPkgConfigDb db label targets result = SolverTest {
    testLabel                = label
  , testTargets              = targets
  , testResult               = result
  , testMaxBackjumps         = Nothing
  , testFineGrainedConflicts = FineGrainedConflicts True
  , testMinimizeConflictSet  = MinimizeConflictSet False
  , testIndepGoals           = IndependentGoals False
  , testPreferOldest         = PreferOldest False
  , testAllowBootLibInstalls = AllowBootLibInstalls False
  , testOnlyConstrained      = OnlyConstrainedNone
  , testEnableBackjumping    = EnableBackjumping True
  , testSolveExecutables     = SolveExecutables True
  , testGoalOrder            = Nothing
  , testConstraints          = []
  , testSoftConstraints      = []
  , testVerbosity            = normal
  , testDb                   = db
  , testSupportedExts        = exts
  , testSupportedLangs       = langs
  , testPkgConfigDb          = maybe NoPkgConfigDb pkgConfigDbFromList mPkgConfigDb
  , testEnableAllTests       = EnableAllTests False
  }

runTest :: SolverTest -> TF.TestTree
runTest SolverTest{..} = askOption $ \(OptionShowSolverLog showSolverLog) ->
    testCase testLabel $ do
      let progress = exResolve testDb testSupportedExts
                     testSupportedLangs testPkgConfigDb testTargets
                     testMaxBackjumps (CountConflicts True)
                     testFineGrainedConflicts testMinimizeConflictSet
                     testIndepGoals testPreferOldest (ReorderGoals False) testAllowBootLibInstalls
                     testOnlyConstrained testEnableBackjumping testSolveExecutables
                     (sortGoals <$> testGoalOrder) testConstraints
                     testSoftConstraints testVerbosity testEnableAllTests
          printMsg msg = when showSolverLog $ putStrLn msg
          msgs = foldProgress (:) (const []) (const []) progress
      assertBool ("Unexpected solver log:\n" ++ unlines msgs) $
                 resultLogPredicate testResult $ concatMap lines msgs
      result <- foldProgress ((>>) . printMsg) (return . Left) (return . Right) progress
      case result of
        Left  err  -> assertBool ("Unexpected error:\n" ++ err)
                                 (checkErrorMsg testResult err)
        Right plan -> assertEqual "" (toMaybe testResult) (Just (extractInstallPlan plan))
  where
    toMaybe :: SolverResult -> Maybe [(String, Int)]
    toMaybe = either (const Nothing) Just . resultErrorMsgPredicateOrPlan

    checkErrorMsg :: SolverResult -> String -> Bool
    checkErrorMsg result msg =
        case resultErrorMsgPredicateOrPlan result of
          Left f  -> f msg
          Right _ -> False

    sortGoals :: [ExampleVar]
              -> Variable P.QPN -> Variable P.QPN -> Ordering
    sortGoals = orderFromList . map toVariable

    -- Sort elements in the list ahead of elements not in the list. Otherwise,
    -- follow the order in the list.
    orderFromList :: Eq a => [a] -> a -> a -> Ordering
    orderFromList xs =
        comparing $ \x -> let i = elemIndex x xs in (isNothing i, i)

    toVariable :: ExampleVar -> Variable P.QPN
    toVariable (P q pn)        = PackageVar (toQPN q pn)
    toVariable (F q pn fn)     = FlagVar    (toQPN q pn) (C.mkFlagName fn)
    toVariable (S q pn stanza) = StanzaVar  (toQPN q pn) stanza

    toQPN :: ExampleQualifier -> ExamplePkgName -> P.QPN
    toQPN q pn = P.Q pp (C.mkPackageName pn)
      where
        pp = case q of
               QualNone           -> P.PackagePath P.DefaultNamespace P.QualToplevel
               QualIndep p        -> P.PackagePath (P.Independent $ C.mkPackageName p)
                                                   P.QualToplevel
               QualSetup s        -> P.PackagePath P.DefaultNamespace
                                                   (P.QualSetup (C.mkPackageName s))
               QualIndepSetup p s -> P.PackagePath (P.Independent $ C.mkPackageName p)
                                                   (P.QualSetup (C.mkPackageName s))
               QualExe p1 p2      -> P.PackagePath P.DefaultNamespace
                                                   (P.QualExe (C.mkPackageName p1) (C.mkPackageName p2))