Skip to content

Commit

Permalink
Add --no-implicit-module-import
Browse files Browse the repository at this point in the history
Make runner omit the usual pre-test module import. This can be used to
test functions from non-exposed modules.

Closes #10
  • Loading branch information
martijnbastiaan committed Jan 5, 2022
1 parent 58458a8 commit c2a5381
Show file tree
Hide file tree
Showing 8 changed files with 63 additions and 10 deletions.
20 changes: 17 additions & 3 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,22 @@ You _hide_ the import of `Prelude` by using:
You can override command line flags per module by using a module annotation. For example, if you know a specific module does not support test order randomization, you can disable it with:

```haskell
{-# ANN module "--no-randomize-order" #-}
{-# ANN module "doctest-parallel: --no-randomize-order" #-}
```

## Test non-exposed modules
Generally, `doctest-parallel` cannot test binders that are part of non-exposed modules, unless they are re-exported from exposed modules. By default `doctest-parallel` will fail to do so (and report an error message), because it doesn't track whether functions are re-exported in such a way. To test a re-exported function, add the following to the _non-exposed_ module:

```haskell
{-# ANN module "doctest-parallel: --no-implicit-module-import" #-}
```

This makes `doctest-parallel` omit the usual module import at the start of a test.

Then, before a test -or in `$setup`- add:

```haskell
>>> import Exposed.Module (someFunction)
```


