1{-# LANGUAGE ScopedTypeVariables #-}
2{-# LANGUAGE NamedFieldPuns #-}
3{-# LANGUAGE NondecreasingIndentation #-}
4{-# LANGUAGE FlexibleContexts #-}
5{-# LANGUAGE CPP #-}
6
7-- | Generally useful definitions that we expect most test scripts
8-- to use.
9module Test.Cabal.Prelude (
10    module Test.Cabal.Prelude,
11    module Test.Cabal.Monad,
12    module Test.Cabal.Run,
13    module System.FilePath,
14    module Control.Monad,
15    module Control.Monad.IO.Class,
16    module Distribution.Version,
17    module Distribution.Simple.Program,
18) where
19
20import Test.Cabal.Script
21import Test.Cabal.Run
22import Test.Cabal.Monad
23import Test.Cabal.Plan
24
25import Distribution.Compat.Time (calibrateMtimeChangeDelay)
26import Distribution.Simple.Compiler (PackageDBStack, PackageDB(..))
27import Distribution.Simple.Program.Types
28import Distribution.Simple.Program.Db
29import Distribution.Simple.Program
30import Distribution.System (OS(Windows,Linux,OSX), buildOS)
31import Distribution.Simple.Utils
32    ( withFileContents, withTempDirectory, tryFindPackageDesc )
33import Distribution.Simple.Configure
34    ( getPersistBuildConfig )
35import Distribution.Version
36import Distribution.Package
37import Distribution.Types.UnqualComponentName
38import Distribution.Types.LocalBuildInfo
39import Distribution.PackageDescription
40import Distribution.PackageDescription.Parsec
41import Distribution.Verbosity (normal)
42
43import Distribution.Compat.Stack
44
45import Text.Regex.TDFA ((=~))
46
47import Control.Concurrent.Async (waitCatch, withAsync)
48import qualified Data.Aeson as JSON
49import qualified Data.ByteString.Lazy as BSL
50import Control.Monad (unless, when, void, forM_, liftM2, liftM4)
51import Control.Monad.Trans.Reader (withReaderT, runReaderT)
52import Control.Monad.IO.Class (MonadIO (..))
53import qualified Data.ByteString.Char8 as C
54import Data.List (isInfixOf, stripPrefix, isPrefixOf, intercalate)
55import Data.List.NonEmpty (NonEmpty (..))
56import qualified Data.List.NonEmpty as NE
57import Data.Maybe (mapMaybe, fromMaybe)
58import System.Exit (ExitCode (..))
59import System.FilePath ((</>), takeExtensions, takeDrive, takeDirectory, normalise, splitPath, joinPath, splitFileName, (<.>), dropTrailingPathSeparator)
60import Control.Concurrent (threadDelay)
61import qualified Data.Char as Char
62import System.Directory (getTemporaryDirectory, getCurrentDirectory, copyFile, removeFile, copyFile, doesFileExist, createDirectoryIfMissing, getDirectoryContents)
63
64#ifndef mingw32_HOST_OS
65import Control.Monad.Catch ( bracket_ )
66import System.Posix.Files  ( createSymbolicLink )
67import System.Posix.Resource
68#endif
69
70------------------------------------------------------------------------
71-- * Utilities
72
73runM :: FilePath -> [String] -> Maybe String -> TestM Result
74runM path args input = do
75    env <- getTestEnv
76    r <- liftIO $ run (testVerbosity env)
77                 (Just (testCurrentDir env))
78                 (testEnvironment env)
79                 path
80                 args
81                 input
82    recordLog r
83    requireSuccess r
84
85runProgramM :: Program -> [String] -> Maybe String -> TestM Result
86runProgramM prog args input = do
87    configured_prog <- requireProgramM prog
88    -- TODO: Consider also using other information from
89    -- ConfiguredProgram, e.g., env and args
90    runM (programPath configured_prog) args input
91
92getLocalBuildInfoM :: TestM LocalBuildInfo
93getLocalBuildInfoM = do
94    env <- getTestEnv
95    liftIO $ getPersistBuildConfig (testDistDir env)
96
97------------------------------------------------------------------------
98-- * Changing parameters
99
100withDirectory :: FilePath -> TestM a -> TestM a
101withDirectory f = withReaderT
102    (\env -> env { testRelativeCurrentDir = testRelativeCurrentDir env </> f })
103
104-- We append to the environment list, as per 'getEffectiveEnvironment'
105-- which prefers the latest override.
106withEnv :: [(String, Maybe String)] -> TestM a -> TestM a
107withEnv e = withReaderT (\env -> env { testEnvironment = testEnvironment env ++ e })
108
109-- HACK please don't use me
110withEnvFilter :: (String -> Bool) -> TestM a -> TestM a
111withEnvFilter p = withReaderT (\env -> env { testEnvironment = filter (p . fst) (testEnvironment env) })
112
113------------------------------------------------------------------------
114-- * Running Setup
115
116marked_verbose :: String
117marked_verbose = "-vverbose +markoutput +nowrap"
118
119setup :: String -> [String] -> TestM ()
120setup cmd args = void (setup' cmd args)
121
122setup' :: String -> [String] -> TestM Result
123setup' = setup'' "."
124
125setup''
126  :: FilePath
127  -- ^ Subdirectory to find the @.cabal@ file in.
128  -> String
129  -- ^ Command name
130  -> [String]
131  -- ^ Arguments
132  -> TestM Result
133setup'' prefix cmd args = do
134    env <- getTestEnv
135    when ((cmd == "register" || cmd == "copy") && not (testHavePackageDb env)) $
136        error "Cannot register/copy without using 'withPackageDb'"
137    ghc_path     <- programPathM ghcProgram
138    haddock_path <- programPathM haddockProgram
139    let args' = case cmd of
140            "configure" ->
141                -- If the package database is empty, setting --global
142                -- here will make us error loudly if we try to install
143                -- into a bad place.
144                [ "--global"
145                -- NB: technically unnecessary with Cabal, but
146                -- definitely needed for Setup, which doesn't
147                -- respect cabal.config
148                , "--with-ghc", ghc_path
149                , "--with-haddock", haddock_path
150                -- This avoids generating hashes in our package IDs,
151                -- which helps the test suite's expect tests.
152                , "--enable-deterministic"
153                -- These flags make the test suite run faster
154                -- Can't do this unless we LD_LIBRARY_PATH correctly
155                -- , "--enable-executable-dynamic"
156                -- , "--disable-optimization"
157                -- Specify where we want our installed packages to go
158                , "--prefix=" ++ testPrefixDir env
159                ] ++ packageDBParams (testPackageDBStack env)
160                  ++ args
161            _ -> args
162    let rel_dist_dir = definitelyMakeRelative (testCurrentDir env) (testDistDir env)
163        full_args = cmd :| [marked_verbose, "--distdir", rel_dist_dir] ++ args'
164    defaultRecordMode RecordMarked $ do
165    recordHeader ["Setup", cmd]
166
167    -- We test `cabal act-act-setup` when running cabal-tests.
168    --
169    -- `cabal` and `Setup.hs` do have different interface.
170    --
171
172    pdfile <- liftIO $ tryFindPackageDesc (testVerbosity env) (testCurrentDir env </> prefix)
173    pdesc <- liftIO $ readGenericPackageDescription (testVerbosity env) pdfile
174    if testCabalInstallAsSetup env
175    then if buildType (packageDescription pdesc) == Simple
176         then runProgramM cabalProgram ("act-as-setup" : "--" : NE.toList full_args) Nothing
177         else fail "Using act-as-setup for not 'build-type: Simple' package"
178    else do
179        if buildType (packageDescription pdesc) == Simple
180            then runM (testSetupPath env) (NE.toList full_args) Nothing
181            -- Run the Custom script!
182            else do
183              r <- liftIO $ runghc (testScriptEnv env)
184                                   (Just (testCurrentDir env))
185                                   (testEnvironment env)
186                                   (testCurrentDir env </> prefix </> "Setup.hs")
187                                   (NE.toList full_args)
188              recordLog r
189              requireSuccess r
190
191    -- This code is very tempting (and in principle should be quick:
192    -- after all we are loading the built version of Cabal), but
193    -- actually it costs quite a bit in wallclock time (e.g. 54sec to
194    -- 68sec on AllowNewer, working with un-optimized Cabal.)
195    {-
196    r <- liftIO $ runghc (testScriptEnv env)
197                         (Just (testCurrentDir env))
198                         (testEnvironment env)
199                         "Setup.hs"
200                         (cmd : ["-v", "--distdir", testDistDir env] ++ args')
201    -- don't forget to check results...
202    -}
203
204definitelyMakeRelative :: FilePath -> FilePath -> FilePath
205definitelyMakeRelative base0 path0 =
206    let go [] path = joinPath path
207        go base [] = joinPath (replicate (length base) "..")
208        go (x:xs) (y:ys)
209            | x == y    = go xs ys
210            | otherwise = go (x:xs) [] </> go [] (y:ys)
211    -- NB: It's important to normalize, as otherwise if
212    -- we see "foo/./bar" we'll incorrectly conclude that we need
213    -- to go "../../.." to get out of it.
214    in go (splitPath (normalise base0)) (splitPath (normalise path0))
215
216-- | This abstracts the common pattern of configuring and then building.
217setup_build :: [String] -> TestM ()
218setup_build args = do
219    setup "configure" args
220    setup "build" []
221    return ()
222
223-- | This abstracts the common pattern of "installing" a package.
224setup_install :: [String] -> TestM ()
225setup_install args = do
226    setup "configure" args
227    setup "build" []
228    setup "copy" []
229    setup "register" []
230    return ()
231
232-- | This abstracts the common pattern of "installing" a package,
233-- with haddock documentation.
234setup_install_with_docs :: [String] -> TestM ()
235setup_install_with_docs args = do
236    setup "configure" args
237    setup "build" []
238    setup "haddock" []
239    setup "copy" []
240    setup "register" []
241    return ()
242
243packageDBParams :: PackageDBStack -> [String]
244packageDBParams dbs = "--package-db=clear"
245                    : map (("--package-db=" ++) . convert) dbs
246  where
247    convert :: PackageDB -> String
248    convert  GlobalPackageDB         = "global"
249    convert  UserPackageDB           = "user"
250    convert (SpecificPackageDB path) = path
251
252------------------------------------------------------------------------
253-- * Running cabal
254
255cabal :: String -> [String] -> TestM ()
256cabal cmd args = void (cabal' cmd args)
257
258cabal' :: String -> [String] -> TestM Result
259cabal' = cabalG' []
260
261cabalWithStdin :: String -> [String] -> String -> TestM Result
262cabalWithStdin cmd args input = cabalGArgs [] cmd args (Just input)
263
264cabalG :: [String] -> String -> [String] -> TestM ()
265cabalG global_args cmd args = void (cabalG' global_args cmd args)
266
267cabalG' :: [String] -> String -> [String] -> TestM Result
268cabalG' global_args cmd args = cabalGArgs global_args cmd args Nothing
269
270cabalGArgs :: [String] -> String -> [String] -> Maybe String -> TestM Result
271cabalGArgs global_args cmd args input = do
272    env <- getTestEnv
273    -- Freeze writes out cabal.config to source directory, this is not
274    -- overwritable
275    when (cmd == "v1-freeze") requireHasSourceCopy
276    let extra_args
277          | cmd `elem` ["v1-update", "outdated", "user-config", "man", "v1-freeze", "check"]
278          = [ ]
279
280          -- new-build commands are affected by testCabalProjectFile
281          | cmd == "v2-sdist"
282          = [ "--project-file", testCabalProjectFile env ]
283
284          | "v2-" `isPrefixOf` cmd
285          = [ "--builddir", testDistDir env
286            , "--project-file", testCabalProjectFile env
287            , "-j1" ]
288
289          | otherwise
290          = [ "--builddir", testDistDir env ] ++
291            install_args
292
293        install_args
294          | cmd == "v1-install" || cmd == "v1-build" = [ "-j1" ]
295          | otherwise                                = []
296
297        cabal_args = global_args
298                  ++ [ cmd, marked_verbose ]
299                  ++ extra_args
300                  ++ args
301    defaultRecordMode RecordMarked $ do
302    recordHeader ["cabal", cmd]
303    cabal_raw' cabal_args input
304
305cabal_raw' :: [String] -> Maybe String -> TestM Result
306cabal_raw' cabal_args input = runProgramM cabalProgram cabal_args input
307
308withProjectFile :: FilePath -> TestM a -> TestM a
309withProjectFile fp m =
310    withReaderT (\env -> env { testCabalProjectFile = fp }) m
311
312-- | Assuming we've successfully configured a new-build project,
313-- read out the plan metadata so that we can use it to do other
314-- operations.
315withPlan :: TestM a -> TestM a
316withPlan m = do
317    env0 <- getTestEnv
318    let filepath = testDistDir env0 </> "cache" </> "plan.json"
319    mplan <- JSON.eitherDecode `fmap` liftIO (BSL.readFile filepath)
320    case mplan of
321        Left err   -> fail $ "withPlan: cannot decode plan " ++ err
322        Right plan -> withReaderT (\env -> env { testPlan = Just plan }) m
323
324-- | Run an executable from a package.  Requires 'withPlan' to have
325-- been run so that we can find the dist dir.
326runPlanExe :: String {- package name -} -> String {- component name -}
327           -> [String] -> TestM ()
328runPlanExe pkg_name cname args = void $ runPlanExe' pkg_name cname args
329
330-- | Run an executable from a package.  Requires 'withPlan' to have
331-- been run so that we can find the dist dir.  Also returns 'Result'.
332runPlanExe' :: String {- package name -} -> String {- component name -}
333            -> [String] -> TestM Result
334runPlanExe' pkg_name cname args = do
335    Just plan <- testPlan `fmap` getTestEnv
336    let dist_dir = planDistDir plan (mkPackageName pkg_name)
337                        (CExeName (mkUnqualComponentName cname))
338    defaultRecordMode RecordAll $ do
339    recordHeader [pkg_name, cname]
340    runM (dist_dir </> "build" </> cname </> cname) args Nothing
341
342------------------------------------------------------------------------
343-- * Running ghc-pkg
344
345withPackageDb :: TestM a -> TestM a
346withPackageDb m = do
347    env <- getTestEnv
348    let db_path = testPackageDbDir env
349    if testHavePackageDb env
350        then m
351        else withReaderT (\nenv ->
352                            nenv { testPackageDBStack
353                                    = testPackageDBStack env
354                                   ++ [SpecificPackageDB db_path]
355                                , testHavePackageDb = True
356                                } )
357               $ do ghcPkg "init" [db_path]
358                    m
359
360ghcPkg :: String -> [String] -> TestM ()
361ghcPkg cmd args = void (ghcPkg' cmd args)
362
363ghcPkg' :: String -> [String] -> TestM Result
364ghcPkg' cmd args = do
365    env <- getTestEnv
366    unless (testHavePackageDb env) $
367        error "Must initialize package database using withPackageDb"
368    -- NB: testDBStack already has the local database
369    ghcConfProg <- requireProgramM ghcProgram
370    let db_stack = testPackageDBStack env
371        extraArgs = ghcPkgPackageDBParams
372                        (fromMaybe
373                            (error "ghc-pkg: cannot detect version")
374                            (programVersion ghcConfProg))
375                        db_stack
376    recordHeader ["ghc-pkg", cmd]
377    runProgramM ghcPkgProgram (cmd : extraArgs ++ args) Nothing
378
379ghcPkgPackageDBParams :: Version -> PackageDBStack -> [String]
380ghcPkgPackageDBParams version dbs = concatMap convert dbs where
381    convert :: PackageDB -> [String]
382    -- Ignoring global/user is dodgy but there's no way good
383    -- way to give ghc-pkg the correct flags in this case.
384    convert  GlobalPackageDB         = []
385    convert  UserPackageDB           = []
386    convert (SpecificPackageDB path)
387        | version >= mkVersion [7,6]
388        = ["--package-db=" ++ path]
389        | otherwise
390        = ["--package-conf=" ++ path]
391
392------------------------------------------------------------------------
393-- * Running other things
394
395-- | Run an executable that was produced by cabal.  The @exe_name@
396-- is precisely the name of the executable section in the file.
397runExe :: String -> [String] -> TestM ()
398runExe exe_name args = void (runExe' exe_name args)
399
400runExe' :: String -> [String] -> TestM Result
401runExe' exe_name args = do
402    env <- getTestEnv
403    defaultRecordMode RecordAll $ do
404    recordHeader [exe_name]
405    runM (testDistDir env </> "build" </> exe_name </> exe_name) args Nothing
406
407-- | Run an executable that was installed by cabal.  The @exe_name@
408-- is precisely the name of the executable.
409runInstalledExe :: String -> [String] -> TestM ()
410runInstalledExe exe_name args = void (runInstalledExe' exe_name args)
411
412-- | Run an executable that was installed by cabal.  Use this
413-- instead of 'runInstalledExe' if you need to inspect the
414-- stdout/stderr output.
415runInstalledExe' :: String -> [String] -> TestM Result
416runInstalledExe' exe_name args = do
417    env <- getTestEnv
418    defaultRecordMode RecordAll $ do
419    recordHeader [exe_name]
420    runM (testPrefixDir env </> "bin" </> exe_name) args Nothing
421
422-- | Run a shell command in the current directory.
423shell :: String -> [String] -> TestM Result
424shell exe args = runM exe args Nothing
425
426------------------------------------------------------------------------
427-- * Repository manipulation
428
429-- Workflows we support:
430--  1. Test comes with some packages (directories in repository) which
431--  should be in the repository and available for depsolving/installing
432--  into global store.
433--
434-- Workflows we might want to support in the future
435--  * Regression tests may want to test on Hackage index.  They will
436--  operate deterministically as they will be pinned to a timestamp.
437--  (But should we allow this? Have to download the tarballs in that
438--  case. Perhaps dep solver only!)
439--  * We might sdist a local package, and then upload it to the
440--  repository
441--  * Some of our tests involve old versions of Cabal.  This might
442--  be one of the rare cases where we're willing to grab the entire
443--  tarball.
444--
445-- Properties we want to hold:
446--  1. Tests can be run offline.  No dependence on hackage.haskell.org
447--  beyond what we needed to actually get the build of Cabal working
448--  itself
449--  2. Tests are deterministic.  Updates to Hackage should not cause
450--  tests to fail.  (OTOH, it's good to run tests on most recent
451--  Hackage index; some sort of canary test which is run nightly.
452--  Point is it should NOT be tied to cabal source code.)
453--
454-- Technical notes:
455--  * We depend on hackage-repo-tool binary.  It would better if it was
456--  libified into hackage-security but this has not been done yet.
457--
458
459hackageRepoTool :: String -> [String] -> TestM ()
460hackageRepoTool cmd args = void $ hackageRepoTool' cmd args
461
462hackageRepoTool' :: String -> [String] -> TestM Result
463hackageRepoTool' cmd args = do
464    recordHeader ["hackage-repo-tool", cmd]
465    runProgramM hackageRepoToolProgram (cmd : args) Nothing
466
467tar :: [String] -> TestM ()
468tar args = void $ tar' args
469
470tar' :: [String] -> TestM Result
471tar' args = do
472    recordHeader ["tar"]
473    runProgramM tarProgram args Nothing
474
475-- | Creates a tarball of a directory, such that if you
476-- archive the directory "/foo/bar/baz" to "mine.tgz", @tar tf@ reports
477-- @baz/file1@, @baz/file2@, etc.
478archiveTo :: FilePath -> FilePath -> TestM ()
479src `archiveTo` dst = do
480    -- TODO: Consider using the @tar@ library?
481    let (src_parent, src_dir) = splitFileName src
482    -- TODO: --format ustar, like createArchive?
483    -- --force-local is necessary for handling colons in Windows paths.
484    tar $ ["-czf", dst]
485       ++ ["--force-local" | buildOS == Windows]
486       ++ ["-C", src_parent, src_dir]
487
488infixr 4 `archiveTo`
489
490-- | Given a directory (relative to the 'testCurrentDir') containing
491-- a series of directories representing packages, generate an
492-- external repository corresponding to all of these packages
493withRepo :: FilePath -> TestM a -> TestM a
494withRepo repo_dir m = do
495    env <- getTestEnv
496
497    -- Check if hackage-repo-tool is available, and skip if not
498    skipUnless =<< isAvailableProgram hackageRepoToolProgram
499
500    -- 1. Generate keys
501    hackageRepoTool "create-keys" ["--keys", testKeysDir env]
502    -- 2. Initialize repo directory
503    let package_dir = testRepoDir env </> "package"
504    liftIO $ createDirectoryIfMissing True (testRepoDir env </> "index")
505    liftIO $ createDirectoryIfMissing True package_dir
506    -- 3. Create tarballs
507    pkgs <- liftIO $ getDirectoryContents (testCurrentDir env </> repo_dir)
508    forM_ pkgs $ \pkg -> do
509        case pkg of
510            '.':_ -> return ()
511            _     -> testCurrentDir env </> repo_dir </> pkg
512                        `archiveTo`
513                            package_dir </> pkg <.> "tar.gz"
514    -- 4. Initialize repository
515    hackageRepoTool "bootstrap" ["--keys", testKeysDir env, "--repo", testRepoDir env]
516    -- 5. Wire it up in .cabal/config
517    -- TODO: libify this
518    let package_cache = testCabalDir env </> "packages"
519    liftIO $ appendFile (testUserCabalConfigFile env)
520           $ unlines [ "repository test-local-repo"
521                     , "  url: " ++ repoUri env
522                     , "  secure: True"
523                     -- TODO: Hypothetically, we could stick in the
524                     -- correct key here
525                     , "  root-keys: "
526                     , "  key-threshold: 0"
527                     , "remote-repo-cache: " ++ package_cache ]
528    -- 6. Create local directories (TODO: this is a bug #4136, once you
529    -- fix that this can be removed)
530    liftIO $ createDirectoryIfMissing True (package_cache </> "test-local-repo")
531    -- 7. Update our local index
532    cabal "v1-update" []
533    -- 8. Profit
534    withReaderT (\env' -> env' { testHaveRepo = True }) m
535    -- TODO: Arguably should undo everything when we're done...
536  where
537    -- Work around issue #5218 (incorrect conversions between Windows paths and
538    -- file URIs) by using a relative path on Windows.
539    repoUri env =
540      if buildOS == Windows
541      then let relPath = definitelyMakeRelative (testCurrentDir env)
542                                                (testRepoDir env)
543               convertSeparators = intercalate "/"
544                                 . map dropTrailingPathSeparator
545                                 . splitPath
546           in "file:" ++ convertSeparators relPath
547      else "file:" ++ testRepoDir env
548
549------------------------------------------------------------------------
550-- * Subprocess run results
551
552requireSuccess :: Result -> TestM Result
553requireSuccess r@Result { resultCommand = cmd
554                        , resultExitCode = exitCode
555                        , resultOutput = output } = withFrozenCallStack $ do
556    env <- getTestEnv
557    when (exitCode /= ExitSuccess && not (testShouldFail env)) $
558        assertFailure $ "Command " ++ cmd ++ " failed.\n" ++
559        "Output:\n" ++ output ++ "\n"
560    when (exitCode == ExitSuccess && testShouldFail env) $
561        assertFailure $ "Command " ++ cmd ++ " succeeded.\n" ++
562        "Output:\n" ++ output ++ "\n"
563    return r
564
565initWorkDir :: TestM ()
566initWorkDir = do
567    env <- getTestEnv
568    liftIO $ createDirectoryIfMissing True (testWorkDir env)
569
570-- | Record a header to help identify the output to the expect
571-- log.  Unlike the 'recordLog', we don't record all arguments;
572-- just enough to give you an idea of what the command might have
573-- been.  (This is because the arguments may not be deterministic,
574-- so we don't want to spew them to the log.)
575recordHeader :: [String] -> TestM ()
576recordHeader args = do
577    env <- getTestEnv
578    let mode = testRecordMode env
579        str_header = "# " ++ intercalate " " args ++ "\n"
580        header = C.pack (testRecordNormalizer env str_header)
581    case mode of
582        DoNotRecord -> return ()
583        _ -> do
584            initWorkDir
585            liftIO $ putStr str_header
586            liftIO $ C.appendFile (testWorkDir env </> "test.log") header
587            liftIO $ C.appendFile (testActualFile env) header
588
589recordLog :: Result -> TestM ()
590recordLog res = do
591    env <- getTestEnv
592    let mode = testRecordMode env
593    initWorkDir
594    liftIO $ C.appendFile (testWorkDir env </> "test.log")
595                         (C.pack $ "+ " ++ resultCommand res ++ "\n"
596                            ++ resultOutput res ++ "\n\n")
597    liftIO . C.appendFile (testActualFile env) . C.pack . testRecordNormalizer env $
598        case mode of
599            RecordAll    -> unlines (lines (resultOutput res))
600            RecordMarked -> getMarkedOutput (resultOutput res)
601            DoNotRecord  -> ""
602
603getMarkedOutput :: String -> String -- trailing newline
604getMarkedOutput out = unlines (go (lines out) False)
605  where
606    go [] _ = []
607    go (x:xs) True
608        | "-----END CABAL OUTPUT-----"   `isPrefixOf` x
609                    =     go xs False
610        | otherwise = x : go xs True
611    go (x:xs) False
612        -- NB: Windows has extra goo at the end
613        | "-----BEGIN CABAL OUTPUT-----" `isPrefixOf` x
614                    = go xs True
615        | otherwise = go xs False
616
617------------------------------------------------------------------------
618-- * Test helpers
619
620assertFailure :: WithCallStack (String -> m ())
621assertFailure msg = withFrozenCallStack $ error msg
622
623assertExitCode :: MonadIO m => WithCallStack (ExitCode -> Result -> m ())
624assertExitCode code result =
625  when (code /= resultExitCode result) $
626    assertFailure $ "Expected exit code: "
627                 ++ show code
628                 ++ "\nActual: "
629                 ++ show (resultExitCode result)
630
631assertEqual :: (Eq a, Show a, MonadIO m) => WithCallStack (String -> a -> a -> m ())
632assertEqual s x y =
633    withFrozenCallStack $
634      when (x /= y) $
635        error (s ++ ":\nExpected: " ++ show x ++ "\nActual: " ++ show y)
636
637assertNotEqual :: (Eq a, Show a, MonadIO m) => WithCallStack (String -> a -> a -> m ())
638assertNotEqual s x y =
639    withFrozenCallStack $
640      when (x == y) $
641        error (s ++ ":\nGot both: " ++ show x)
642
643assertBool :: MonadIO m => WithCallStack (String -> Bool -> m ())
644assertBool s x =
645    withFrozenCallStack $
646      unless x $ error s
647
648shouldExist :: MonadIO m => WithCallStack (FilePath -> m ())
649shouldExist path =
650    withFrozenCallStack $
651    liftIO $ doesFileExist path >>= assertBool (path ++ " should exist")
652
653shouldNotExist :: MonadIO m => WithCallStack (FilePath -> m ())
654shouldNotExist path =
655    withFrozenCallStack $
656    liftIO $ doesFileExist path >>= assertBool (path ++ " should exist") . not
657
658assertRegex :: MonadIO m => String -> String -> Result -> m ()
659assertRegex msg regex r =
660    withFrozenCallStack $
661    let out = resultOutput r
662    in assertBool (msg ++ ",\nactual output:\n" ++ out)
663       (out =~ regex)
664
665fails :: TestM a -> TestM a
666fails = withReaderT (\env -> env { testShouldFail = not (testShouldFail env) })
667
668defaultRecordMode :: RecordMode -> TestM a -> TestM a
669defaultRecordMode mode = withReaderT (\env -> env {
670    testRecordDefaultMode = mode
671    })
672
673recordMode :: RecordMode -> TestM a -> TestM a
674recordMode mode = withReaderT (\env -> env {
675    testRecordUserMode = Just mode
676    })
677
678recordNormalizer :: (String -> String) -> TestM a -> TestM a
679recordNormalizer f =
680    withReaderT (\env -> env { testRecordNormalizer = testRecordNormalizer env . f })
681
682assertOutputContains :: MonadIO m => WithCallStack (String -> Result -> m ())
683assertOutputContains needle result =
684    withFrozenCallStack $
685    unless (needle `isInfixOf` (concatOutput output)) $
686    assertFailure $ " expected: " ++ needle
687  where output = resultOutput result
688
689assertOutputDoesNotContain :: MonadIO m => WithCallStack (String -> Result -> m ())
690assertOutputDoesNotContain needle result =
691    withFrozenCallStack $
692    when (needle `isInfixOf` (concatOutput output)) $
693    assertFailure $ "unexpected: " ++ needle
694  where output = resultOutput result
695
696assertFindInFile :: MonadIO m => WithCallStack (String -> FilePath -> m ())
697assertFindInFile needle path =
698    withFrozenCallStack $
699    liftIO $ withFileContents path
700                 (\contents ->
701                  unless (needle `isInfixOf` contents)
702                         (assertFailure ("expected: " ++ needle ++ "\n" ++
703                                         " in file: " ++ path)))
704
705assertFileDoesContain :: MonadIO m => WithCallStack (FilePath -> String -> m ())
706assertFileDoesContain path needle =
707    withFrozenCallStack $
708    liftIO $ withFileContents path
709                 (\contents ->
710                  unless (needle `isInfixOf` contents)
711                         (assertFailure ("expected: " ++ needle ++ "\n" ++
712                                         " in file: " ++ path)))
713
714assertFileDoesNotContain :: MonadIO m => WithCallStack (FilePath -> String -> m ())
715assertFileDoesNotContain path needle =
716    withFrozenCallStack $
717    liftIO $ withFileContents path
718                 (\contents ->
719                  when (needle `isInfixOf` contents)
720                       (assertFailure ("expected: " ++ needle ++ "\n" ++
721                                       " in file: " ++ path)))
722
723-- | Replace line breaks with spaces, correctly handling "\r\n".
724concatOutput :: String -> String
725concatOutput = unwords . lines . filter ((/=) '\r')
726
727------------------------------------------------------------------------
728-- * Skipping tests
729
730hasSharedLibraries  :: TestM Bool
731hasSharedLibraries = do
732    shared_libs_were_removed <- ghcVersionIs (>= mkVersion [7,8])
733    return (not (buildOS == Windows && shared_libs_were_removed))
734
735hasProfiledLibraries :: TestM Bool
736hasProfiledLibraries = do
737    env <- getTestEnv
738    ghc_path <- programPathM ghcProgram
739    let prof_test_hs = testWorkDir env </> "Prof.hs"
740    liftIO $ writeFile prof_test_hs "module Prof where"
741    r <- liftIO $ run (testVerbosity env) (Just (testCurrentDir env))
742                      (testEnvironment env) ghc_path ["-prof", "-c", prof_test_hs]
743                      Nothing
744    return (resultExitCode r == ExitSuccess)
745
746-- | Check if the GHC that is used for compiling package tests has
747-- a shared library of the cabal library under test in its database.
748--
749-- An example where this is needed is if you want to dynamically link
750-- detailed-0.9 test suites, since those depend on the Cabal library unde rtest.
751hasCabalShared :: TestM Bool
752hasCabalShared = do
753  env <- getTestEnv
754  return (testHaveCabalShared env)
755
756ghcVersionIs :: WithCallStack ((Version -> Bool) -> TestM Bool)
757ghcVersionIs f = do
758    ghc_program <- requireProgramM ghcProgram
759    case programVersion ghc_program of
760        Nothing -> error $ "ghcVersionIs: no ghc version for "
761                        ++ show (programLocation ghc_program)
762        Just v -> return (f v)
763
764isWindows :: TestM Bool
765isWindows = return (buildOS == Windows)
766
767isOSX :: TestM Bool
768isOSX = return (buildOS == OSX)
769
770isLinux :: TestM Bool
771isLinux = return (buildOS == Linux)
772
773getOpenFilesLimit :: TestM (Maybe Integer)
774#ifdef mingw32_HOST_OS
775-- No MS-specified limit, was determined experimentally on Windows 10 Pro x64,
776-- matches other online reports from other versions of Windows.
777getOpenFilesLimit = return (Just 2048)
778#else
779getOpenFilesLimit = liftIO $ do
780    ResourceLimits { softLimit } <- getResourceLimit ResourceOpenFiles
781    case softLimit of
782        ResourceLimit n | n >= 0 && n <= 4096 -> return (Just n)
783        _                                     -> return Nothing
784#endif
785
786hasCabalForGhc :: TestM Bool
787hasCabalForGhc = do
788    env <- getTestEnv
789    ghc_program <- requireProgramM ghcProgram
790    (runner_ghc_program, _) <- liftIO $ requireProgram
791        (testVerbosity env)
792        ghcProgram
793        (runnerProgramDb (testScriptEnv env))
794
795    -- TODO: I guess, to be more robust what we should check for
796    -- specifically is that the Cabal library we want to use
797    -- will be picked up by the package db stack of ghc-program
798
799    -- liftIO $ putStrLn $ "ghc_program:        " ++ show ghc_program
800    -- liftIO $ putStrLn $ "runner_ghc_program: " ++ show runner_ghc_program
801
802    return (programPath ghc_program == programPath runner_ghc_program)
803
804-- | If you want to use a Custom setup with new-build, it needs to
805-- be 1.20 or later.  Ordinarily, Cabal can go off and build a
806-- sufficiently recent Cabal if necessary, but in our test suite,
807-- by default, we try to avoid doing so (since that involves a
808-- rather lengthy build process), instead using the boot Cabal if
809-- possible.  But some GHCs don't have a recent enough boot Cabal!
810-- You'll want to exclude them in that case.
811--
812hasNewBuildCompatBootCabal :: TestM Bool
813hasNewBuildCompatBootCabal = ghcVersionIs (>= mkVersion [7,9])
814
815------------------------------------------------------------------------
816-- * Broken tests
817
818expectBroken :: Int -> TestM a -> TestM ()
819expectBroken ticket m = do
820    env <- getTestEnv
821    liftIO . withAsync (runReaderT m env) $ \a -> do
822        r <- waitCatch a
823        case r of
824            Left e  -> do
825                putStrLn $ "This test is known broken, see #" ++ show ticket ++ ":"
826                print e
827                runReaderT expectedBroken env
828            Right _ -> do
829                runReaderT unexpectedSuccess env
830
831expectBrokenIf :: Bool -> Int -> TestM a -> TestM ()
832expectBrokenIf False _ m = void $ m
833expectBrokenIf True ticket m = expectBroken ticket m
834
835expectBrokenUnless :: Bool -> Int -> TestM a -> TestM ()
836expectBrokenUnless b = expectBrokenIf (not b)
837
838------------------------------------------------------------------------
839-- * Miscellaneous
840
841git :: String -> [String] -> TestM ()
842git cmd args = void $ git' cmd args
843
844git' :: String -> [String] -> TestM Result
845git' cmd args = do
846    recordHeader ["git", cmd]
847    runProgramM gitProgram (cmd : args) Nothing
848
849gcc :: [String] -> TestM ()
850gcc args = void $ gcc' args
851
852gcc' :: [String] -> TestM Result
853gcc' args = do
854    recordHeader ["gcc"]
855    runProgramM gccProgram args Nothing
856
857ghc :: [String] -> TestM ()
858ghc args = void $ ghc' args
859
860ghc' :: [String] -> TestM Result
861ghc' args = do
862    recordHeader ["ghc"]
863    runProgramM ghcProgram args Nothing
864
865-- | If a test needs to modify or write out source files, it's
866-- necessary to make a hermetic copy of the source files to operate
867-- on.  This function arranges for this to be done.
868--
869-- This requires the test repository to be a Git checkout, because
870-- we use the Git metadata to figure out what files to copy into the
871-- hermetic copy.
872--
873-- Also see 'withSourceCopyDir'.
874withSourceCopy :: TestM a -> TestM a
875withSourceCopy m = do
876    env <- getTestEnv
877    let cwd  = testCurrentDir env
878        dest = testSourceCopyDir env
879    r <- git' "ls-files" ["--cached", "--modified"]
880    forM_ (lines (resultOutput r)) $ \f -> do
881        unless (isTestFile f) $ do
882            liftIO $ createDirectoryIfMissing True (takeDirectory (dest </> f))
883            liftIO $ copyFile (cwd </> f) (dest </> f)
884    withReaderT (\nenv -> nenv { testHaveSourceCopy = True }) m
885
886-- | If a test needs to modify or write out source files, it's
887-- necessary to make a hermetic copy of the source files to operate
888-- on.  This function arranges for this to be done in a subdirectory
889-- with a given name, so that tests that are sensitive to the path
890-- that they're running in (e.g., autoconf tests) can run.
891--
892-- This requires the test repository to be a Git checkout, because
893-- we use the Git metadata to figure out what files to copy into the
894-- hermetic copy.
895--
896-- Also see 'withSourceCopy'.
897withSourceCopyDir :: FilePath -> TestM a -> TestM a
898withSourceCopyDir dir =
899  withReaderT (\nenv -> nenv { testSourceCopyRelativeDir = dir }) . withSourceCopy
900
901-- | Look up the 'InstalledPackageId' of a package name.
902getIPID :: String -> TestM String
903getIPID pn = do
904    r <- ghcPkg' "field" ["--global", pn, "id"]
905    -- Don't choke on warnings from ghc-pkg
906    case mapMaybe (stripPrefix "id: ") (lines (resultOutput r)) of
907        -- ~/.cabal/store may contain multiple versions of single package
908        -- we pick first one. It should work
909        (x:_) -> return (takeWhile (not . Char.isSpace) x)
910        _     -> error $ "could not determine id of " ++ pn
911
912-- | Delay a sufficient period of time to permit file timestamp
913-- to be updated.
914delay :: TestM ()
915delay = do
916    env <- getTestEnv
917    is_old_ghc <- ghcVersionIs (< mkVersion [7,7])
918    -- For old versions of GHC, we only had second-level precision,
919    -- so we need to sleep a full second.  Newer versions use
920    -- millisecond level precision, so we only have to wait
921    -- the granularity of the underlying filesystem.
922    -- TODO: cite commit when GHC got better precision; this
923    -- version bound was empirically generated.
924    liftIO . threadDelay $
925        if is_old_ghc
926            then 1000000
927            else fromMaybe
928                    (error "Delay must be enclosed by withDelay")
929                    (testMtimeChangeDelay env)
930
931-- | Calibrate file modification time delay, if not
932-- already determined.
933withDelay :: TestM a -> TestM a
934withDelay m = do
935    env <- getTestEnv
936    case testMtimeChangeDelay env of
937        Nothing -> do
938            -- Figure out how long we need to delay for recompilation tests
939            (_, mtimeChange) <- liftIO $ calibrateMtimeChangeDelay
940            withReaderT (\nenv -> nenv { testMtimeChangeDelay = Just mtimeChange }) m
941        Just _ -> m
942
943-- | Create a symlink for the duration of the provided action. If the symlink
944-- already exists, it is deleted. Does not work on Windows.
945withSymlink :: FilePath -> FilePath -> TestM a -> TestM a
946#ifdef mingw32_HOST_OS
947withSymlink _oldpath _newpath _act =
948  error "PackageTests.PackageTester.withSymlink: does not work on Windows!"
949#else
950withSymlink oldpath newpath0 act = do
951  env <- getTestEnv
952  let newpath = testCurrentDir env </> newpath0
953  symlinkExists <- liftIO $ doesFileExist newpath
954  when symlinkExists $ liftIO $ removeFile newpath
955  bracket_ (liftIO $ createSymbolicLink oldpath newpath)
956           (liftIO $ removeFile newpath) act
957#endif
958
959writeSourceFile :: FilePath -> String -> TestM ()
960writeSourceFile fp s = do
961    requireHasSourceCopy
962    cwd <- fmap testCurrentDir getTestEnv
963    liftIO $ writeFile (cwd </> fp) s
964
965copySourceFileTo :: FilePath -> FilePath -> TestM ()
966copySourceFileTo src dest = do
967    requireHasSourceCopy
968    cwd <- fmap testCurrentDir getTestEnv
969    liftIO $ copyFile (cwd </> src) (cwd </> dest)
970
971requireHasSourceCopy :: TestM ()
972requireHasSourceCopy = do
973    env <- getTestEnv
974    unless (testHaveSourceCopy env) $ do
975        error "This operation requires a source copy; use withSourceCopy and 'git add' all test files"
976
977-- NB: Keep this synchronized with partitionTests
978isTestFile :: FilePath -> Bool
979isTestFile f =
980    case takeExtensions f of
981        ".test.hs"      -> True
982        ".multitest.hs" -> True
983        _               -> False
984
985-- | Work around issue #4515 (store paths exceeding the Windows path length
986-- limit) by creating a temporary directory for the new-build store. This
987-- function creates a directory immediately under the current drive on Windows.
988-- The directory must be passed to new- commands with --store-dir.
989withShorterPathForNewBuildStore :: (FilePath -> IO a) -> IO a
990withShorterPathForNewBuildStore test = do
991  tempDir <- if buildOS == Windows
992             then takeDrive `fmap` getCurrentDirectory
993             else getTemporaryDirectory
994  withTempDirectory normal tempDir "cabal-test-store" test
995