1{-# LANGUAGE DeriveDataTypeable #-}
2{-# LANGUAGE RecordWildCards #-}
3{-# LANGUAGE ScopedTypeVariables #-}
8import qualified Control.Concurrent as C
9import Control.Exception (Exception, SomeException, handle, throwIO, IOException, try)
10import Control.Monad (replicateM, unless, when, forM_)
11import qualified Data.Map as Map
12import qualified Data.Set as Set
13import qualified Control.Monad.Trans.Writer as W
14import Distribution.Package (Dependency (Dependency))
15import Data.Version (parseVersion, Version (Version))
16import Data.Typeable (Typeable)
19import System.Directory (copyFile, createDirectory,
20 createDirectoryIfMissing, doesFileExist, findExecutable,
21 getDirectoryContents, removeFile,
22 renameDirectory, canonicalizePath)
23import System.Exit (ExitCode (ExitSuccess))
24import System.FilePath ((<.>), (</>), takeDirectory)
25import System.IO (IOMode (WriteMode, AppendMode),
27import System.Process (readProcess, runProcess, waitForProcess, createProcess, proc, cwd)
28import Text.ParserCombinators.ReadP (readP_to_S)
29import Data.IORef (IORef, readIORef, atomicModifyIORef, newIORef)
30import qualified Data.ByteString.Lazy as L
31import qualified Data.ByteString.Lazy.Char8 as L8
32import qualified Distribution.PackageDescription as PD
33import Distribution.PackageDescription.Parse (ParseResult (ParseOk),
34 parsePackageDescription)
36runTestSuites :: BuildSettings -> BuildPlan -> IO ()
37runTestSuites settings' bp = do
38 settings <- fixBuildSettings settings'
39 let selected' = Map.filterWithKey notSkipped $ bpPackages bp
40 let testdir = "runtests"
44 createDirectory testdir
45 createDirectory docdir
47 putStrLn "Determining package dependencies"
48 selected <- mapM (addDependencies settings (Map.keysSet selected') testdir)
49 $ Map.toList selected'
50 putStrLn "Running test suites"
52 copyBuiltInHaddocks docdir
54 cabalVersion <- getCabalVersion
55 haddockFilesRef <- newIORef []
57 (testWorkerThreads settings)
58 (runTestSuite cabalVersion settings testdir docdir bp haddockFilesRef)
62 unless allPass $ error $ "There were failures, please see the logs in " ++ testdir
64 notSkipped p _ = p `Set.notMember` bpSkippedTests bp
66addDependencies :: BuildSettings
67 -> Set PackageName -- ^ all packages to be installed
68 -> FilePath -- ^ testdir
69 -> (PackageName, SelectedPackageInfo)
70 -> IO (PackageName, Set PackageName, SelectedPackageInfo)
71addDependencies settings allPackages testdir (packageName, spi) = do
72 package' <- replaceTarball (tarballDir settings) package
73 deps <- handle (\e -> print (e :: IOException) >> return Set.empty)
74 $ getDeps allPackages testdir packageName package package'
75 return (packageName, deps, spi)
77 package = packageVersionString (packageName, spiVersion spi)
79getDeps :: Set PackageName -- ^ all packages to be installed
80 -> FilePath -> PackageName -> String -> String -> IO (Set PackageName)
81getDeps allPackages testdir (PackageName name) nameVer loc = do
82 (Nothing, Nothing, Nothing, ph) <- createProcess
83 (proc "cabal" ["unpack", loc, "--verbose=0"]) { cwd = Just testdir }
84 ec <- waitForProcess ph
85 unless (ec == ExitSuccess) $ error $ "Unable to unpack: " ++ loc
86 lbs <- L.readFile $ testdir </> nameVer </> name <.> "cabal"
87 case parsePackageDescription $ L8.unpack lbs of
88 ParseOk _ gpd -> return $ Set.intersection allPackages $ allLibraryDeps gpd
91allLibraryDeps :: PD.GenericPackageDescription -> Set PackageName
93 maybe Set.empty (W.execWriter . goTree) . PD.condLibrary
96 mapM_ goDep $ PD.condTreeConstraints tree
97 forM_ (PD.condTreeComponents tree) $ \(_, y, z) -> do
99 maybe (return ()) goTree z
101 goDep (Dependency pn _) = W.tell $ Set.singleton pn
103getCabalVersion :: IO CabalVersion
105 output <- readProcess "cabal" ["--numeric-version"] ""
106 case filter (null . snd) $ readP_to_S parseVersion $ filter notCRLF output of
107 (Version (x:y:_) _, _):_ -> return $ CabalVersion x y
108 _ -> error $ "Invalid cabal version: " ++ show output
114parFoldM :: Int -- ^ number of threads
115 -> ((PackageName, payload) -> IO c)
118 -> [(PackageName, Set PackageName, payload)]
120parFoldM threadCount0 f g a0 bs0 = do
123 signal <- C.newEmptyMVar
124 completed <- newIORef Set.empty
125 tids <- replicateM threadCount0 $ C.forkIO $ worker completed ma mbs signal
126 wait threadCount0 signal tids
128 unrun <- C.takeMVar mbs
129 when (not $ null unrun) $
130 error $ "The following tests were not run: " ++ unwords
131 [x | (PackageName x, _, _) <- unrun]
134 worker completedRef ma mbs signal =
136 (C.putMVar signal . Just)
137 (loop >> C.putMVar signal Nothing)
140 mb <- C.modifyMVar mbs $ \bs -> do
141 completed <- readIORef completedRef
142 return $ case findReady completed bs of
143 -- There's a workload ready with no deps
144 Just (b, bs') -> (bs', Just b)
145 -- No workload with no deps
146 Nothing -> (bs, Nothing)
149 Just (name, _, payload) -> do
150 c <- f (name, payload)
151 C.modifyMVar_ ma $ \a -> return $! g a c
152 atomicModifyIORef completedRef $ \s -> (Set.insert name s, ())
154 wait threadCount signal tids
155 | threadCount == 0 = return ()
157 me <- C.takeMVar signal
159 Nothing -> wait (threadCount - 1) signal tids
161 mapM_ C.killThread tids
162 throwIO (e :: SomeException)
164-- | Find a workload whose dependencies have been met.
166 => Set key -- ^ workloads already complete
167 -> [(key, Set key, value)]
168 -> Maybe ((key, Set key, value), [(key, Set key, value)])
173 loop front (x@(_, deps, _):xs)
174 | Set.null $ Set.difference deps completed = Just (x, front xs)
175 | otherwise = loop (front . (x:)) xs
177data TestException = TestException
178 deriving (Show, Typeable)
179instance Exception TestException
181data CabalVersion = CabalVersion Int Int
182 deriving (Eq, Ord, Show)
184runTestSuite :: CabalVersion
186 -> FilePath -- ^ testdir
187 -> FilePath -- ^ docdir
189 -> IORef [(String, FilePath)] -- ^ .haddock files
190 -> (PackageName, SelectedPackageInfo)
192runTestSuite cabalVersion settings testdir docdir
193 bp haddockFilesRef (packageName, SelectedPackageInfo {..}) = do
194 -- Set up a new environment that includes the sandboxed bin folder in PATH.
195 env' <- getModifiedEnv settings
196 let menv = Just $ addSandbox env'
197 addSandbox = (("HASKELL_PACKAGE_SANDBOX", packageDir settings):)
199 let run cmd args wdir handle' = do
200 ph <- runProcess cmd args (Just wdir) menv Nothing (Just handle') (Just handle')
201 ec <- waitForProcess ph
202 unless (ec == ExitSuccess) $ throwIO TestException
204 passed <- handle (\TestException -> return False) $ do
205 case cabalFileDir settings of
208 let PackageName name = packageName
209 basename = name ++ ".cabal"
210 src = dir </> basename
211 dst = cfd </> basename
212 createDirectoryIfMissing True cfd
214 getHandle WriteMode $ run "cabal" (addCabalArgs settings BSTest ["configure", "--enable-tests"]) dir
216 -- Try building docs first in case tests have an expected failure.
217 when (buildDocs settings) $ do
218 hfs <- readIORef haddockFilesRef
219 let hfsOpts = flip map hfs $ \(pkgVer, hf) -> concat
220 [ "--haddock-options=--read-interface="
226 getHandle AppendMode $ run "cabal"
228 : "--hyperlink-source"
231 : "--html-location=../$pkg-$version/"
233 let PackageName packageName' = packageName
234 handle (\(_ :: IOException) -> return ()) $ renameDirectory
235 (dir </> "dist" </> "doc" </> "html" </> packageName')
238 enewPath <- try $ canonicalizePath $ docdir </> package </> packageName' <.> "haddock"
239 case enewPath :: Either IOException FilePath of
240 Left _ -> return () -- print e
241 Right newPath -> atomicModifyIORef haddockFilesRef $ \hfs'
242 -> ((package, newPath) : hfs', ())
244 when spiHasTests $ do
245 getHandle AppendMode $ run "cabal" ["build"] dir
246 getHandle AppendMode $ run "cabal" (concat
248 , if cabalVersion >= CabalVersion 1 20
249 then ["--show-details=streaming"] -- FIXME temporary workaround for https://github.com/haskell/cabal/issues/1810
253 let expectedFailure = packageName `Set.member` bpExpectedFailures bp
257 when expectedFailure $ putStrLn $ " " ++ package ++ " passed, but I didn't think it would."
258 else unless expectedFailure $ putStrLn $ concat
259 [ "Test suite failed: "
262 , unMaintainer spiMaintainer
263 , githubMentions spiGithubUser
267 return $! passed || expectedFailure
269 logfile = testdir </> package <.> "log"
270 dir = testdir </> package
271 getHandle mode = withBinaryFile logfile mode
272 package = packageVersionString (packageName, spiVersion)
274copyBuiltInHaddocks docdir = do
275 Just ghc <- findExecutable "ghc"
276 copyTree (takeDirectory ghc </> "../share/doc/ghc/html/libraries") docdir
278 copyTree src dest = do
279 entries <- fmap (filter (\s -> s /= "." && s /= ".."))
280 $ getDirectoryContents src
281 forM_ entries $ \entry -> do
282 let src' = src </> entry
283 dest' = dest </> entry
284 isFile <- doesFileExist src'
286 then copyFile src' dest'
288 createDirectory dest'
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list