tasty-silver 3.0.1.0 → 3.0.2.0
raw patch · 5 files changed
+605/−99 lines, 5 filesdep +ansi-terminaldep +arraydep +stmPVP ok
version bump matches the API change (PVP)
Dependencies added: ansi-terminal, array, stm
API changes (from Hackage documentation)
+ Test.Tasty.Silver.Interactive: instance Eq HideSuccesses
+ Test.Tasty.Silver.Interactive: instance Eq UseColor
+ Test.Tasty.Silver.Interactive: instance IsOption HideSuccesses
+ Test.Tasty.Silver.Interactive: instance IsOption UseColor
+ Test.Tasty.Silver.Interactive: instance Monoid FailureStatus
+ Test.Tasty.Silver.Interactive: instance Monoid Statistics
+ Test.Tasty.Silver.Interactive: instance Monoid TestOutput
+ Test.Tasty.Silver.Interactive: instance Ord HideSuccesses
+ Test.Tasty.Silver.Interactive: instance Ord UseColor
+ Test.Tasty.Silver.Interactive: instance Ord a => Monoid (Maximum a)
+ Test.Tasty.Silver.Interactive: instance Typeable HideSuccesses
+ Test.Tasty.Silver.Interactive: instance Typeable UseColor
+ Test.Tasty.Silver.Interactive.Run: instance IsTest t => IsTest (CustomTestExec t)
+ Test.Tasty.Silver.Interactive.Run: instance Typeable CustomTestExec
+ Test.Tasty.Silver.Interactive.Run: wrapRunTest :: (forall t. IsTest t => TestName -> OptionSet -> t -> (Progress -> IO ()) -> IO Result) -> TestTree -> TestTree
Files
- Test/Tasty/Silver.hs +1/−3
- Test/Tasty/Silver/Interactive.hs +516/−71
- Test/Tasty/Silver/Interactive/Run.hs +31/−0
- Test/Tasty/Silver/Internal.hs +42/−14
- tasty-silver.cabal +15/−11
Test/Tasty/Silver.hs view
@@ -66,8 +66,6 @@ import qualified Data.Set as Set import Control.Monad --- trick to avoid an explicit dependency on transformers-import Control.Monad.Error (liftIO) import Data.Text.Encoding @@ -115,7 +113,7 @@ goldenTest1 name (maybe Nothing (Just . decodeUtf8) <$> readFileMaybe ref)- (liftIO (toTxt <$> act))+ (toTxt <$> act) textLikeDiff textLikeShow (upd . BL.fromStrict . encodeUtf8)
Test/Tasty/Silver/Interactive.hs view
@@ -1,4 +1,8 @@ {-# LANGUAGE GeneralizedNewtypeDeriving, PatternGuards, DeriveDataTypeable #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE ImplicitParams #-}+{-# LANGUAGE BangPatterns #-} -- | Golden test management, interactive mode. Runs the tests, and asks -- the user how to proceed in case of failure or missing golden standard. module Test.Tasty.Silver.Interactive@@ -15,16 +19,25 @@ ) where +import Prelude hiding (fail) import Test.Tasty hiding (defaultMain) import Test.Tasty.Runners import Test.Tasty.Options import Test.Tasty.Silver.Internal+import Test.Tasty.Silver.Interactive.Run import Data.Typeable import Data.Tagged-import Data.Proxy import Data.Maybe-import Control.Monad.Cont-import Control.Monad.State+import Data.Monoid+import Data.Foldable (foldMap)+import Data.Char+import qualified Data.IntMap as IntMap+import Control.Monad.State hiding (fail)+import Control.Monad.STM+import Control.Monad.Reader hiding (fail)+import Control.Monad.Identity hiding (fail)+import Control.Concurrent.STM.TVar+import Control.Exception import Text.Printf import qualified Data.Text as T import Data.Text.Encoding@@ -36,6 +49,9 @@ import System.IO import System.IO.Temp import System.FilePath+import Test.Tasty.Providers+import qualified Data.Map as M+import System.Console.ANSI -- | Like @defaultMain@ from the main tasty package, but also includes the -- golden test management capabilities.@@ -49,92 +65,85 @@ parseValue = fmap Interactive . safeRead optionName = return "interactive" optionHelp = return "Run tests in interactive mode."- optionCLParser =- fmap Interactive $- switch- ( long (untag (optionName :: Tagged Interactive String))- <> help (untag (optionHelp :: Tagged Interactive String))- )+ optionCLParser = flagCLParser (Just 'i') (Interactive True) +data ResultStatus = RPass | RFail | RInteract GoldenResultI +type GoldenStatus = GoldenResultI++type GoldenStatusMap = TVar (M.Map TestName GoldenStatus)+ interactiveTests :: Ingredient interactiveTests = TestManager [Option (Proxy :: Proxy Interactive)] $ \opts tree ->- case lookupOption opts of- Interactive False -> Nothing- Interactive True -> Just $- runTestsInteractive opts tree+ case lookupOption opts of+ Interactive False -> Nothing+ Interactive True -> Just $+ runTestsInteractive opts tree --- | Get the list of all golden tests in a given test tree-getGoldenTests :: OptionSet -> TestTree -> [(TestName, Golden)]-getGoldenTests =- foldTestTree- trivialFold { foldSingle = \_ name t -> fmap ((,) name) $ maybeToList $ cast t } --- | Run in interactive mode (only tested on linux)+runSingleTest :: IsTest t => GoldenStatusMap -> TestName -> OptionSet -> t -> (Progress -> IO ()) -> IO Result+runSingleTest gs n opts t cb = do+ case (cast t :: Maybe Golden) of+ Nothing -> run opts t cb+ Just g -> do++ (r, gr) <- runGolden g++ -- we may be in a different thread here than the main ui.+ -- force evaluation of actual value here, as we have to evaluate it before+ -- leaving this test.+ gr' <- forceGoldenResult gr+ atomically $ modifyTVar gs (M.insert n gr')+ return r+++-- | A simple console UI runTestsInteractive :: OptionSet -> TestTree -> IO Bool runTestsInteractive opts tests = do- let gs = getGoldenTests opts tests+ gmap <- newTVarIO M.empty+ let tests' = wrapRunTest (runSingleTest gmap) tests - liftIO $ hSetBuffering stdout NoBuffering+ r <- launchTestTree opts tests' $ \smap ->+ do+ isTerm <- hSupportsANSI stdout - (nFail, nReject) <- flip execStateT (0, 0) $ forM_ gs runTest+ (\k -> if isTerm+ then (do hideCursor; k) `finally` showCursor+ else k) $ do - -- warn when there were problems- when (nFail > 0 || nReject > 0) (do- _ <- printf "NOTE: %d tests threw exceptions!\n" nFail- printf "NOTE: %d tests were rejected!\n" nReject- )+ hSetBuffering stdout NoBuffering - -- is everything ok?- return (nFail == 0 && nReject == 0)- where runTest :: (TestName, Golden) -> StateT (Integer, Integer) IO ()- runTest (n, (Golden getGolden getActual cmp shw upd)) = do- -- execute test- liftIO $ putStrLn "Executing test"+ let+ whenColor = lookupOption opts+ HideSuccesses hideSuccesses = lookupOption opts - -- we can't run any update inside the vg monad,- -- as the golden file might still be open- (pFail, pReject, act) <- liftIO $ do- tested <- getActual+ let+ ?colors = useColor whenColor isTerm - -- read golden- liftIO $ putStrLn "Getting golden"- golden <- getGolden- case golden of- Nothing -> do- _ <- liftIO $ printf "%s: No golden value. Press <enter> to see actual value.\n" n- _ <- liftIO getLine- _ <- liftIO $ shw tested >>= showValue n- liftIO $ tryAccept n upd tested- Just golden' -> do- cmp' <- liftIO $ cmp golden' tested- case cmp' of- Equal -> do- _ <- liftIO $ printf "%s: Golden value matches output.\n" n- return (0, 0, return ())- diff' -> do- _ <- liftIO $ printf "%s: Output does not match golden value. Press <enter> to see diff.\n" n- _ <- liftIO getLine- _ <- liftIO $ showDiff n diff'- liftIO $ tryAccept n upd tested- -- now execute update- liftIO act- modify (\(nFail, nReject) -> (nFail + pFail, nReject + pReject))+ let+ outp = produceOutput opts tests - tryAccept :: TestName -> (a -> IO ()) -> a -> IO (Integer, Integer, IO ())- tryAccept n upd new = do- _ <- printf "%s: Accept actual value as new golden value? [yn]" n- ans <- getLine- case ans of- "y" -> do- return (0, 0, upd new)- "n" -> return (0, 1, return ())- _ -> do- putStrLn "Invalid answer."- tryAccept n upd new+ case () of { _+ | hideSuccesses && isTerm ->+ consoleOutputHidingSuccesses outp smap gmap+ | hideSuccesses && not isTerm ->+ streamOutputHidingSuccesses outp smap gmap+ | otherwise -> consoleOutput outp smap gmap+ } + return $ \time -> do+ stats <- computeStatistics smap+ printStatistics stats time+ return $ statFailures stats == 0++++ return r+++ showDiff :: TestName -> GDiff -> IO () showDiff n (DiffText _ tGold tAct) = do withSystemTempFile (n <.> "golden") (\fGold hGold -> do@@ -149,6 +158,7 @@ ["-c", "git diff --color=always --no-index --text " ++ fGold ++ " " ++ fAct ++ " | less -r > /dev/tty"] ) )+ showDiff n (ShowDiffed _ t) = showInLess n t showDiff _ Equal = error "Can't show diff for equal values..." @@ -161,3 +171,438 @@ _ <- PL.readProcessWithExitCode "sh" ["-c", "less > /dev/tty"] inp return () where inp = B.fromStrict $ encodeUtf8 t++tryAccept :: String -> TestName -> (a -> IO ()) -> a -> IO Bool+tryAccept pref nm upd new = do+ isTerm <- hSupportsANSI stdout+ when isTerm showCursor+ _ <- printf "%sAccept actual value as new golden value? [yn] " pref+ ans <- getLine+ case ans of+ "y" -> do+ upd new+ printf "%s" pref+ when isTerm hideCursor+ return True+ "n" -> do+ printf "%s" pref+ when isTerm hideCursor+ return False+ _ -> do+ printf "%sInvalid answer.\n" pref+ tryAccept pref nm upd new+++--------------------------------------------------+-- TestOutput base definitions+--------------------------------------------------+-- {{{+-- | 'TestOutput' is an intermediary between output formatting and output+-- printing. It lets us have several different printing modes (normal; print+-- failures only; quiet).+data TestOutput+ = PrintTest+ {- test name, used for golden lookup #-} (TestName)+ {- print test name -} (IO ())+ {- print test result -} ((Result, ResultStatus) -> IO ())+ | PrintHeading (IO ()) TestOutput+ | Skip+ | Seq TestOutput TestOutput++-- The monoid laws should hold observationally w.r.t. the semantics defined+-- in this module+instance Monoid TestOutput where+ mempty = Skip+ mappend = Seq++type Level = Int++produceOutput :: (?colors :: Bool) => OptionSet -> TestTree -> TestOutput+produceOutput opts tree =+ let+ -- Do not retain the reference to the tree more than necessary+ !alignment = computeAlignment opts tree++ handleSingleTest+ :: (IsTest t, ?colors :: Bool)+ => OptionSet -> TestName -> t -> Ap (Reader Level) TestOutput+ handleSingleTest _opts name _test = Ap $ do+ level <- ask++ let+ align = replicate (alignment - indentSize * level - length name) ' '+ pref = indent level ++ replicate (length name) ' ' ++ " " ++ align+ printTestName =+ printf "%s%s: %s" (indent level) name align++ printTestResult (result, resultStatus) = do+ result' <- case resultStatus of+ RInteract (GRNoGolden a shw upd) -> do+ printf "Golden value missing. Press <enter> to show actual value.\n"+ _ <- getLine+ let a' = runIdentity a+ shw' <- shw a'+ showValue name shw'+ isUpd <- tryAccept pref name upd a'+ return $ if isUpd then testPassed "Created golden value." else testFailed "Golden value missing."+ RInteract (GRDifferent _ a diff upd) -> do+ printf "Golden value differs from actual value.\n"+ showDiff name diff+ isUpd <- tryAccept pref name upd a+ return $ if isUpd then testPassed "Updated golden value." else testFailed "Golden value does not match actual output."+ _ -> return result+ rDesc <- formatMessage $ resultDescription result'++ -- use an appropriate printing function+ let+ printFn =+ if resultSuccessful result'+ then ok+ else fail+ time = resultTime result+ if resultSuccessful result'+ then printFn "OK"+ else printFn "FAIL"+ -- print time only if it's significant+ when (time >= 0.01) $+ printFn (printf " (%.2fs)" time)+ printFn "\n"++ when (not $ null rDesc) $+ (if resultSuccessful result' then infoOk else infoFail) $+ printf "%s%s\n" (indent $ level + 1) (formatDesc (level+1) rDesc)++ return $ PrintTest name printTestName printTestResult++ handleGroup :: TestName -> Ap (Reader Level) TestOutput -> Ap (Reader Level) TestOutput+ handleGroup name grp = Ap $ do+ level <- ask+ let+ printHeading = printf "%s%s\n" (indent level) name+ printBody = runReader (getApp grp) (level + 1)+ return $ PrintHeading printHeading printBody++ in+ flip runReader 0 $ getApp $+ foldTestTree+ trivialFold+ { foldSingle = handleSingleTest+ , foldGroup = handleGroup+ }+ opts tree++foldTestOutput+ :: (?colors :: Bool, Monoid b)+ => (IO () -> IO (Result, ResultStatus)+ -> ((Result, ResultStatus) -> IO ())+ -> b)+ -> (IO () -> b -> b)+ -> TestOutput -> StatusMap -> GoldenStatusMap -> b+foldTestOutput foldTest foldHeading outputTree smap gmap =+ flip evalState 0 $ getApp $ go outputTree where+ go (PrintTest nm printName printResult) = Ap $ do+ ix <- get+ put $! ix + 1+ let+ readStatusVar = getResultWithGolden smap gmap nm ix+ return $ foldTest printName readStatusVar printResult+ go (PrintHeading printName printBody) = Ap $+ foldHeading printName <$> getApp (go printBody)+ go (Seq a b) = mappend (go a) (go b)+ go Skip = mempty++-- }}}++--------------------------------------------------+-- TestOutput modes+--------------------------------------------------+-- {{{+consoleOutput :: (?colors :: Bool) => TestOutput -> StatusMap -> GoldenStatusMap -> IO ()+consoleOutput outp smap gmap =+ getTraversal . fst $ foldTestOutput foldTest foldHeading outp smap gmap+ where+ foldTest printName getResult printResult =+ ( Traversal $ do+ _ <- printName+ r <- getResult+ printResult r+ , Any True)+ foldHeading printHeading (printBody, Any nonempty) =+ ( Traversal $ do+ when nonempty $ printHeading >> getTraversal printBody+ , Any nonempty+ )++consoleOutputHidingSuccesses :: (?colors :: Bool) => TestOutput -> StatusMap -> GoldenStatusMap -> IO ()+consoleOutputHidingSuccesses outp smap gmap =+ void . getApp $ foldTestOutput foldTest foldHeading outp smap gmap+ where+ foldTest printName getResult printResult =+ Ap $ do+ _ <- printName+ r <- getResult+ if resultSuccessful (fst r)+ then clearThisLine >> (return $ Any False)+ else printResult r >> (return $ Any True)++ foldHeading printHeading printBody =+ Ap $ do+ _ <- printHeading+ Any failed <- getApp printBody+ unless failed clearAboveLine+ return $ Any failed++ clearAboveLine = do cursorUpLine 1; clearThisLine+ clearThisLine = do clearLine; setCursorColumn 0++streamOutputHidingSuccesses :: (?colors :: Bool) => TestOutput -> StatusMap -> GoldenStatusMap -> IO ()+streamOutputHidingSuccesses outp smap gmap =+ void . flip evalStateT [] . getApp $+ foldTestOutput foldTest foldHeading outp smap gmap+ where+ foldTest printName getResult printResult =+ Ap $ do+ r <- liftIO $ getResult+ if resultSuccessful (fst r)+ then return $ Any False+ else do+ stack <- get+ put []++ _ <- liftIO $ do+ sequence_ $ reverse stack+ _ <- printName+ printResult r++ return $ Any True++ foldHeading printHeading printBody =+ Ap $ do+ modify (printHeading :)+ Any failed <- getApp printBody+ unless failed $+ modify $ \stack ->+ case stack of+ _:rest -> rest+ [] -> [] -- shouldn't happen anyway+ return $ Any failed++-- }}}++--------------------------------------------------+-- Statistics+--------------------------------------------------+-- {{{++data Statistics = Statistics+ { statTotal :: !Int+ , statFailures :: !Int+ }++instance Monoid Statistics where+ Statistics t1 f1 `mappend` Statistics t2 f2 = Statistics (t1 + t2) (f1 + f2)+ mempty = Statistics 0 0++computeStatistics :: StatusMap -> IO Statistics+computeStatistics = getApp . foldMap (\var -> Ap $+ (\r -> Statistics 1 (if resultSuccessful r then 0 else 1))+ <$> getResultFromTVar var)++printStatistics :: (?colors :: Bool) => Statistics -> Time -> IO ()+printStatistics st time = do+ printf "\n"++ case statFailures st of+ 0 -> do+ ok $ printf "All %d tests passed (%.2fs)\n" (statTotal st) time++ fs -> do+ fail $ printf "%d out of %d tests failed (%.2fs)\n" fs (statTotal st) time++data FailureStatus+ = Unknown+ | Failed+ | OK++instance Monoid FailureStatus where+ mappend Failed _ = Failed+ mappend _ Failed = Failed++ mappend OK OK = OK++ mappend _ _ = Unknown++ mempty = OK++-- }}}++--------------------------------------------------+-- Console test reporter+--------------------------------------------------++-- | Report only failed tests+newtype HideSuccesses = HideSuccesses Bool+ deriving (Eq, Ord, Typeable)+instance IsOption HideSuccesses where+ defaultValue = HideSuccesses False+ parseValue = fmap HideSuccesses . safeRead+ optionName = return "hide-successes"+ optionHelp = return "Do not print tests that passed successfully"+ optionCLParser = flagCLParser Nothing (HideSuccesses True)++-- | When to use color on the output+data UseColor+ = Never | Always | Auto+ deriving (Eq, Ord, Typeable)++-- | Control color output+instance IsOption UseColor where+ defaultValue = Auto+ parseValue = parseUseColor+ optionName = return "color"+ optionHelp = return "When to use colored output. Options are 'never', 'always' and 'auto' (default: 'auto')"+ optionCLParser =+ option parse+ ( long name+ <> help (untag (optionHelp :: Tagged UseColor String))+ )+ where+ name = untag (optionName :: Tagged UseColor String)+ parse = str >>=+ maybe (readerError $ "Could not parse " ++ name) pure <$> parseValue++-- | @useColor when isTerm@ decides if colors should be used,+-- where @isTerm@ denotes where @stdout@ is a terminal device.+useColor :: UseColor -> Bool -> Bool+useColor cond isTerm =+ case cond of+ Never -> False+ Always -> True+ Auto -> isTerm++parseUseColor :: String -> Maybe UseColor+parseUseColor s =+ case map toLower s of+ "never" -> return Never+ "always" -> return Always+ "auto" -> return Auto+ _ -> Nothing++-- }}}++--------------------------------------------------+-- Various utilities+--------------------------------------------------+-- {{{++getResultWithGolden :: StatusMap -> GoldenStatusMap -> TestName -> Int -> IO (Result, ResultStatus)+getResultWithGolden smap gmap nm ix = do+ r <- getResultFromTVar statusVar++ gr <- atomically $ readTVar gmap+ case nm `M.lookup` gr of+ Just g@(GRDifferent {}) -> return (r, RInteract g)+ Just g@(GRNoGolden {}) -> return (r, RInteract g)+ _ | resultSuccessful r -> return (r, RPass)+ _ | otherwise -> return (r, RFail)+ where statusVar =+ fromMaybe (error "internal error: index out of bounds") $+ IntMap.lookup ix smap++getResultFromTVar :: TVar Status -> IO Result+getResultFromTVar statusVar = do+ atomically $ do+ status <- readTVar statusVar+ case status of+ Done r -> return r+ _ -> retry++-- }}}++--------------------------------------------------+-- Formatting+--------------------------------------------------+-- {{{++indentSize :: Int+indentSize = 2++indent :: Int -> String+indent n = replicate (indentSize * n) ' '++-- handle multi-line result descriptions properly+formatDesc+ :: Int -- indent+ -> String+ -> String+formatDesc n desc =+ let+ -- remove all trailing linebreaks+ chomped = reverse . dropWhile (== '\n') . reverse $ desc++ multiline = '\n' `elem` chomped++ -- we add a leading linebreak to the description, to start it on a new+ -- line and add an indentation+ paddedDesc = flip concatMap chomped $ \c ->+ if c == '\n'+ then c : indent n+ else [c]+ in+ if multiline+ then paddedDesc+ else chomped++data Maximum a+ = Maximum a+ | MinusInfinity++instance Ord a => Monoid (Maximum a) where+ mempty = MinusInfinity++ Maximum a `mappend` Maximum b = Maximum (a `max` b)+ MinusInfinity `mappend` a = a+ a `mappend` MinusInfinity = a++-- | Compute the amount of space needed to align "OK"s and "FAIL"s+computeAlignment :: OptionSet -> TestTree -> Int+computeAlignment opts =+ fromMonoid .+ foldTestTree+ trivialFold+ { foldSingle = \_ name _ level -> Maximum (length name + level)+ , foldGroup = \_ m -> m . (+ indentSize)+ }+ opts+ where+ fromMonoid m =+ case m 0 of+ MinusInfinity -> 0+ Maximum x -> x++-- (Potentially) colorful output+ok, fail, infoOk, infoFail :: (?colors :: Bool) => String -> IO ()+fail = output BoldIntensity Vivid Red+ok = output NormalIntensity Dull Green+infoOk = output NormalIntensity Dull White+infoFail = output NormalIntensity Dull Red++output+ :: (?colors :: Bool)+ => ConsoleIntensity+ -> ColorIntensity+ -> Color+ -> String+ -> IO ()+output bold intensity color st+ | ?colors =+ (do+ setSGR+ [ SetColor Foreground intensity color+ , SetConsoleIntensity bold+ ]+ putStr st+ ) `finally` setSGR []+ | otherwise = putStr st++-- }}}
+ Test/Tasty/Silver/Interactive/Run.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE GeneralizedNewtypeDeriving, PatternGuards, DeriveDataTypeable #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE RankNTypes #-}+module Test.Tasty.Silver.Interactive.Run+ ( wrapRunTest+ )+ where++import Test.Tasty hiding (defaultMain)+import Test.Tasty.Runners+import Test.Tasty.Options+import Data.Typeable+import Data.Tagged+import Test.Tasty.Providers++data CustomTestExec t = IsTest t => CustomTestExec t (OptionSet -> t -> (Progress -> IO ()) -> IO Result)+ deriving (Typeable)++instance IsTest t => IsTest (CustomTestExec t) where+ run opts (CustomTestExec t r) cb = r opts t cb+ testOptions = retag $ (testOptions :: Tagged t [OptionDescription])+++-- | Provide new test run function wrapping the existing tests.+wrapRunTest :: (forall t . IsTest t => TestName -> OptionSet -> t -> (Progress -> IO ()) -> IO Result) -> TestTree -> TestTree+wrapRunTest f (SingleTest n t) = SingleTest n (CustomTestExec t (f n))+wrapRunTest f (TestGroup n ts) = TestGroup n (fmap (wrapRunTest f) ts)+wrapRunTest f (PlusTestOptions o t) = PlusTestOptions o (wrapRunTest f t)+wrapRunTest f (WithResource r t) = WithResource r (\x -> wrapRunTest f (t x))+wrapRunTest f (AskOptions t) = AskOptions (\o -> wrapRunTest f (t o))
Test/Tasty/Silver/Internal.hs view
@@ -4,6 +4,7 @@ import Control.Applicative import Control.Exception+import Control.Monad.Identity import Data.Typeable (Typeable) import Data.ByteString as SB import System.IO.Error@@ -63,26 +64,53 @@ = ShowText T.Text -- ^ Show the given text. instance IsTest Golden where- run opts golden _ = runGolden golden (lookupOption opts)+ run opts golden _ = do+ (r, gr) <- runGolden golden+ let (AcceptTests accept) = lookupOption opts :: AcceptTests+ case gr of+ GRNoGolden act _ upd | accept -> do+ act >>= upd+ return $ testPassed "Created golden file."+ GRDifferent _ act _ upd | accept -> do+ upd act+ return $ testPassed "Updated golden file."+ _ -> return r+ testOptions = return [Option (Proxy :: Proxy AcceptTests)] -runGolden :: Golden -> AcceptTests -> IO Result-runGolden (Golden getGolden getActual cmp _ upd) (AcceptTests accept) = do+type GoldenResult = GoldenResult' IO+type GoldenResultI = GoldenResult' Identity++data GoldenResult' m+ = GREqual+ | forall a . GRDifferent+ (a) -- golden+ (a) -- actual+ (GDiff) -- diff+ (a -> IO ()) -- update+ | forall a . GRNoGolden+ (m a) -- compute actual (we don't want to compute it if it is not used)+ (a -> IO GShow) --show+ (a -> IO ()) -- update++runGolden :: Golden -> IO (Result, GoldenResult)+runGolden (Golden getGolden getActual cmp shw upd) = do ref' <- getGolden case ref' of- Nothing | accept -> do- new <- getActual- upd new- return $ testPassed "Created golden file."- Nothing -> return $ testFailed "Missing golden value."+ Nothing -> return (testFailed "Missing golden value.", GRNoGolden getActual shw upd) Just ref -> do new <- getActual -- Output could be arbitrarily big, so don't even try to say what wen't wrong. cmp' <- cmp ref new case cmp' of- Equal -> return $ testPassed ""- _ | accept -> do- upd new- return $ testPassed "Updated golden file."- d | isJust (gReason d) -> return $ testFailed $ fromJust $ gReason d- _ -> return $ testFailed "Result did not match expected output. Use interactive mode to see full output."+ Equal -> return (testPassed "", GREqual)+ d -> let r = fromMaybe "Result did not match expected output. Use interactive mode (-i) to see full output." (gReason d)+ in return (testFailed r, GRDifferent ref new cmp' upd)++forceGoldenResult :: GoldenResult -> IO GoldenResultI+forceGoldenResult gr = case gr of+ (GRNoGolden act shw upd) -> do+ act' <- act+ return $ GRNoGolden (Identity act') shw upd+ (GRDifferent a b c d) -> return $ GRDifferent a b c d+ (GREqual) -> return GREqual
tasty-silver.cabal view
@@ -1,5 +1,5 @@ name: tasty-silver-version: 3.0.1.0+version: 3.0.2.0 synopsis: Golden tests support for tasty. Fork of tasty-golden. description: This package provides support for «golden testing».@@ -30,27 +30,31 @@ exposed-modules: Test.Tasty.Silver Test.Tasty.Silver.Advanced Test.Tasty.Silver.Interactive+ Test.Tasty.Silver.Interactive.Run other-modules: Test.Tasty.Silver.Internal ghc-options: -Wall build-depends:+ ansi-terminal >= 0.6.2.1 && < 0.7,+ array >= 0.5.0.0 && <0.6,+ async, base ==4.*,- tasty >= 0.8, bytestring,- process >= 1.2 && < 1.3,+ containers,+ directory,+ deepseq,+ filepath, mtl, optparse-applicative,- filepath,- temporary-rc,+ process >= 1.2 && < 1.3,+ process-extras >= 0.2 && < 0.3,+ stm >= 2.4.2 && < 2.5, tagged,- deepseq,- containers,- directory,- async,- text >= 0.11.0.0 && < 1.3,- process-extras >= 0.2 && < 0.3+ tasty >= 0.8,+ temporary-rc,+ text >= 0.11.0.0 && < 1.3 Test-suite test Default-language: