Skip to content

Commit

Permalink
Support for non-Identity generator monad (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
fsoikin authored Oct 11, 2024
1 parent 76e1bc6 commit 4a855c6
Show file tree
Hide file tree
Showing 11 changed files with 3,378 additions and 1,461 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 0.0.3

## Changed

- `runSpecAndExitProcess'` and `runSpecAndGetResults` now work for any test tree
generator monad, via the new `TestTreeGenerator` class.

## 0.0.2

### Changed
Expand Down
85 changes: 77 additions & 8 deletions src/Node.purs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Effect (Effect)
import Effect.Aff (Aff, launchAff_)
import Effect.Class (liftEffect)
import Node.Process (exit')
import Test.Spec (Spec)
import Test.Spec (SpecT, Spec)
import Test.Spec.Result (Result)
import Test.Spec.Runner (Reporter)
import Test.Spec.Runner as Spec
Expand All @@ -19,6 +19,9 @@ import Test.Spec.Tree (Tree)

-- | Runs the given spec, using configuration derived from CLI options (if any),
-- | and exits the process with an exit indicating success or failure.
-- |
-- | For more control over the configuration or test tree generating monad, use
-- | `runSpecAndExitProcess'`.
runSpecAndExitProcess :: Array Reporter -> Spec Unit -> Effect Unit
runSpecAndExitProcess =
runSpecAndExitProcess' { defaultConfig: Cfg.defaultConfig, parseCLIOptions: true }
Expand All @@ -29,12 +32,26 @@ runSpecAndExitProcess =
-- | The `parseCLIOptions` parameter determines whether the `defaultConfig`
-- | should be used as is or CLI options (if any provided) should be applied on
-- | top of it.
runSpecAndExitProcess' :: c.
{ defaultConfig :: Cfg.TestRunConfig' c
, parseCLIOptions :: Boolean
}
-- |
-- | Note that, because this function works for any test tree generator monad
-- | `m`, you will need to specify it somehow. You can either give the spec
-- | parameter an explicit type:
-- |
-- | spec :: SpecT Aff Unit Aff Unit
-- | spec = do
-- | ...
-- |
-- | Or specify the monad via visible type application:
-- |
-- | runSpecAndExitProcess' @Aff ...
-- |
runSpecAndExitProcess' :: @m c.
TestTreeGenerator m
=> { defaultConfig :: Cfg.TestRunConfig' c
, parseCLIOptions :: Boolean
}
-> Array Reporter
-> Spec Unit
-> SpecT Aff Unit m Unit
-> Effect Unit
runSpecAndExitProcess' args reporters spec = launchAff_ do
config <-
Expand All @@ -45,9 +62,61 @@ runSpecAndExitProcess' args reporters spec = launchAff_ do
res <- runSpecAndGetResults config reporters spec
liftEffect $ exit' $ if successful res then 0 else 1

runSpecAndGetResults :: c. Cfg.TestRunConfig' c -> Array Reporter -> Spec Unit -> Aff (Array (Tree String Void Result))
-- | The core logic of a persistent test run:
-- |
-- | * Runs the spec tree generation in the given monad `m` (which is usually
-- | just `Identity`, but can be different in most complex scenarios)
-- | * Persists results to disk.
-- | * Returns the tree of results.
-- |
runSpecAndGetResults :: c m
. TestTreeGenerator m
=> Cfg.TestRunConfig' c
-> Array Reporter
-> SpecT Aff Unit m Unit
-> Aff (Array (Tree String Void Result))
runSpecAndGetResults config reporters spec = do
specCfg <- Cfg.toSpecConfig config <#> _ { exit = false }
results <- un Identity $ Spec.evalSpecT specCfg reporters spec
results <- generateTestTree $ Spec.evalSpecT specCfg reporters spec
Persist.persistResults results
pure results

-- | A monad in which test tree generation happens. This is different from the
-- | monad in which the tests themselves run.
-- |
-- | In most cases the test tree would be generated in `Identity`, making for
-- | deterministic, pure test trees:
-- |
-- | spec :: SpecT Aff Unit Identity Unit
-- | spec = do
-- | it "is a pure test" do
-- | (2 + 2) `shouldEqual` 4
-- |
-- | But in more complicated scenarios, you might want to generate test trees in
-- | a more powerful monad. For example, the following test tree is generated in
-- | the `Effect` monad, utilizing the effectful function `randomInt` to
-- | determine the number of tests to generate:
-- |
-- | spec :: SpecT Aff Unit Effect Unit
-- | spec = do
-- | numTests <- randomInt 1 10
-- | for_ numTests \i -> do
-- | it ("is test number " <> show i) do
-- | (i + i - i) `shouldEqual` i
-- |
-- | This class assumes that the monad can be evaluated without any additional
-- | parameters. This allows for most normal use cases with ergonomic API. For
-- | more complicated cases, where the generator monad requires something extra
-- | (such as `StateT` or `ReaderT`), you can always use the `mapSpecTree`
-- | function to transform the generated test tree before running it.
class Monad m <= TestTreeGenerator m where
-- | Evaluates the test tree generator monad, returning the generated test
-- | tree. See comments on the `TestTreeGenerator` class for more information.
generateTestTree :: a. m (Aff a) -> Aff a

instance TestTreeGenerator Identity where
generateTestTree = un Identity
instance TestTreeGenerator Aff where
generateTestTree = join
instance TestTreeGenerator Effect where
generateTestTree = liftEffect >>> join
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Summary
0/0 tests passed
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
✓︎ test number 1
✓︎ test number 2
✓︎ test number 3

Summary
3/3 tests passed
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
✓︎ test number 1
✓︎ test number 2
✓︎ test number 3
✓︎ test number 4
✓︎ test number 5

Summary
5/5 tests passed
Loading

0 comments on commit 4a855c6

Please sign in to comment.