sydtest 0.7.0.1 → 0.8.0.0
raw patch · 14 files changed
+174/−106 lines, 14 files
Files
- CHANGELOG.md +16/−0
- LICENSE.md +1/−1
- src/Test/Syd.hs +1/−0
- src/Test/Syd/Def/Around.hs +6/−5
- src/Test/Syd/Def/AroundAll.hs +11/−12
- src/Test/Syd/Def/Specify.hs +8/−6
- src/Test/Syd/Def/TestDefM.hs +40/−68
- src/Test/Syd/Modify.hs +1/−1
- src/Test/Syd/Output.hs +11/−9
- src/Test/Syd/Run.hs +1/−1
- src/Test/Syd/Runner/Wrappers.hs +0/−1
- src/Test/Syd/SpecDef.hs +61/−0
- sydtest.cabal +3/−2
- test/Test/Syd/DescriptionsSpec.hs +14/−0
CHANGELOG.md view
@@ -1,5 +1,21 @@ # Changelog +## [0.8.0.0] - 2022-02-11++### Changed++* The `TestDefM` now contains a `TestDefEnv` which also contains the test description path, along with `TestRunSettings`.+* Removed the `MonadState ()` instance of `TestDefM`. It was just silly.+* Changed the internals of `TestDefM` to use `ReaderT` and a strict `WriterT` instead of `RWST`.+* Renamed `wrapRWST` to `wrapForest`.+* Fixed the property label output to use the right total.+* Moved `filterTestForest` and `randomiseTestForest` from `Test.Syd.TestDefM` to `Test.Syd.SpecDef`, where `SpecDefForest` is defined.++### Added++* `getTestDescriptionPath` to get the test description path upwards from inside a test definition.++ ## [0.7.0.1] - 2021-12-23 ### Changed
LICENSE.md view
@@ -1,6 +1,6 @@ Sydtest License -Copyright (c) 2020-2021 Tom Sydney Kerckhove+Copyright (c) 2020-2022 Tom Sydney Kerckhove **Anyone** can use this software to test your software under the following license:
src/Test/Syd.hs view
@@ -50,6 +50,7 @@ specifyWithBoth, specifyWithAll, prop,+ getTestDescriptionPath, -- ** Commented-out tests xdescribe,
src/Test/Syd/Def/Around.hs view
@@ -11,7 +11,8 @@ module Test.Syd.Def.Around where import Control.Exception-import Control.Monad.RWS.Strict+import Control.Monad.Reader+import Control.Monad.Writer.Strict import Data.Kind import Test.QuickCheck.IO () import Test.Syd.Def.TestDefM@@ -175,10 +176,10 @@ TestDefM outers newInner result -> TestDefM outers oldInner result aroundWith' func (TestDefM rwst) =- local (\trs -> trs {testRunSettingMaxShrinks = 0}) $+ local (\tde -> tde {testDefEnvTestRunSettings = (testDefEnvTestRunSettings tde) {testRunSettingMaxShrinks = 0}}) $ TestDefM $- flip mapRWST rwst $ \inner -> do- (res, s, forest) <- inner+ flip mapWriterT rwst $ \inner -> do+ (res, forest) <- inner -- a: outers -- c: newInner -- d: oldInner@@ -221,4 +222,4 @@ modifyForest = map modifyTree let forest' :: SpecDefForest outers oldInner () forest' = modifyForest forest- pure (res, s, forest')+ pure (res, forest')
src/Test/Syd/Def/AroundAll.hs view
@@ -3,7 +3,6 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE IncoherentInstances #-}-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-}@@ -11,7 +10,7 @@ -- | This module defines all the functions you will use to define your test suite. module Test.Syd.Def.AroundAll where -import Control.Monad.RWS.Strict+import Control.Monad.Writer.Strict import Test.QuickCheck.IO () import Test.Syd.Def.TestDefM import Test.Syd.HList@@ -23,7 +22,7 @@ IO outer -> TestDefM (outer ': otherOuters) inner result -> TestDefM otherOuters inner result-beforeAll action = wrapRWST $ \forest -> DefBeforeAllNode action forest+beforeAll action = wrapForest $ \forest -> DefBeforeAllNode action forest -- | Run a custom action before all spec items in a group without setting up any outer resources. beforeAll_ ::@@ -57,7 +56,7 @@ (HList outers -> IO ()) -> TestDefM outers inner result -> TestDefM outers inner result-afterAll' func = wrapRWST $ \forest -> DefAfterAllNode func forest+afterAll' func = wrapForest $ \forest -> DefAfterAllNode func forest -- | Run a custom action after all spec items without using any outer resources. afterAll_ ::@@ -75,7 +74,7 @@ ((outer -> IO ()) -> IO ()) -> TestDefM (outer ': otherOuters) inner result -> TestDefM otherOuters inner result-aroundAll func = wrapRWST $ \forest -> DefAroundAllNode func forest+aroundAll func = wrapForest $ \forest -> DefAroundAllNode func forest -- | Run a custom action before and/or after all spec items in a group without accessing any resources. --@@ -115,7 +114,7 @@ (IO () -> IO ()) -> TestDefM outers inner result -> TestDefM outers inner result-aroundAll_ func = wrapRWST $ \forest -> DefWrapNode func forest+aroundAll_ func = wrapForest $ \forest -> DefWrapNode func forest -- | Run a custom action before and/or after all spec items in a group to provide access to a resource 'a' while using a resource 'b' --@@ -126,16 +125,16 @@ ((newOuter -> IO ()) -> (oldOuter -> IO ())) -> TestDefM (newOuter ': oldOuter ': otherOuters) inner result -> TestDefM (oldOuter ': otherOuters) inner result-aroundAllWith func = wrapRWST $ \forest -> DefAroundAllWithNode func forest+aroundAllWith func = wrapForest $ \forest -> DefAroundAllWithNode func forest -- | Declare a node in the spec def forest-wrapRWST ::+wrapForest :: -- | The wrapper node (TestForest outers1 inner1 -> TestTree outers2 inner2) -> TestDefM outers1 inner1 result -> TestDefM outers2 inner2 result-wrapRWST func (TestDefM rwst) = TestDefM $- flip mapRWST rwst $ \inner -> do- (res, s, forest) <- inner+wrapForest func (TestDefM rwst) = TestDefM $+ flip mapWriterT rwst $ \inner -> do+ (res, forest) <- inner let forest' = [func forest]- pure (res, s, forest')+ pure (res, forest')
src/Test/Syd/Def/Specify.hs view
@@ -3,7 +3,6 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE IncoherentInstances #-}-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-}@@ -66,7 +65,10 @@ String -> TestDefM outers inner () -> TestDefM outers inner ()-describe s func = censor ((: []) . DefDescribeNode (T.pack s)) func+describe s =+ let t = T.pack s+ in local (\tde -> tde {testDefEnvDescriptionPath = t : testDefEnvDescriptionPath tde})+ . censor ((: []) . DefDescribeNode t) -- TODO maybe we want to keep all tests below but replace them with a "Pending" instead. xdescribe :: String -> TestDefM outers inner () -> TestDefM outers inner ()@@ -170,7 +172,7 @@ test -> TestDefM outers inner () it s t = do- sets <- ask+ sets <- asks testDefEnvTestRunSettings let testDef = TDef { testDefVal = \supplyArgs ->@@ -271,7 +273,7 @@ test -> TestDefM (outer ': otherOuters) inner () itWithOuter s t = do- sets <- ask+ sets <- asks testDefEnvTestRunSettings let testDef = TDef { testDefVal = \supplyArgs ->@@ -369,7 +371,7 @@ test -> TestDefM (outer ': otherOuters) inner () itWithBoth s t = do- sets <- ask+ sets <- asks testDefEnvTestRunSettings let testDef = TDef { testDefVal = \supplyArgs ->@@ -437,7 +439,7 @@ test -> TestDefM outers inner () itWithAll s t = do- sets <- ask+ sets <- asks testDefEnvTestRunSettings let testDef = TDef { testDefVal = \supplyArgs ->
src/Test/Syd/Def/TestDefM.hs view
@@ -1,28 +1,23 @@ {-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE IncoherentInstances #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} module Test.Syd.Def.TestDefM where -import Control.Monad-import Control.Monad.RWS.Strict import Control.Monad.Random-import Data.DList (DList)-import qualified Data.DList as DList+import Control.Monad.Reader+import Control.Monad.Writer.Strict import Data.Kind-import Data.Maybe import Data.Text (Text)-import qualified Data.Text as T-import System.Random.Shuffle+import GHC.Generics (Generic) import Test.QuickCheck.IO () import Test.Syd.OptParse import Test.Syd.Run@@ -50,17 +45,35 @@ -- -- In practice, all of these three parameters should be '()' at the top level. newtype TestDefM (outers :: [Type]) inner result = TestDefM- { unTestDefM :: RWST TestRunSettings (TestForest outers inner) () IO result+ { unTestDefM :: WriterT (TestForest outers inner) (ReaderT TestDefEnv IO) result }- deriving (Functor, Applicative, Monad, MonadIO, MonadReader TestRunSettings, MonadWriter (TestForest outers inner), MonadState ())+ deriving+ ( Functor,+ Applicative,+ Monad,+ MonadIO,+ MonadReader TestDefEnv,+ MonadWriter (TestForest outers inner)+ ) +data TestDefEnv = TestDefEnv+ { testDefEnvDescriptionPath :: ![Text],+ testDefEnvTestRunSettings :: !TestRunSettings+ }+ deriving (Show, Eq, Generic)+ execTestDefM :: Settings -> TestDefM outers inner result -> IO (TestForest outers inner) execTestDefM sets = fmap snd . runTestDefM sets runTestDefM :: Settings -> TestDefM outers inner result -> IO (result, TestForest outers inner) runTestDefM sets defFunc = do let func = unTestDefM defFunc- (a, _, testForest) <- runRWST func (toTestRunSettings sets) ()+ let testDefEnv =+ TestDefEnv+ { testDefEnvDescriptionPath = [],+ testDefEnvTestRunSettings = toTestRunSettings sets+ }+ (a, testForest) <- runReaderT (runWriterT func) testDefEnv let testForest' = filterTestForest (settingFilter sets) testForest stdgen <- case settingSeed sets of FixedSeed seed -> pure $ mkStdGen seed@@ -71,6 +84,21 @@ else testForest' pure (a, testForest'') +-- | Get the path of 'describe' strings upwards.+--+-- Note that using this function makes tests less movable, depending on what+-- you do with these strings.+-- For example, if you use these strings to define the path to a golden test+-- file, then that path will change if you move the tests somewhere else.+-- This combines unfortunately with the way @sydtest-discover@ makes the module+-- name part of this path.+-- Indeed: moving your tests to another module will change their path as well,+-- if you use @sydtest-discover@.+-- Also note that while test forests can be randomised, their description path+-- upwards will not, because of how trees are structured.+getTestDescriptionPath :: TestDefM outers inner [Text]+getTestDescriptionPath = asks testDefEnvDescriptionPath+ toTestRunSettings :: Settings -> TestRunSettings toTestRunSettings Settings {..} = TestRunSettings@@ -82,59 +110,3 @@ testRunSettingGoldenStart = settingGoldenStart, testRunSettingGoldenReset = settingGoldenReset }--filterTestForest :: Maybe Text -> SpecDefForest outers inner result -> SpecDefForest outers inner result-filterTestForest mf = fromMaybe [] . goForest DList.empty- where- goForest :: DList Text -> SpecDefForest a b c -> Maybe (SpecDefForest a b c)- goForest ts sdf = do- let sdf' = mapMaybe (goTree ts) sdf- guard $ not $ null sdf'- pure sdf'-- filterGuard :: DList Text -> Bool- filterGuard dl = case mf of- Just f -> f `T.isInfixOf` T.intercalate "." (DList.toList dl)- Nothing -> True-- goTree :: DList Text -> SpecDefTree a b c -> Maybe (SpecDefTree a b c)- goTree dl = \case- DefSpecifyNode t td e -> do- let tl = DList.snoc dl t- guard $ filterGuard tl- pure $ DefSpecifyNode t td e- DefPendingNode t mr -> do- let tl = DList.snoc dl t- guard $ filterGuard tl- pure $ DefPendingNode t mr- DefDescribeNode t sdf -> DefDescribeNode t <$> goForest (DList.snoc dl t) sdf- DefWrapNode func sdf -> DefWrapNode func <$> goForest dl sdf- DefBeforeAllNode func sdf -> DefBeforeAllNode func <$> goForest dl sdf- DefAroundAllNode func sdf -> DefAroundAllNode func <$> goForest dl sdf- DefAroundAllWithNode func sdf -> DefAroundAllWithNode func <$> goForest dl sdf- DefAfterAllNode func sdf -> DefAfterAllNode func <$> goForest dl sdf- DefParallelismNode func sdf -> DefParallelismNode func <$> goForest dl sdf- DefRandomisationNode func sdf -> DefRandomisationNode func <$> goForest dl sdf- DefFlakinessNode func sdf -> DefFlakinessNode func <$> goForest dl sdf--randomiseTestForest :: MonadRandom m => SpecDefForest outers inner result -> m (SpecDefForest outers inner result)-randomiseTestForest = goForest- where- goForest :: MonadRandom m => SpecDefForest a b c -> m (SpecDefForest a b c)- goForest = traverse goTree >=> shuffleM- goTree :: MonadRandom m => SpecDefTree a b c -> m (SpecDefTree a b c)- goTree = \case- DefSpecifyNode t td e -> pure $ DefSpecifyNode t td e- DefPendingNode t mr -> pure $ DefPendingNode t mr- DefDescribeNode t sdf -> DefDescribeNode t <$> goForest sdf- DefWrapNode func sdf -> DefWrapNode func <$> goForest sdf- DefBeforeAllNode func sdf -> DefBeforeAllNode func <$> goForest sdf- DefAroundAllNode func sdf -> DefAroundAllNode func <$> goForest sdf- DefAroundAllWithNode func sdf -> DefAroundAllWithNode func <$> goForest sdf- DefAfterAllNode func sdf -> DefAfterAllNode func <$> goForest sdf- DefParallelismNode func sdf -> DefParallelismNode func <$> goForest sdf- DefFlakinessNode i sdf -> DefFlakinessNode i <$> goForest sdf- DefRandomisationNode eor sdf ->- DefRandomisationNode eor <$> case eor of- RandomiseExecutionOrder -> goForest sdf- DoNotRandomiseExecutionOrder -> pure sdf
src/Test/Syd/Modify.hs view
@@ -39,7 +39,7 @@ import Test.Syd.SpecDef modifyRunSettings :: (TestRunSettings -> TestRunSettings) -> TestDefM a b c -> TestDefM a b c-modifyRunSettings = local+modifyRunSettings func = local (\tde -> tde {testDefEnvTestRunSettings = func $ testDefEnvTestRunSettings tde}) modifyMaxSuccess :: (Int -> Int) -> TestDefM a b c -> TestDefM a b c modifyMaxSuccess func = modifyRunSettings $ \trs -> trs {testRunSettingMaxSuccess = func (testRunSettingMaxSuccess trs)}
src/Test/Syd/Output.hs view
@@ -194,7 +194,7 @@ | testRunResultStatus == TestPassed, w <- maybeToList testRunResultNumTests ],- map pad $ labelsChunks testRunResultLabels,+ map pad $ labelsChunks (fromMaybe 1 testRunResultNumTests) testRunResultLabels, map pad $ classesChunks testRunResultClasses, map pad $ tablesChunks testRunResultTables, [pad $ outputGoldenCase gc | gc <- maybeToList testRunResultGoldenCase]@@ -211,18 +211,21 @@ ] TestFailed -> [["Retries: ", chunk (T.pack (show retries)), " (likely not flaky)"]] -labelsChunks :: Maybe (Map [String] Int) -> [[Chunk]]-labelsChunks Nothing = []-labelsChunks (Just labels)- | M.size labels <= 1 = []+labelsChunks :: Word -> Maybe (Map [String] Int) -> [[Chunk]]+labelsChunks _ Nothing = []+labelsChunks totalCount (Just labels)+ | M.size labels < 1 = [] | otherwise =- [chunk "labels"] :+ [chunk "Labels"] : map ( pad . ( \(ss, i) -> [ chunk ( T.pack- ( printf "%5.2f%% %s" (100 * fromIntegral i / fromIntegral total :: Double) (commaList (map show ss))+ ( printf+ "%5.2f%% %s"+ (100 * fromIntegral i / fromIntegral totalCount :: Double)+ (commaList (map show ss)) ) ) ]@@ -231,14 +234,13 @@ (M.toList labels) where pad = (chunk (T.pack (replicate paddingSize ' ')) :)- total = sum $ map snd $ M.toList labels classesChunks :: Maybe (Map String Int) -> [[Chunk]] classesChunks Nothing = [] classesChunks (Just classes) | M.null classes = [] | otherwise =- [chunk "classes"] :+ [chunk "Classes"] : map ( pad . ( \(s, i) ->
src/Test/Syd/Run.hs view
@@ -367,7 +367,7 @@ testRunSettingGoldenStart :: !Bool, testRunSettingGoldenReset :: !Bool }- deriving (Show, Generic)+ deriving (Show, Eq, Generic) defaultTestRunSettings :: TestRunSettings defaultTestRunSettings =
src/Test/Syd/Runner/Wrappers.hs view
@@ -1,7 +1,6 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-}
src/Test/Syd/SpecDef.hs view
@@ -16,12 +16,17 @@ -- | This module defines all the functions you will use to define your test suite. module Test.Syd.SpecDef where +import Control.Monad+import Control.Monad.Random+import Data.DList (DList)+import qualified Data.DList as DList import Data.Kind import Data.Maybe import Data.Text (Text) import qualified Data.Text as T import Data.Word import GHC.Stack+import System.Random.Shuffle import Test.QuickCheck.IO () import Test.Syd.HList import Test.Syd.OptParse@@ -166,6 +171,62 @@ DefParallelismNode p sdf -> DefParallelismNode p <$> goF sdf DefRandomisationNode p sdf -> DefRandomisationNode p <$> goF sdf DefFlakinessNode p sdf -> DefFlakinessNode p <$> goF sdf++filterTestForest :: Maybe Text -> SpecDefForest outers inner result -> SpecDefForest outers inner result+filterTestForest mf = fromMaybe [] . goForest DList.empty+ where+ goForest :: DList Text -> SpecDefForest a b c -> Maybe (SpecDefForest a b c)+ goForest ts sdf = do+ let sdf' = mapMaybe (goTree ts) sdf+ guard $ not $ null sdf'+ pure sdf'++ filterGuard :: DList Text -> Bool+ filterGuard dl = case mf of+ Just f -> f `T.isInfixOf` T.intercalate "." (DList.toList dl)+ Nothing -> True++ goTree :: DList Text -> SpecDefTree a b c -> Maybe (SpecDefTree a b c)+ goTree dl = \case+ DefSpecifyNode t td e -> do+ let tl = DList.snoc dl t+ guard $ filterGuard tl+ pure $ DefSpecifyNode t td e+ DefPendingNode t mr -> do+ let tl = DList.snoc dl t+ guard $ filterGuard tl+ pure $ DefPendingNode t mr+ DefDescribeNode t sdf -> DefDescribeNode t <$> goForest (DList.snoc dl t) sdf+ DefWrapNode func sdf -> DefWrapNode func <$> goForest dl sdf+ DefBeforeAllNode func sdf -> DefBeforeAllNode func <$> goForest dl sdf+ DefAroundAllNode func sdf -> DefAroundAllNode func <$> goForest dl sdf+ DefAroundAllWithNode func sdf -> DefAroundAllWithNode func <$> goForest dl sdf+ DefAfterAllNode func sdf -> DefAfterAllNode func <$> goForest dl sdf+ DefParallelismNode func sdf -> DefParallelismNode func <$> goForest dl sdf+ DefRandomisationNode func sdf -> DefRandomisationNode func <$> goForest dl sdf+ DefFlakinessNode func sdf -> DefFlakinessNode func <$> goForest dl sdf++randomiseTestForest :: MonadRandom m => SpecDefForest outers inner result -> m (SpecDefForest outers inner result)+randomiseTestForest = goForest+ where+ goForest :: MonadRandom m => SpecDefForest a b c -> m (SpecDefForest a b c)+ goForest = traverse goTree >=> shuffleM+ goTree :: MonadRandom m => SpecDefTree a b c -> m (SpecDefTree a b c)+ goTree = \case+ DefSpecifyNode t td e -> pure $ DefSpecifyNode t td e+ DefPendingNode t mr -> pure $ DefPendingNode t mr+ DefDescribeNode t sdf -> DefDescribeNode t <$> goForest sdf+ DefWrapNode func sdf -> DefWrapNode func <$> goForest sdf+ DefBeforeAllNode func sdf -> DefBeforeAllNode func <$> goForest sdf+ DefAroundAllNode func sdf -> DefAroundAllNode func <$> goForest sdf+ DefAroundAllWithNode func sdf -> DefAroundAllWithNode func <$> goForest sdf+ DefAfterAllNode func sdf -> DefAfterAllNode func <$> goForest sdf+ DefParallelismNode func sdf -> DefParallelismNode func <$> goForest sdf+ DefFlakinessNode i sdf -> DefFlakinessNode i <$> goForest sdf+ DefRandomisationNode eor sdf ->+ DefRandomisationNode eor <$> case eor of+ RandomiseExecutionOrder -> goForest sdf+ DoNotRandomiseExecutionOrder -> pure sdf data Parallelism = Parallel | Sequential
sydtest.cabal view
@@ -1,11 +1,11 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.34.5.+-- This file has been generated from package.yaml by hpack version 0.34.6. -- -- see: https://github.com/sol/hpack name: sydtest-version: 0.7.0.1+version: 0.8.0.0 synopsis: A modern testing framework for Haskell with good defaults and advanced testing features. description: A modern testing framework for Haskell with good defaults and advanced testing features. Sydtest aims to make the common easy and the hard possible. See https://github.com/NorfairKing/sydtest#readme for more information. category: Testing@@ -124,6 +124,7 @@ Test.Syd.AroundAllSpec Test.Syd.AroundCombinationSpec Test.Syd.AroundSpec+ Test.Syd.DescriptionsSpec Test.Syd.FootgunSpec Test.Syd.GoldenSpec Test.Syd.OptParseSpec
+ test/Test/Syd/DescriptionsSpec.hs view
@@ -0,0 +1,14 @@+{-# LANGUAGE OverloadedStrings #-}++module Test.Syd.DescriptionsSpec (spec) where++import Test.Syd++spec :: Spec+spec =+ describe "foo" $+ describe "bar" $+ describe "quux" $ do+ descs <- getTestDescriptionPath+ it "gets the right descriptions" $+ descs `shouldBe` ["quux", "bar", "foo", "Test.Syd.DescriptionsSpec"]