1-- | Running tests
2{-# LANGUAGE ScopedTypeVariables, ExistentialQuantification, RankNTypes,
3             FlexibleContexts, BangPatterns, CPP, DeriveDataTypeable #-}
4module Test.Tasty.Run
5  ( Status(..)
6  , StatusMap
7  , launchTestTree
8  , DependencyException(..)
9  ) where
10
11import qualified Data.IntMap as IntMap
12import qualified Data.Sequence as Seq
13import qualified Data.Foldable as F
14import Data.Maybe
15import Data.Graph (SCC(..), stronglyConnComp)
16import Data.Typeable
17import Control.Monad.State
18import Control.Monad.Writer
19import Control.Monad.Reader
20import Control.Concurrent
21import Control.Concurrent.STM
22import Control.Concurrent.Timeout (timeout)
23import Control.Concurrent.Async
24import Control.Exception as E
25import Control.Applicative
26import Control.Arrow
27import GHC.Conc (labelThread)
28import Prelude  -- Silence AMP and FTP import warnings
29
30import Test.Tasty.Core
31import Test.Tasty.Parallel
32import Test.Tasty.Patterns
33import Test.Tasty.Patterns.Types
34import Test.Tasty.Options
35import Test.Tasty.Options.Core
36import Test.Tasty.Runners.Reducers
37import Test.Tasty.Runners.Utils (timed, forceElements)
38import Test.Tasty.Providers.ConsoleFormat (noResultDetails)
39
40-- | Current status of a test
41data Status
42  = NotStarted
43    -- ^ test has not started running yet
44  | Executing Progress
45    -- ^ test is being run
46  | Done Result
47    -- ^ test finished with a given result
48  deriving Show
49
50-- | Mapping from test numbers (starting from 0) to their status variables.
51--
52-- This is what an ingredient uses to analyse and display progress, and to
53-- detect when tests finish.
54type StatusMap = IntMap.IntMap (TVar Status)
55
56data Resource r
57  = NotCreated
58  | BeingCreated
59  | FailedToCreate SomeException
60  | Created r
61  | BeingDestroyed
62  | Destroyed
63
64instance Show (Resource r) where
65  show r = case r of
66    NotCreated -> "NotCreated"
67    BeingCreated -> "BeingCreated"
68    FailedToCreate exn -> "FailedToCreate " ++ show exn
69    Created {} -> "Created"
70    BeingDestroyed -> "BeingDestroyed"
71    Destroyed -> "Destroyed"
72
73data Initializer
74  = forall res . Initializer
75      (IO res)
76      (TVar (Resource res))
77data Finalizer
78  = forall res . Finalizer
79      (res -> IO ())
80      (TVar (Resource res))
81      (TVar Int)
82
83-- | Execute a test taking care of resources
84executeTest
85  :: ((Progress -> IO ()) -> IO Result)
86    -- ^ the action to execute the test, which takes a progress callback as
87    -- a parameter
88  -> TVar Status -- ^ variable to write status to
89  -> Timeout -- ^ optional timeout to apply
90  -> Seq.Seq Initializer -- ^ initializers (to be executed in this order)
91  -> Seq.Seq Finalizer -- ^ finalizers (to be executed in this order)
92  -> IO ()
93executeTest action statusVar timeoutOpt inits fins = mask $ \restore -> do
94  resultOrExn <- try $ restore $ do
95    -- N.B. this can (re-)throw an exception. It's okay. By design, the
96    -- actual test will not be run, then. We still run all the
97    -- finalizers.
98    --
99    -- There's no point to transform these exceptions to something like
100    -- EitherT, because an async exception (cancellation) can strike
101    -- anyway.
102    initResources
103
104    -- If all initializers ran successfully, actually run the test.
105    -- We run it in a separate thread, so that the test's exception
106    -- handler doesn't interfere with our timeout.
107    withAsync (action yieldProgress) $ \asy -> do
108      labelThread (asyncThreadId asy) "tasty_test_execution_thread"
109      timed $ applyTimeout timeoutOpt $ do
110        r <- wait asy
111        -- Not only wait for the result to be returned, but make sure to
112        -- evalute it inside applyTimeout; see #280.
113        evaluate $
114          resultOutcome r `seq`
115          forceElements (resultDescription r) `seq`
116          forceElements (resultShortDescription r)
117        return r
118
119  -- no matter what, try to run each finalizer
120  mbExn <- destroyResources restore
121
122  atomically . writeTVar statusVar $ Done $
123    case resultOrExn <* maybe (Right ()) Left mbExn of
124      Left ex -> exceptionResult ex
125      Right (t,r) -> r { resultTime = t }
126
127  where
128    initResources :: IO ()
129    initResources =
130      F.forM_ inits $ \(Initializer doInit initVar) -> do
131        join $ atomically $ do
132          resStatus <- readTVar initVar
133          case resStatus of
134            NotCreated -> do
135              -- signal to others that we're taking care of the resource
136              -- initialization
137              writeTVar initVar BeingCreated
138              return $
139                (do
140                  res <- doInit
141                  atomically $ writeTVar initVar $ Created res
142                 ) `E.catch` \exn -> do
143                  atomically $ writeTVar initVar $ FailedToCreate exn
144                  throwIO exn
145            BeingCreated -> retry
146            Created {} -> return $ return ()
147            FailedToCreate exn -> return $ throwIO exn
148            -- If the resource is destroyed or being destroyed
149            -- while we're starting a test, the test suite is probably
150            -- shutting down. We are about to be killed.
151            -- (In fact we are probably killed already, so these cases are
152            -- unlikely to occur.)
153            -- In any case, the most sensible thing to do is to go to
154            -- sleep, awaiting our fate.
155            Destroyed      -> return $ sleepIndefinitely
156            BeingDestroyed -> return $ sleepIndefinitely
157
158    applyTimeout :: Timeout -> IO Result -> IO Result
159    applyTimeout NoTimeout a = a
160    applyTimeout (Timeout t tstr) a = do
161      let
162        timeoutResult =
163          Result
164            { resultOutcome = Failure $ TestTimedOut t
165            , resultDescription =
166                "Timed out after " ++ tstr
167            , resultShortDescription = "TIMEOUT"
168            , resultTime = fromIntegral t
169            , resultDetailsPrinter = noResultDetails
170            }
171      fromMaybe timeoutResult <$> timeout t a
172
173    -- destroyResources should not be interrupted by an exception
174    -- Here's how we ensure this:
175    --
176    -- * the finalizer is wrapped in 'try'
177    -- * async exceptions are masked by the caller
178    -- * we don't use any interruptible operations here (outside of 'try')
179    destroyResources :: (forall a . IO a -> IO a) -> IO (Maybe SomeException)
180    destroyResources restore = do
181      -- remember the first exception that occurred
182      liftM getFirst . execWriterT . getTraversal $
183        flip F.foldMap fins $ \fin@(Finalizer _ _ finishVar) ->
184          Traversal $ do
185            iAmLast <- liftIO $ atomically $ do
186              nUsers <- readTVar finishVar
187              let nUsers' = nUsers - 1
188              writeTVar finishVar nUsers'
189              return $ nUsers' == 0
190
191            mbExcn <- liftIO $
192              if iAmLast
193              then destroyResource restore fin
194              else return Nothing
195
196            tell $ First mbExcn
197
198    -- The callback
199    -- Since this is not used yet anyway, disable for now.
200    -- I'm not sure whether we should get rid of this altogether. For most
201    -- providers this is either difficult to implement or doesn't make
202    -- sense at all.
203    -- See also https://github.com/feuerbach/tasty/issues/33
204    yieldProgress _ = return ()
205
206type InitFinPair = (Seq.Seq Initializer, Seq.Seq Finalizer)
207
208-- | Dependencies of a test
209type Deps = [(DependencyType, Expr)]
210
211-- | Traversal type used in 'createTestActions'
212type Tr = Traversal
213        (WriterT ([(InitFinPair -> IO (), (TVar Status, Path, Deps))], Seq.Seq Finalizer)
214        (ReaderT (Path, Deps)
215        IO))
216
217-- | Exceptions related to dependencies between tests.
218data DependencyException
219  = DependencyLoop
220    -- ^ Test dependencies form a loop. In other words, test A cannot start
221    -- until test B finishes, and test B cannot start until test
222    -- A finishes.
223  deriving (Typeable)
224
225instance Show DependencyException where
226  show DependencyLoop = "Test dependencies form a loop."
227
228instance Exception DependencyException
229
230-- | Turn a test tree into a list of actions to run tests coupled with
231-- variables to watch them.
232createTestActions
233  :: OptionSet
234  -> TestTree
235  -> IO ([(Action, TVar Status)], Seq.Seq Finalizer)
236createTestActions opts0 tree = do
237  let
238    traversal :: Tr
239    traversal =
240      foldTestTree
241        (trivialFold :: TreeFold Tr)
242          { foldSingle = runSingleTest
243          , foldResource = addInitAndRelease
244          , foldGroup = \_opts name (Traversal a) ->
245              Traversal $ local (first (Seq.|> name)) a
246          , foldAfter = \_opts deptype pat (Traversal a) ->
247              Traversal $ local (second ((deptype, pat) :)) a
248          }
249        opts0 tree
250  (tests, fins) <- unwrap (mempty :: Path) (mempty :: Deps) traversal
251  let
252    mb_tests :: Maybe [(Action, TVar Status)]
253    mb_tests = resolveDeps $ map
254      (\(act, testInfo) ->
255        (act (Seq.empty, Seq.empty), testInfo))
256      tests
257  case mb_tests of
258    Just tests' -> return (tests', fins)
259    Nothing -> throwIO DependencyLoop
260
261  where
262    runSingleTest :: IsTest t => OptionSet -> TestName -> t -> Tr
263    runSingleTest opts name test = Traversal $ do
264      statusVar <- liftIO $ atomically $ newTVar NotStarted
265      (parentPath, deps) <- ask
266      let
267        path = parentPath Seq.|> name
268        act (inits, fins) =
269          executeTest (run opts test) statusVar (lookupOption opts) inits fins
270      tell ([(act, (statusVar, path, deps))], mempty)
271    addInitAndRelease :: OptionSet -> ResourceSpec a -> (IO a -> Tr) -> Tr
272    addInitAndRelease _opts (ResourceSpec doInit doRelease) a = wrap $ \path deps -> do
273      initVar <- atomically $ newTVar NotCreated
274      (tests, fins) <- unwrap path deps $ a (getResource initVar)
275      let ntests = length tests
276      finishVar <- atomically $ newTVar ntests
277      let
278        ini = Initializer doInit initVar
279        fin = Finalizer doRelease initVar finishVar
280        tests' = map (first $ local $ (Seq.|> ini) *** (fin Seq.<|)) tests
281      return (tests', fins Seq.|> fin)
282    wrap
283      :: (Path ->
284          Deps ->
285          IO ([(InitFinPair -> IO (), (TVar Status, Path, Deps))], Seq.Seq Finalizer))
286      -> Tr
287    wrap = Traversal . WriterT . fmap ((,) ()) . ReaderT . uncurry
288    unwrap
289      :: Path
290      -> Deps
291      -> Tr
292      -> IO ([(InitFinPair -> IO (), (TVar Status, Path, Deps))], Seq.Seq Finalizer)
293    unwrap path deps = flip runReaderT (path, deps) . execWriterT . getTraversal
294
295-- | Take care of the dependencies.
296--
297-- Return 'Nothing' if there is a dependency cycle.
298resolveDeps :: [(IO (), (TVar Status, Path, Deps))] -> Maybe [(Action, TVar Status)]
299resolveDeps tests = checkCycles $ do
300  (run_test, (statusVar, path0, deps)) <- tests
301  let
302    -- Note: Duplicate dependencies may arise if the same test name matches
303    -- multiple patterns. It's not clear that removing them is worth the
304    -- trouble; might consider this in the future.
305    deps' :: [(DependencyType, TVar Status, Path)]
306    deps' = do
307      (deptype, depexpr) <- deps
308      (_, (statusVar1, path, _)) <- tests
309      guard $ exprMatches depexpr path
310      return (deptype, statusVar1, path)
311
312    getStatus :: STM ActionStatus
313    getStatus = foldr
314      (\(deptype, statusvar, _) k -> do
315        status <- readTVar statusvar
316        case status of
317          Done result
318            | deptype == AllFinish || resultSuccessful result -> k
319            | otherwise -> return ActionSkip
320          _ -> return ActionWait
321      )
322      (return ActionReady)
323      deps'
324  let
325    dep_paths = map (\(_, _, path) -> path) deps'
326    action = Action
327      { actionStatus = getStatus
328      , actionRun = run_test
329      , actionSkip = writeTVar statusVar $ Done $ Result
330          -- See Note [Skipped tests]
331          { resultOutcome = Failure TestDepFailed
332          , resultDescription = ""
333          , resultShortDescription = "SKIP"
334          , resultTime = 0
335          , resultDetailsPrinter = noResultDetails
336          }
337      }
338  return ((action, statusVar), (path0, dep_paths))
339
340checkCycles :: Ord b => [(a, (b, [b]))] -> Maybe [a]
341checkCycles tests = do
342  let
343    result = fst <$> tests
344    graph = [ ((), v, vs) | (v, vs) <- snd <$> tests ]
345    sccs = stronglyConnComp graph
346    not_cyclic = all (\scc -> case scc of
347        AcyclicSCC{} -> True
348        CyclicSCC{}  -> False)
349      sccs
350  guard not_cyclic
351  return result
352
353-- | Used to create the IO action which is passed in a WithResource node
354getResource :: TVar (Resource r) -> IO r
355getResource var =
356  atomically $ do
357    rState <- readTVar var
358    case rState of
359      Created r -> return r
360      Destroyed -> throwSTM UseOutsideOfTest
361      _ -> throwSTM $ unexpectedState "getResource" rState
362
363-- | Run a resource finalizer.
364--
365-- This function is called from two different places:
366--
367-- 1. A test thread, which is the last one to use the resource.
368-- 2. The main thread, if an exception (e.g. Ctrl-C) is received.
369--
370-- Therefore, it is possible that this function is called multiple
371-- times concurrently on the same finalizer.
372--
373-- This function should be run with async exceptions masked,
374-- and the restore function should be passed as an argument.
375destroyResource :: (forall a . IO a -> IO a) -> Finalizer -> IO (Maybe SomeException)
376destroyResource restore (Finalizer doRelease stateVar _) = join . atomically $ do
377  rState <- readTVar stateVar
378  case rState of
379    Created res -> do
380      writeTVar stateVar BeingDestroyed
381      return $
382        (either Just (const Nothing)
383          <$> try (restore $ doRelease res))
384          <* atomically (writeTVar stateVar Destroyed)
385    BeingCreated   -> retry
386    -- If the resource is being destroyed, wait until it is destroyed.
387    -- This is so that we don't start destroying the next resource out of
388    -- order.
389    BeingDestroyed -> retry
390    NotCreated -> do
391      -- prevent the resource from being created by a competing thread
392      writeTVar stateVar Destroyed
393      return $ return Nothing
394    FailedToCreate {} -> return $ return Nothing
395    Destroyed         -> return $ return Nothing
396
397-- | Start running the tests (in background, in parallel) and pass control
398-- to the callback.
399--
400-- Once the callback returns, stop running the tests.
401--
402-- The number of test running threads is determined by the 'NumThreads'
403-- option.
404launchTestTree
405  :: OptionSet
406  -> TestTree
407  -> (StatusMap -> IO (Time -> IO a))
408    -- ^ A callback. First, it receives the 'StatusMap' through which it
409    -- can observe the execution of tests in real time. Typically (but not
410    -- necessarily), it waits until all the tests are finished.
411    --
412    -- After this callback returns, the test-running threads (if any) are
413    -- terminated and all resources acquired by tests are released.
414    --
415    -- The callback must return another callback (of type @'Time' -> 'IO'
416    -- a@) which additionally can report and/or record the total time
417    -- taken by the test suite. This time includes the time taken to run
418    -- all resource initializers and finalizers, which is why it is more
419    -- accurate than what could be measured from inside the first callback.
420  -> IO a
421launchTestTree opts tree k0 = do
422  (testActions, fins) <- createTestActions opts tree
423  let NumThreads numTheads = lookupOption opts
424  (t,k1) <- timed $ do
425     abortTests <- runInParallel numTheads (fst <$> testActions)
426     (do let smap = IntMap.fromList $ zip [0..] (snd <$> testActions)
427         k0 smap)
428      `finallyRestore` \restore -> do
429         -- Tell all running tests to wrap up.
430         abortTests
431         -- Destroy all allocated resources in the case they didn't get
432         -- destroyed by their tests. (See #75.)
433         F.mapM_ (destroyResource restore) fins
434         -- Wait until all resources are destroyed. (Specifically, those
435         -- that were being destroyed by their tests, not those that were
436         -- destroyed by destroyResource above.)
437         restore $ waitForResources fins
438  k1 t
439  where
440    alive :: Resource r -> Bool
441    alive r = case r of
442      NotCreated -> False
443      BeingCreated -> True
444      FailedToCreate {} -> False
445      Created {} -> True
446      BeingDestroyed -> True
447      Destroyed -> False
448
449    waitForResources fins = atomically $
450      F.forM_ fins $ \(Finalizer _ rvar _) -> do
451        res <- readTVar rvar
452        check $ not $ alive res
453
454unexpectedState :: String -> Resource r -> SomeException
455unexpectedState where_ r = toException $ UnexpectedState where_ (show r)
456
457sleepIndefinitely :: IO ()
458sleepIndefinitely = forever $ threadDelay (10^(7::Int))
459
460-- | Like 'finally' (which also masks its finalizers), but pass the restore
461-- action to the finalizer.
462finallyRestore
463  :: IO a
464    -- ^ computation to run first
465  -> ((forall c . IO c -> IO c) -> IO b)
466    -- ^ computation to run afterward (even if an exception was raised)
467  -> IO a
468    -- ^ returns the value from the first computation
469a `finallyRestore` sequel =
470  mask $ \restore -> do
471    r <- restore a `onException` sequel restore
472    _ <- sequel restore
473    return r
474