diff --git a/README.markdown b/README.markdown index fb9071e..bbf1d38 100644 --- a/README.markdown +++ b/README.markdown @@ -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) ``` @@ -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. diff --git a/doctest-parallel.cabal b/doctest-parallel.cabal index 526a1b5..e772824 100644 --- a/doctest-parallel.cabal +++ b/doctest-parallel.cabal @@ -142,6 +142,7 @@ library spectests-modules ModuleIsolation.TestA ModuleIsolation.TestB ModuleOptions.Foo + NonExposedModule.Exposed Multiline.Multiline PropertyBool.Foo PropertyBoolWithTypeSignature.Foo @@ -165,6 +166,8 @@ library spectests-modules TrailingWhitespace.Foo WithCbits.Bar WithCInclude.Bar + other-modules: + NonExposedModule.NoImplicitImport test-suite spectests main-is: Spec.hs diff --git a/example/README.md b/example/README.md index 9f4fa9c..983497a 100644 --- a/example/README.md +++ b/example/README.md @@ -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 @@ -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) diff --git a/src/Test/DocTest/Internal/Options.hs b/src/Test/DocTest/Internal/Options.hs index 462a778..ea24ca6 100644 --- a/src/Test/DocTest/Internal/Options.hs +++ b/src/Test/DocTest/Internal/Options.hs @@ -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" @@ -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)" , "" @@ -107,6 +109,9 @@ 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 @@ -114,6 +119,7 @@ defaultModuleConfig = ModuleConfig { cfgPreserveIt = False , cfgRandomizeOrder = False , cfgSeed = Nothing + , cfgImplicitModuleImport = True } defaultConfig :: Config @@ -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 diff --git a/src/Test/DocTest/Internal/Runner.hs b/src/Test/DocTest/Internal/Runner.hs index 9e90630..7e2d70e 100644 --- a/src/Test/DocTest/Internal/Runner.hs +++ b/src/Test/DocTest/Internal/Runner.hs @@ -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) @@ -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) ) @@ -200,6 +200,10 @@ 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 @@ -207,8 +211,8 @@ runModule modConfig0 implicitPrelude ghciArgs output mod_ = 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 @@ -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 @@ -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 @@ -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'" diff --git a/test/MainSpec.hs b/test/MainSpec.hs index 43a92ec..efe81f8 100644 --- a/test/MainSpec.hs +++ b/test/MainSpec.hs @@ -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) diff --git a/test/integration/NonExposedModule/Exposed.hs b/test/integration/NonExposedModule/Exposed.hs new file mode 100644 index 0000000..1fb0e37 --- /dev/null +++ b/test/integration/NonExposedModule/Exposed.hs @@ -0,0 +1,3 @@ +module NonExposedModule.Exposed (foo) where + +import NonExposedModule.NoImplicitImport (foo) diff --git a/test/integration/NonExposedModule/NoImplicitImport.hs b/test/integration/NonExposedModule/NoImplicitImport.hs new file mode 100644 index 0000000..d58e2fa --- /dev/null +++ b/test/integration/NonExposedModule/NoImplicitImport.hs @@ -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