forked from haskell/cabal
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPackageTests.hs
327 lines (295 loc) · 14.7 KB
/
PackageTests.hs
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
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE PatternGuards #-}
-- This is the runner for the package-tests suite. The actual
-- tests are in in PackageTests.Tests
module Main where
import PackageTests.Options
import PackageTests.PackageTester
import PackageTests.Tests
import Distribution.Simple.Configure
( ConfigStateFileError(..), findDistPrefOrDefault, getConfigStateFile
, interpretPackageDbFlags, configCompilerEx )
import Distribution.Simple.Compiler (PackageDB(..), PackageDBStack
,CompilerFlavor(GHC))
import Distribution.Simple.LocalBuildInfo (LocalBuildInfo(..))
import Distribution.Simple.Program (defaultProgramDb)
import Distribution.Simple.Setup (Flag(..), readPackageDbList, showPackageDbList)
import Distribution.Simple.Utils (cabalVersion)
import Distribution.Text (display)
import Distribution.Verbosity (normal, flagToVerbosity, lessVerbose)
import Distribution.ReadE (readEOrFail)
import Distribution.Compat.Time (calibrateMtimeChangeDelay)
import Control.Exception
import Data.Proxy ( Proxy(..) )
import Distribution.Compat.Environment ( lookupEnv )
import System.Directory
import Test.Tasty
import Test.Tasty.Options
import Test.Tasty.Ingredients
#if MIN_VERSION_base(4,6,0)
import System.Environment ( getExecutablePath )
#endif
main :: IO ()
main = do
-- In abstract, the Cabal test suite makes calls to the "Setup"
-- executable and tests the output of Cabal. However, we have to
-- responsible for building this executable in the first place,
-- since (1) Cabal doesn't support a test-suite depending on an
-- executable, so we can't put a "Setup" executable in the Cabal
-- file and then depend on it, (2) we don't want to call the Cabal
-- functions *directly* because we need to capture and save the
-- stdout and stderr, and (3) even if we could do all that, we will
-- want to test some Custom setup scripts, which will be specific to
-- the test at hand and need to be compiled against Cabal.
--
-- To be able to build the executable, there is some information
-- we need:
--
-- 1. We need to know what ghc to use,
--
-- 2. We need to know what package databases (plural!) contain
-- all of the necessary dependencies to make our Cabal package
-- well-formed.
--
-- We could have the user pass these all in as arguments, but
-- there's a more convenient way to get this information: the *build
-- configuration* that was used to build the Cabal library (and this
-- test suite) in the first place. To do this, we need to find the
-- 'dist' directory that was set as the build directory for Cabal.
-- First, figure out the dist directory associated with this Cabal.
dist_dir :: FilePath <- guessDistDir
-- Next, attempt to read out the LBI. This may not work, in which
-- case we'll try to guess the correct parameters. This is ignored
-- if values are explicitly passed into the test suite.
mb_lbi <- getPersistBuildConfig_ (dist_dir </> "setup-config")
-- You need to run the test suite in the right directory, sorry.
-- This variable is modestly misnamed: this refers to the base
-- directory of Cabal (so, CHECKOUT_DIR/Cabal, not
-- CHECKOUT_DIR/Cabal/test).
cabal_dir <- getCurrentDirectory
-- TODO: make this controllable by a flag. We do have a flag
-- parser but it's not called early enough for this verbosity...
verbosity <- maybe normal (readEOrFail flagToVerbosity)
`fmap` lookupEnv "VERBOSE"
-------------------------------------------------------------------
-- SETTING UP GHC AND GHC-PKG
-------------------------------------------------------------------
-- NOTE: There are TWO configurations of GHC we have to manage
-- when running the test suite.
--
-- 1. The primary GHC is the one that was used to build the
-- copy of Cabal that we are testing. This configuration
-- can be pulled out of the LBI.
--
-- 2. The "with" GHC is the version of GHC we ask the Cabal
-- we are testing to use (i.e., using --with-compiler). Notice
-- that this does NOT have to match the version we compiled
-- the library with! (Not all tests will work in this situation,
-- however, since some need to link against the Cabal library.)
-- By default we use the same configuration as the one from the
-- LBI, but a user can override it to test against a different
-- version of GHC.
mb_ghc_path <- lookupEnv "CABAL_PACKAGETESTS_GHC"
mb_ghc_pkg_path <- lookupEnv "CABAL_PACKAGETESTS_GHC_PKG"
boot_programs <-
case (mb_ghc_path, mb_ghc_pkg_path) of
(Nothing, Nothing) | Just lbi <- mb_lbi -> do
putStrLn "Using configuration from LBI"
return (withPrograms lbi)
_ -> do
putStrLn "(Re)configuring test suite (ignoring LBI)"
(_comp, _compPlatform, programDb)
<- configCompilerEx
(Just GHC) mb_ghc_path mb_ghc_pkg_path
-- NB: if we accept full ConfigFlags parser then
-- should use (mkProgramDb cfg (configPrograms cfg))
-- instead.
defaultProgramDb
(lessVerbose verbosity)
return programDb
mb_with_ghc_path <- lookupEnv "CABAL_PACKAGETESTS_WITH_GHC"
mb_with_ghc_pkg_path <- lookupEnv "CABAL_PACKAGETESTS_WITH_GHC_PKG"
with_programs <-
case (mb_with_ghc_path, mb_with_ghc_path) of
(Nothing, Nothing) -> return boot_programs
_ -> do
putStrLn "Configuring test suite for --with-compiler"
(_comp, _compPlatform, with_programs)
<- configCompilerEx
(Just GHC) mb_with_ghc_path mb_with_ghc_pkg_path
defaultProgramDb
(lessVerbose verbosity)
return with_programs
-------------------------------------------------------------------
-- SETTING UP THE DATABASE STACK
-------------------------------------------------------------------
-- Figure out what database stack to use. (This is the tricky bit,
-- because we need to have enough databases to make the just-built
-- Cabal package well-formed).
db_stack_env <- lookupEnv "CABAL_PACKAGETESTS_DB_STACK"
let packageDBStack0 = case db_stack_env of
Just str -> interpretPackageDbFlags True -- user install? why not.
(concatMap readPackageDbList
(splitSearchPath str))
Nothing ->
case mb_lbi of
Just lbi -> withPackageDB lbi
-- A wild guess!
Nothing -> interpretPackageDbFlags True []
-- Package DBs are not guaranteed to be absolute, so make them so in
-- case a subprocess using the package DB needs a different CWD.
packageDBStack1 <- mapM canonicalizePackageDB packageDBStack0
-- The LBI's database stack does *not* contain the inplace installed
-- Cabal package. So we need to add that to the stack.
let package_db_stack
= packageDBStack1 ++
[SpecificPackageDB
(dist_dir </> "package.conf.inplace")]
-- NB: It's possible that our database stack is broken (e.g.,
-- it's got a database for the wrong version of GHC, or it
-- doesn't have enough to let us build Cabal.) We'll notice
-- when we attempt to compile setup.
-- There is also is a parameter for the stack for --with-compiler,
-- since if GHC is a different version we need a different set of
-- databases. The default should actually be quite reasonable
-- as, unlike in the case of the GHC used to build Cabal, we don't
-- expect htere to be a Cabal available.
with_ghc_db_stack_env :: Maybe String
<- lookupEnv "CABAL_PACKAGETESTS_WITH_GHC_DB_STACK"
let withGhcDBStack0 :: PackageDBStack
withGhcDBStack0 =
interpretPackageDbFlags True
$ case with_ghc_db_stack_env of
Nothing -> []
Just str -> concatMap readPackageDbList (splitSearchPath str)
with_ghc_db_stack :: PackageDBStack
<- mapM canonicalizePackageDB withGhcDBStack0
-- THIS ISN'T EVEN MY FINAL FORM. The package database stack
-- controls where we install a package; specifically, the package is
-- installed to the top-most package on the stack (this makes the
-- most sense, since it could depend on any of the packages below
-- it.) If the test wants to register anything (as opposed to just
-- working in place), then we need to have another temporary
-- database we can install into (and not accidentally clobber any of
-- the other stacks.) This is done on a per-test basis.
--
-- ONE MORE THING. On the subject of installing the package (with
-- copy/register) it is EXTREMELY important that we also overload
-- the install directories, so we don't clobber anything in the
-- default install paths. VERY IMPORTANT.
-- Figure out how long we need to delay for recompilation tests
(mtimeChange, mtimeChange') <- calibrateMtimeChangeDelay
let suite = SuiteConfig
{ cabalDistPref = dist_dir
, bootProgramDb = boot_programs
, withProgramDb = with_programs
, packageDBStack = package_db_stack
, withGhcDBStack = with_ghc_db_stack
, suiteVerbosity = verbosity
, absoluteCWD = cabal_dir
, mtimeChangeDelay = mtimeChange'
}
let toMillis :: Int -> Double
toMillis x = fromIntegral x / 1000.0
putStrLn $ "Cabal test suite - testing cabal version "
++ display cabalVersion
putStrLn $ "Cabal build directory: " ++ dist_dir
putStrLn $ "Cabal source directory: " ++ cabal_dir
putStrLn $ "File modtime calibration: " ++ show (toMillis mtimeChange')
++ " (maximum observed: " ++ show (toMillis mtimeChange) ++ ")"
-- TODO: it might be useful to factor this out so that ./Setup
-- configure dumps this file, so we can read it without in a version
-- stable way.
putStrLn $ "Environment:"
putStrLn $ "CABAL_PACKAGETESTS_GHC=" ++ show (ghcPath suite) ++ " \\"
putStrLn $ "CABAL_PACKAGETESTS_GHC_PKG=" ++ show (ghcPkgPath suite) ++ " \\"
putStrLn $ "CABAL_PACKAGETESTS_WITH_GHC=" ++ show (withGhcPath suite) ++ " \\"
putStrLn $ "CABAL_PACKAGETESTS_WITH_GHC_PKG=" ++ show (withGhcPkgPath suite) ++ " \\"
-- For brevity, we use the pre-canonicalized values
let showDBStack = show
. intercalate [searchPathSeparator]
. showPackageDbList
. uninterpretPackageDBFlags
putStrLn $ "CABAL_PACKAGETESTS_DB_STACK=" ++ showDBStack packageDBStack0
putStrLn $ "CABAL_PACKAGETESTS_WITH_DB_STACK=" ++ showDBStack withGhcDBStack0
-- Create a shared Setup executable to speed up Simple tests
putStrLn $ "Building shared ./Setup executable"
rawCompileSetup verbosity suite [] "tests"
defaultMainWithIngredients options $
runTestTree "Package Tests" (tests suite)
-- Reverse of 'interpretPackageDbFlags'.
-- prop_idem stk b
-- = interpretPackageDbFlags b (uninterpretPackageDBFlags stk) == stk
uninterpretPackageDBFlags :: PackageDBStack -> [Maybe PackageDB]
uninterpretPackageDBFlags stk = Nothing : map (\x -> Just x) stk
-- | Guess what the 'dist' directory Cabal was installed in is. There's
-- no 100% reliable way to find this, but there are a few good shots:
--
-- 1. Test programs are ~always built in-place, in a directory
-- that looks like dist/build/package-tests/package-tests;
-- thus the directory can be determined by looking at $0.
-- This method is robust against sandboxes, Nix local
-- builds, and Stack, but doesn't work if you're running
-- in an interpreter.
--
-- 2. We can use the normal input methods (as per Cabal),
-- checking for the CABAL_BUILDDIR environment variable as
-- well as the default location in the current working directory.
--
-- NB: If you update this, also update its copy in cabal-install's
-- IntegrationTests
guessDistDir :: IO FilePath
guessDistDir = do
#if MIN_VERSION_base(4,6,0)
-- Method (1)
-- TODO: this needs to be BC'ified, probably.
exe_path <- canonicalizePath =<< getExecutablePath
-- exe_path is something like /path/to/dist/build/package-tests/package-tests
let dist0 = dropFileName exe_path </> ".." </> ".."
b <- doesFileExist (dist0 </> "setup-config")
#else
let dist0 = error "no path"
b = False
#endif
-- Method (2)
if b then canonicalizePath dist0
else findDistPrefOrDefault NoFlag >>= canonicalizePath
canonicalizePackageDB :: PackageDB -> IO PackageDB
canonicalizePackageDB (SpecificPackageDB path)
= SpecificPackageDB `fmap` canonicalizePath path
canonicalizePackageDB x = return x
-- | Like Distribution.Simple.Configure.getPersistBuildConfig but
-- doesn't check that the Cabal version matches, which it doesn't when
-- we run Cabal's own test suite, due to bootstrapping issues.
-- Here's the situation:
--
-- 1. There's some system Cabal-1.0 installed. We use this
-- to build Setup.hs
-- 2. We run ./Setup configure, which uses Cabal-1.0 to
-- write out the LocalBuildInfo
-- 3. We build the Cabal library, whose version is Cabal-2.0
-- 4. We build the package-tests executable, which LINKS AGAINST
-- Cabal-2.0
-- 5. We try to read the LocalBuildInfo that ./Setup configure
-- wrote out, but it's Cabal-1.0 format!
--
-- It's a bit skeevy that we're trying to read Cabal-1.0 LocalBuildInfo
-- using Cabal-2.0's parser, but this seems to work OK in practice
-- because LocalBuildInfo is a slow-moving data structure. If
-- we ever make a major change, this won't work, and we'll have to
-- take a different approach (either setting "build-type: Custom"
-- so we bootstrap with the most recent Cabal, or by writing the
-- information we need in another format.)
getPersistBuildConfig_ :: FilePath -> IO (Maybe LocalBuildInfo)
getPersistBuildConfig_ filename = do
eLBI <- try $ getConfigStateFile filename
case eLBI of
-- If the version doesn't match but we still got a successful
-- parse, don't complain and just use it!
Left (ConfigStateFileBadVersion _ _ (Right lbi)) -> return (Just lbi)
Left _ -> return Nothing
Right lbi -> return (Just lbi)
options :: [Ingredient]
options = includingOptions
[Option (Proxy :: Proxy OptionEnableAllTests)] :
defaultIngredients