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