Expand All @@ -307,9 +322,8 @@ This is a fork of [sol/doctest](https://github.com/sol/doctest) that allows runn
* A minor change: it does not count lines in setup blocks as test cases
* A minor change: the testsuite has been ported to v2 commands

There are two downsides to using this project:
AFAIK there's only one downside to using this project:

* Examples in non-exposed modules cannot be tested (but will nonetheless be detected and consequently fail)
* Use of conditionals in a cabal file as well as CPP flags will be ignored (TODO?)

All in all, you can expect `doctest-parallel` to run about 1 or 2 orders of magnitude faster than `doctest` for large projects.
Expand Down
3 changes: 3 additions & 0 deletions doctest-parallel.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ library spectests-modules
ModuleIsolation.TestA
ModuleIsolation.TestB
ModuleOptions.Foo
NonExposedModule.Exposed
Multiline.Multiline
PropertyBool.Foo
PropertyBoolWithTypeSignature.Foo
Expand All @@ -165,6 +166,8 @@ library spectests-modules
TrailingWhitespace.Foo
WithCbits.Bar
WithCInclude.Bar
other-modules:
NonExposedModule.NoImplicitImport

test-suite spectests
main-is: Spec.hs
Expand Down
2 changes: 2 additions & 0 deletions example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ Usage:
Options:
-jN number of threads to use
† --implicit-module-import import module before testing it (default)
† --randomize-order randomize order in which tests are run
† --seed=N use a specific seed to randomize test order
† --preserve-it preserve the `it` variable between examples
Expand All @@ -91,6 +92,7 @@ Options:
--info output machine-readable version information and exit
Supported inverted options:
† --no-implicit-module-import
† --no-randomize-order (default)
† --no-preserve-it (default)
Expand Down
8 changes: 8 additions & 0 deletions src/Test/DocTest/Internal/Options.hs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ usage = unlines [
, ""
, "Options:"
, " -jN number of threads to use"
, "† --implicit-module-import import module before testing it (default)"
, "† --randomize-order randomize order in which tests are run"
, "† --seed=N use a specific seed to randomize test order"
, "† --preserve-it preserve the `it` variable between examples"
Expand All @@ -45,6 +46,7 @@ usage = unlines [
, " --info output machine-readable version information and exit"
, ""
, "Supported inverted options:"
, "† --no-implicit-module-import"
, "† --no-randomize-order (default)"
, "† --no-preserve-it (default)"
, ""
Expand Down Expand Up @@ -107,13 +109,17 @@ data ModuleConfig = ModuleConfig
-- ^ Initialize random number generator used to randomize test cases when
-- 'cfgRandomizeOrder' is set. If set to 'Nothing', a random seed is picked
-- from a system RNG source on startup.
, cfgImplicitModuleImport :: Bool
-- ^ Import a module before testing it. Can be disabled to enabled to test
-- non-exposed modules.
} deriving (Show, Eq, Generic, NFData)

defaultModuleConfig :: ModuleConfig
defaultModuleConfig = ModuleConfig
{ cfgPreserveIt = False
, cfgRandomizeOrder = False
, cfgSeed = Nothing
, cfgImplicitModuleImport = True
}

defaultConfig :: Config
Expand Down Expand Up @@ -145,6 +151,8 @@ parseModuleOption config arg =
"--no-randomize-order" -> Just config{cfgRandomizeOrder=False}
"--preserve-it" -> Just config{cfgPreserveIt=True}
"--no-preserve-it" -> Just config{cfgPreserveIt=False}
"--implicit-module-import" -> Just config{cfgImplicitModuleImport=True}
"--no-implicit-module-import" -> Just config{cfgImplicitModuleImport=False}
('-':_) | Just n <- parseSeed arg -> Just config{cfgSeed=Just n}
_ -> Nothing

Expand Down
23 changes: 16 additions & 7 deletions src/Test/DocTest/Internal/Runner.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Control.Monad hiding (forM_)
import Data.Foldable (forM_)
import Data.Function (on)
import Data.List (sortBy)
import Data.Maybe (fromMaybe)
import Data.Maybe (fromMaybe, maybeToList)
import GHC.Conc (numCapabilities)
import System.IO (hPutStrLn, hPutStr, stderr, hIsTerminalDevice)
import System.Random (randoms, mkStdGen)
Expand All @@ -26,7 +26,7 @@ import qualified Test.DocTest.Internal.Interpreter as Interpreter
import Test.DocTest.Internal.Parse
import Test.DocTest.Internal.Options
( ModuleName, ModuleConfig (cfgPreserveIt), cfgSeed, cfgPreserveIt
, cfgRandomizeOrder, parseLocatedModuleOptions)
, cfgRandomizeOrder, cfgImplicitModuleImport, parseLocatedModuleOptions)
import Test.DocTest.Internal.Location
import Test.DocTest.Internal.Property
( runProperty, PropertyResult(Failure, Success, Error) )
Expand Down Expand Up @@ -200,15 +200,19 @@ runModule modConfig0 implicitPrelude ghciArgs output mod_ = do
| cfgRandomizeOrder modConfig3 = shuffle seed examples0
| otherwise = examples0

importModule
| cfgImplicitModuleImport modConfig3 = Just (":m +" ++ module_)
| otherwise = Nothing

preserveIt = cfgPreserveIt modConfig3
seed = fromMaybe 0 (cfgSeed modConfig3) -- Should have been set already

reload repl = do
void $ Interpreter.safeEval repl ":reload"
mapM_ (Interpreter.safeEval repl) $
if implicitPrelude
then [":m Prelude", importModule]
else [":m +" ++ module_]
then ":m Prelude" : maybeToList importModule
else maybeToList importModule

when preserveIt $
-- Evaluate a dumb expression to populate the 'it' variable NOTE: This is
Expand All @@ -225,7 +229,11 @@ runModule modConfig0 implicitPrelude ghciArgs output mod_ = do

Interpreter.withInterpreter ghciArgs $ \repl -> withCP65001 $ do
-- Try to import this module, if it fails, something is off
importResult <- Interpreter.safeEval repl importModule
importResult <-
case importModule of
Nothing -> pure (Right "")
Just i -> Interpreter.safeEval repl i

case importResult of
Right "" -> do
-- Run setup group
Expand All @@ -251,7 +259,6 @@ runModule modConfig0 implicitPrelude ghciArgs output mod_ = do
where
Module module_ setup examples0 modArgs = mod_
modConfig2 = parseLocatedModuleOptions module_ modConfig0 modArgs
importModule = ":m +" ++ module_

data ReportUpdate
= UpdateSuccess FromSetup Location
Expand Down Expand Up @@ -326,7 +333,9 @@ reportImportError :: ModuleName -> Report ()
reportImportError modName = do
report ("Could not import module: " <> modName <> ". This can be caused by a number of issues: ")
report ""
report " 1. A module found by GHC contained tests, but was not in 'exposed-modules'."
report " 1. A module found by GHC contained tests, but was not in 'exposed-modules'. If you want"
report " to test non-exposed modules follow the instructions here:"
report " https://github.com/martijnbastiaan/doctest-parallel#test-non-exposed-modules"
report ""
report " 2. For Cabal users: Cabal did not generate a GHC environment file. Either:"
report " * Run with '--write-ghc-environment-files=always'"
Expand Down
4 changes: 4 additions & 0 deletions test/MainSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,7 @@ spec = do
it "sets module level options" $ do
doctest ["ModuleOptions.Foo"]
(cases 5)

it "succeeds for non-exposed modules if --no-implicit-module-import is set" $ do
doctest ["NonExposedModule.NoImplicitImport"]
(cases 2)
3 changes: 3 additions & 0 deletions test/integration/NonExposedModule/Exposed.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module NonExposedModule.Exposed (foo) where

import NonExposedModule.NoImplicitImport (foo)
10 changes: 10 additions & 0 deletions test/integration/NonExposedModule/NoImplicitImport.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module NonExposedModule.NoImplicitImport where

{-# ANN module "doctest-parallel: --no-implicit-module-import" #-}

-- |
-- >>> import NonExposedModule.Exposed (foo)
-- >>> foo 7
-- 14
foo :: Int -> Int
foo a = a + a

0 comments on commit c2a5381

Please sign in to comment.