packages feed

doctest-parallel 0.2.6 → 0.3.0

raw patch · 19 files changed

+891/−438 lines, 19 filesdep −extradep −hspec-discoverdep −prettysetup-changedPVP ok

version bump matches the API change (PVP)

Dependencies removed: extra, hspec-discover, pretty

API changes (from Hackage documentation)

- Language.Haskell.GhciWrapper: Config :: String -> Bool -> Bool -> Config
- Language.Haskell.GhciWrapper: [configGhci] :: Config -> String
- Language.Haskell.GhciWrapper: [configIgnoreDotGhci] :: Config -> Bool
- Language.Haskell.GhciWrapper: [configVerbose] :: Config -> Bool
- Language.Haskell.GhciWrapper: close :: Interpreter -> IO ()
- Language.Haskell.GhciWrapper: data Config
- Language.Haskell.GhciWrapper: data Interpreter
- Language.Haskell.GhciWrapper: defaultConfig :: Config
- Language.Haskell.GhciWrapper: eval :: Interpreter -> String -> IO String
- Language.Haskell.GhciWrapper: evalEcho :: Interpreter -> String -> IO String
- Language.Haskell.GhciWrapper: evalIt :: Interpreter -> String -> IO String
- Language.Haskell.GhciWrapper: instance GHC.Classes.Eq Language.Haskell.GhciWrapper.Config
- Language.Haskell.GhciWrapper: instance GHC.Show.Show Language.Haskell.GhciWrapper.Config
- Language.Haskell.GhciWrapper: new :: Config -> [String] -> IO Interpreter
- Test.DocTest.Internal.Options: [cfgQuiet] :: Config -> Bool
- Test.DocTest.Internal.Options: [cfgVerbose] :: Config -> Bool
- Test.DocTest.Internal.Runner: UpdateStart :: Location -> Expression -> String -> ReportUpdate
- Test.DocTest.Internal.Runner: UpdateVerbose :: String -> ReportUpdate
- Test.DocTest.Internal.Runner: [reportStateQuiet] :: ReportState -> Bool
- Test.DocTest.Internal.Runner: [reportStateVerbose] :: ReportState -> Bool
- Test.DocTest.Internal.Runner: reportStart :: Location -> Expression -> String -> Report ()
- Test.DocTest.Internal.Runner: verboseReport :: String -> Report ()
+ Test.DocTest.Internal.GhciWrapper: Config :: String -> Bool -> Bool -> Config
+ Test.DocTest.Internal.GhciWrapper: [configGhci] :: Config -> String
+ Test.DocTest.Internal.GhciWrapper: [configIgnoreDotGhci] :: Config -> Bool
+ Test.DocTest.Internal.GhciWrapper: [configVerbose] :: Config -> Bool
+ Test.DocTest.Internal.GhciWrapper: close :: Interpreter -> IO ()
+ Test.DocTest.Internal.GhciWrapper: data Config
+ Test.DocTest.Internal.GhciWrapper: data Interpreter
+ Test.DocTest.Internal.GhciWrapper: defaultConfig :: Config
+ Test.DocTest.Internal.GhciWrapper: eval :: Interpreter -> String -> IO String
+ Test.DocTest.Internal.GhciWrapper: evalEcho :: Interpreter -> String -> IO String
+ Test.DocTest.Internal.GhciWrapper: evalIt :: Interpreter -> String -> IO String
+ Test.DocTest.Internal.GhciWrapper: instance GHC.Classes.Eq Test.DocTest.Internal.GhciWrapper.Config
+ Test.DocTest.Internal.GhciWrapper: instance GHC.Show.Show Test.DocTest.Internal.GhciWrapper.Config
+ Test.DocTest.Internal.GhciWrapper: new :: DebugLogger -> Config -> [String] -> IO Interpreter
+ Test.DocTest.Internal.Logging: Debug :: LogLevel
+ Test.DocTest.Internal.Logging: Error :: LogLevel
+ Test.DocTest.Internal.Logging: Info :: LogLevel
+ Test.DocTest.Internal.Logging: Verbose :: LogLevel
+ Test.DocTest.Internal.Logging: Warning :: LogLevel
+ Test.DocTest.Internal.Logging: data LogLevel
+ Test.DocTest.Internal.Logging: formatLog :: ThreadId -> LogLevel -> String -> String
+ Test.DocTest.Internal.Logging: formatLogHere :: LogLevel -> String -> IO String
+ Test.DocTest.Internal.Logging: instance Control.DeepSeq.NFData Test.DocTest.Internal.Logging.LogLevel
+ Test.DocTest.Internal.Logging: instance GHC.Classes.Eq Test.DocTest.Internal.Logging.LogLevel
+ Test.DocTest.Internal.Logging: instance GHC.Classes.Ord Test.DocTest.Internal.Logging.LogLevel
+ Test.DocTest.Internal.Logging: instance GHC.Enum.Bounded Test.DocTest.Internal.Logging.LogLevel
+ Test.DocTest.Internal.Logging: instance GHC.Enum.Enum Test.DocTest.Internal.Logging.LogLevel
+ Test.DocTest.Internal.Logging: instance GHC.Generics.Generic Test.DocTest.Internal.Logging.LogLevel
+ Test.DocTest.Internal.Logging: instance GHC.Show.Show Test.DocTest.Internal.Logging.LogLevel
+ Test.DocTest.Internal.Logging: justifyLeft :: Int -> a -> [a] -> [a]
+ Test.DocTest.Internal.Logging: log :: (?verbosity :: LogLevel) => LogLevel -> String -> IO ()
+ Test.DocTest.Internal.Logging: noLogger :: DebugLogger
+ Test.DocTest.Internal.Logging: parseLogLevel :: String -> Maybe LogLevel
+ Test.DocTest.Internal.Logging: shouldLog :: (?verbosity :: LogLevel) => LogLevel -> Bool
+ Test.DocTest.Internal.Logging: showJustifiedLogLevel :: LogLevel -> String
+ Test.DocTest.Internal.Logging: type DebugLogger = String -> IO ()
+ Test.DocTest.Internal.Nix: compilerVersionStr :: String
+ Test.DocTest.Internal.Nix: findCabalProjectRoot :: MaybeT IO FilePath
+ Test.DocTest.Internal.Nix: findDirectoryUp :: (FilePath -> IO (Maybe a)) -> MaybeT IO a
+ Test.DocTest.Internal.Nix: findDirectoryUpPredicate :: (FilePath -> IO Bool) -> MaybeT IO FilePath
+ Test.DocTest.Internal.Nix: findLocalPackageDb :: MaybeT IO FilePath
+ Test.DocTest.Internal.Nix: fullCompilerVersion :: Version
+ Test.DocTest.Internal.Nix: getGlobalPackageDb :: IO String
+ Test.DocTest.Internal.Nix: getLocalCabalPackageDbArgs :: IO [String]
+ Test.DocTest.Internal.Nix: getLocalNixPackageDbArgs :: IO [String]
+ Test.DocTest.Internal.Nix: getNixGhciArgs :: IO [String]
+ Test.DocTest.Internal.Nix: inNixBuild :: IO Bool
+ Test.DocTest.Internal.Nix: inNixShell :: IO Bool
+ Test.DocTest.Internal.Options: [cfgLogLevel] :: Config -> LogLevel
+ Test.DocTest.Internal.Options: parseLogLevel :: String -> Maybe LogLevel
+ Test.DocTest.Internal.Runner: UpdateLog :: LogLevel -> String -> ReportUpdate
- Test.DocTest: setSeed :: Bool -> ModuleConfig -> IO ModuleConfig
+ Test.DocTest: setSeed :: (?verbosity :: LogLevel) => ModuleConfig -> IO ModuleConfig
- Test.DocTest.Internal.Interpreter: withInterpreter :: [String] -> (Interpreter -> IO a) -> IO a
+ Test.DocTest.Internal.Interpreter: withInterpreter :: DebugLogger -> [String] -> (Interpreter -> IO a) -> IO a
- Test.DocTest.Internal.Options: Config :: Bool -> [ModuleName] -> Maybe Int -> Bool -> ModuleConfig -> Bool -> Config
+ Test.DocTest.Internal.Options: Config :: LogLevel -> [ModuleName] -> Maybe Int -> ModuleConfig -> Bool -> Config
- Test.DocTest.Internal.Runner: ReportState :: Int -> Bool -> Bool -> Bool -> Summary -> ReportState
+ Test.DocTest.Internal.Runner: ReportState :: Int -> Bool -> Summary -> ReportState
- Test.DocTest.Internal.Runner: UpdateSuccess :: FromSetup -> Location -> ReportUpdate
+ Test.DocTest.Internal.Runner: UpdateSuccess :: FromSetup -> ReportUpdate
- Test.DocTest.Internal.Runner: makeThreadPool :: Int -> (Chan ReportUpdate -> Module [Located DocTest] -> IO ()) -> IO (Chan (Module [Located DocTest]), Chan ReportUpdate)
+ Test.DocTest.Internal.Runner: makeThreadPool :: Int -> (Chan (ThreadId, ReportUpdate) -> Module [Located DocTest] -> IO ()) -> IO (Chan (Module [Located DocTest]), Chan (ThreadId, ReportUpdate))
- Test.DocTest.Internal.Runner: report :: String -> Report ()
+ Test.DocTest.Internal.Runner: report :: (?verbosity :: LogLevel, ?threadId :: ThreadId) => LogLevel -> String -> Report ()
- Test.DocTest.Internal.Runner: reportError :: FromSetup -> Location -> Expression -> String -> Report ()
+ Test.DocTest.Internal.Runner: reportError :: (?verbosity :: LogLevel, ?threadId :: ThreadId) => FromSetup -> Location -> Expression -> String -> Report ()
- Test.DocTest.Internal.Runner: reportFailure :: FromSetup -> Location -> Expression -> [String] -> Report ()
+ Test.DocTest.Internal.Runner: reportFailure :: (?verbosity :: LogLevel, ?threadId :: ThreadId) => FromSetup -> Location -> Expression -> [String] -> Report ()
- Test.DocTest.Internal.Runner: reportImportError :: ModuleName -> Either String String -> Report ()
+ Test.DocTest.Internal.Runner: reportImportError :: (?verbosity :: LogLevel, ?threadId :: ThreadId) => ModuleName -> Either String String -> Report ()
- Test.DocTest.Internal.Runner: reportInternalError :: FromSetup -> Module a -> SomeException -> Report ()
+ Test.DocTest.Internal.Runner: reportInternalError :: (?verbosity :: LogLevel, ?threadId :: ThreadId) => FromSetup -> Module a -> SomeException -> Report ()
- Test.DocTest.Internal.Runner: reportOptionError :: Location -> String -> Report ()
+ Test.DocTest.Internal.Runner: reportOptionError :: (?verbosity :: LogLevel, ?threadId :: ThreadId) => Location -> String -> Report ()
- Test.DocTest.Internal.Runner: reportProgress :: Report ()
+ Test.DocTest.Internal.Runner: reportProgress :: (?verbosity :: LogLevel) => Report ()
- Test.DocTest.Internal.Runner: reportSuccess :: FromSetup -> Location -> Report ()
+ Test.DocTest.Internal.Runner: reportSuccess :: (?verbosity :: LogLevel, ?threadId :: ThreadId) => FromSetup -> Report ()
- Test.DocTest.Internal.Runner: report_ :: String -> Report ()
+ Test.DocTest.Internal.Runner: report_ :: (?verbosity :: LogLevel) => LogLevel -> String -> Report ()
- Test.DocTest.Internal.Runner: runExampleGroup :: FromSetup -> Bool -> Interpreter -> Chan ReportUpdate -> [Located Interaction] -> IO Bool
+ Test.DocTest.Internal.Runner: runExampleGroup :: FromSetup -> Bool -> Interpreter -> (ReportUpdate -> IO ()) -> [Located Interaction] -> IO Bool
- Test.DocTest.Internal.Runner: runModule :: ModuleConfig -> Bool -> [String] -> Chan ReportUpdate -> Module [Located DocTest] -> IO ()
+ Test.DocTest.Internal.Runner: runModule :: ModuleConfig -> Bool -> [String] -> Chan (ThreadId, ReportUpdate) -> Module [Located DocTest] -> IO ()
- Test.DocTest.Internal.Runner: runModules :: ModuleConfig -> Maybe Int -> Bool -> Bool -> [String] -> Bool -> [Module [Located DocTest]] -> IO Summary
+ Test.DocTest.Internal.Runner: runModules :: (?verbosity :: LogLevel) => ModuleConfig -> Maybe Int -> Bool -> [String] -> [Module [Located DocTest]] -> IO Summary
- Test.DocTest.Internal.Runner: runTestGroup :: FromSetup -> Bool -> Interpreter -> IO () -> Chan ReportUpdate -> [Located DocTest] -> IO Bool
+ Test.DocTest.Internal.Runner: runTestGroup :: FromSetup -> Bool -> Interpreter -> IO () -> (ReportUpdate -> IO ()) -> [Located DocTest] -> IO Bool

Files

CHANGES.markdown view
@@ -1,3 +1,8 @@+# 0.3.0+ * Add support for Nix shell environments ([#58](https://github.com/martijnbastiaan/doctest-parallel/pull/58))+ * `Language.Haskell.GhciWrapper` has been moved to `Test.DocTest.Internal.GhciWrapper`. This module was never intended to be part of the public API. ([#61](https://github.com/martijnbastiaan/doctest-parallel/pull/61))+ * Add more elaborate debug options. You can now pass `--log-level=LEVEL` where `level` is one of `debug`, `verbose`, `info`, `warning`, or `error`. ([#14](https://github.com/martijnbastiaan/doctest-parallel/issues/14))+ # 0.2.6   * `getNumProcessors` is now used to detect the (default) number of GHCi subprocesses to spawn. This should more reliably use all of a system's resources. Fixes [#53](https://github.com/martijnbastiaan/doctest-parallel/issues/53).   * Add Nix support. If the environment variable `NIX_BUILD_TOP` is present an extra package database is added to `GHC_PACKAGE_PATH`. This isn't expected to break existing builds, but if it does consider passing `--no-nix`. ([#34](https://github.com/martijnbastiaan/doctest-parallel/issues/34))
− Setup.lhs
@@ -1,3 +0,0 @@-#!/usr/bin/env runhaskell-> import Distribution.Simple-> main = defaultMain
doctest-parallel.cabal view
@@ -1,7 +1,7 @@ cabal-version: 2.0  name:           doctest-parallel-version:        0.2.6+version:        0.3.0 synopsis:       Test interactive Haskell examples description:    The doctest program checks examples in source code comments.  It is modeled                 after doctest for Python (<https://docs.python.org/3/library/doctest.html>).@@ -66,26 +66,28 @@  library   ghc-options: -Wall-  hs-source-dirs:-      src-      ghci-wrapper/src+  hs-source-dirs: src   exposed-modules:       Test.DocTest       Test.DocTest.Helpers       Test.DocTest.Internal.Extract+      Test.DocTest.Internal.GhciWrapper       Test.DocTest.Internal.GhcUtil       Test.DocTest.Internal.Interpreter       Test.DocTest.Internal.Location+      Test.DocTest.Internal.Logging+      Test.DocTest.Internal.Nix       Test.DocTest.Internal.Options       Test.DocTest.Internal.Parse       Test.DocTest.Internal.Property       Test.DocTest.Internal.Runner       Test.DocTest.Internal.Runner.Example       Test.DocTest.Internal.Util-      Language.Haskell.GhciWrapper   autogen-modules:       Paths_doctest_parallel   other-modules:+      Control.Monad.Extra+      Data.List.Extra       Paths_doctest_parallel   build-depends:       Cabal >= 2.4 && < 3.9@@ -97,11 +99,9 @@     , deepseq     , directory     , exceptions-    , extra     , filepath     , ghc >=8.2 && <9.5     , ghc-paths >=0.1.0.9-    , pretty     , process     , random >= 1.2     , syb >=0.3@@ -176,6 +176,7 @@   main-is: Spec.hs   other-modules:       ExtractSpec+      GhciWrapperSpec       InterpreterSpec       LocationSpec       MainSpec@@ -191,8 +192,6 @@   cpp-options: -DTEST   hs-source-dirs:       test-  build-tool-depends:-    hspec-discover:hspec-discover   build-depends:       HUnit     , QuickCheck >=2.13.1@@ -209,7 +208,6 @@     , ghc-paths >=0.1.0.9     , hspec >=2.3.0     , hspec-core >=2.3.0-    , hspec-discover     , mockery     , process     , setenv
− ghci-wrapper/src/Language/Haskell/GhciWrapper.hs
@@ -1,156 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE MultiWayIf #-}--module Language.Haskell.GhciWrapper (-  Interpreter-, Config(..)-, defaultConfig-, new-, close-, eval-, evalIt-, evalEcho-) where--import           System.IO hiding (stdin, stdout, stderr)-import           System.Process-import           System.Exit-import           Control.Monad-import           Control.Exception-import           Data.List-import           Data.Maybe--data Config = Config {-  configGhci :: String-, configVerbose :: Bool-, configIgnoreDotGhci :: Bool-} deriving (Eq, Show)--defaultConfig :: Config-defaultConfig = Config {-  configGhci = "ghci"-, configVerbose = False-, configIgnoreDotGhci = True-}---- | Truly random marker, used to separate expressions.------ IMPORTANT: This module relies upon the fact that this marker is unique.  It--- has been obtained from random.org.  Do not expect this module to work--- properly, if you reuse it for any purpose!-marker :: String-marker = show "dcbd2a1e20ae519a1c7714df2859f1890581d57fac96ba3f499412b2f5c928a1"--itMarker :: String-itMarker = "d42472243a0e6fc481e7514cbc9eb08812ed48daa29ca815844d86010b1d113a"--data Interpreter = Interpreter {-    hIn  :: Handle-  , hOut :: Handle-  , process :: ProcessHandle-  }--new :: Config -> [String] -> IO Interpreter-new Config{..} args_ = do-  (Just stdin_, Just stdout_, Nothing, processHandle ) <- createProcess $ (proc configGhci args) {std_in = CreatePipe, std_out = CreatePipe, std_err = Inherit}-  setMode stdin_-  setMode stdout_-  let interpreter = Interpreter {hIn = stdin_, hOut = stdout_, process = processHandle}-  _ <- eval interpreter "import qualified System.IO"-  _ <- eval interpreter "import qualified GHC.IO.Handle"-  -- The buffering of stdout and stderr is NoBuffering-  _ <- eval interpreter "GHC.IO.Handle.hDuplicateTo System.IO.stdout System.IO.stderr"-  -- Now the buffering of stderr is BlockBuffering Nothing-  -- In this situation, GHC 7.7 does not flush the buffer even when-  -- error happens.-  _ <- eval interpreter "GHC.IO.Handle.hSetBuffering System.IO.stdout GHC.IO.Handle.LineBuffering"-  _ <- eval interpreter "GHC.IO.Handle.hSetBuffering System.IO.stderr GHC.IO.Handle.LineBuffering"--  -- this is required on systems that don't use utf8 as default encoding (e.g.-  -- Windows)-  _ <- eval interpreter "GHC.IO.Handle.hSetEncoding System.IO.stdout GHC.IO.Handle.utf8"-  _ <- eval interpreter "GHC.IO.Handle.hSetEncoding System.IO.stderr GHC.IO.Handle.utf8"--  _ <- eval interpreter ":m - System.IO"-  _ <- eval interpreter ":m - GHC.IO.Handle"--  return interpreter-  where-    args = args_ ++ catMaybes [-        if configIgnoreDotGhci then Just "-ignore-dot-ghci" else Nothing-      , if configVerbose then Nothing else Just "-v0"-      ]-    setMode h = do-      hSetBinaryMode h False-      hSetBuffering h LineBuffering-      hSetEncoding h utf8--close :: Interpreter -> IO ()-close repl = do-  hClose $ hIn repl--  -- It is crucial not to close `hOut` before calling `waitForProcess`,-  -- otherwise ghci may not cleanly terminate on SIGINT (ctrl-c) and hang-  -- around consuming 100% CPU.  This happens when ghci tries to print-  -- something to stdout in its signal handler (e.g. when it is blocked in-  -- threadDelay it writes "Interrupted." on SIGINT).-  e <- waitForProcess $ process repl-  hClose $ hOut repl--  when (e /= ExitSuccess) $ do-    throwIO (userError $ "Language.Haskell.GhciWrapper.close: Interpreter exited with an error (" ++ show e ++ ")")--putExpression :: Interpreter -> Bool -> String -> IO ()-putExpression Interpreter{hIn = stdin} preserveIt e = do-  hPutStrLn stdin e-  when preserveIt $ hPutStrLn stdin $ "let " ++ itMarker ++ " = it"-  hPutStrLn stdin (marker ++ " :: Data.String.String")-  when preserveIt $ hPutStrLn stdin $ "let it = " ++ itMarker-  hFlush stdin--getResult :: Bool -> Interpreter -> IO String-getResult echoMode Interpreter{hOut = stdout} = go-  where-    go = do-      line <- hGetLine stdout--      if-        | marker `isSuffixOf` line -> do-          let xs = stripMarker line-          echo xs-          return xs-#if __GLASGOW_HASKELL__ < 810-        -- For some (happy) reason newer GHCs don't decide to print this-        -- message - or at least we don't see it.-        | "Loaded package environment from " `isPrefixOf` line -> do-          go-#endif-        | otherwise -> do-          echo (line ++ "\n")-          result <- go-          return (line ++ "\n" ++ result)-    stripMarker l = take (length l - length marker) l--    echo :: String -> IO ()-    echo-      | echoMode = putStr-      | otherwise = (const $ return ())---- | Evaluate an expression-eval :: Interpreter -> String -> IO String-eval repl expr = do-  putExpression repl False expr-  getResult False repl---- | Like 'eval', but try to preserve the @it@ variable-evalIt :: Interpreter -> String -> IO String-evalIt repl expr = do-  putExpression repl True expr-  getResult False repl---- | Evaluate an expression-evalEcho :: Interpreter -> String -> IO String-evalEcho repl expr = do-  putExpression repl False expr-  getResult True repl
+ src/Control/Monad/Extra.hs view
@@ -0,0 +1,9 @@+module Control.Monad.Extra where++-- | Like @if@, but where the test can be monadic.+ifM :: Monad m => m Bool -> m a -> m a -> m a+ifM predicate t f = do b <- predicate; if b then t else f++-- | Like 'when', but where the test can be monadic.+whenM :: Monad m => m Bool -> m () -> m ()+whenM b t = ifM b t (pure ())
+ src/Data/List/Extra.hs view
@@ -0,0 +1,21 @@+module Data.List.Extra (trim) where++import Data.Char (isSpace)+import Data.List (dropWhileEnd)++-- | Remove spaces from either side of a string. A combination of 'trimEnd' and 'trimStart'.+--+-- > trim      "  hello   " == "hello"+-- > trimStart "  hello   " == "hello   "+-- > trimEnd   "  hello   " == "  hello"+-- > \s -> trim s == trimEnd (trimStart s)+trim :: String -> String+trim = trimEnd . trimStart++-- | Remove spaces from the start of a string, see 'trim'.+trimStart :: String -> String+trimStart = dropWhile isSpace++-- | Remove spaces from the end of a string, see 'trim'.+trimEnd :: String -> String+trimEnd = dropWhileEnd isSpace
src/Test/DocTest.hs view
@@ -1,5 +1,5 @@ {-# LANGUAGE CPP #-}-{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE ImplicitParams #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ViewPatterns #-}@@ -21,16 +21,11 @@ import           Prelude.Compat  import qualified Data.Set as Set+import           Data.List (intercalate)  import           Control.Monad (unless)-import           Control.Monad.Compat (when)-import           Control.Monad.Extra (whenM)-import           Data.List (isInfixOf)-import           Data.Maybe (fromMaybe)-import           System.Directory (doesDirectoryExist, makeAbsolute)-import           System.Environment (lookupEnv, setEnv)+import           Control.Monad.Extra (ifM) import           System.Exit (exitFailure)-import           System.FilePath ((</>)) import           System.IO import           System.Random (randomIO) @@ -45,6 +40,7 @@ import Test.DocTest.Internal.Parse import Test.DocTest.Internal.Options import Test.DocTest.Internal.Runner+import Test.DocTest.Internal.Nix (getNixGhciArgs)  -- Cabal import Distribution.Simple@@ -54,7 +50,10 @@ import Test.DocTest.Helpers   ( Library (libDefaultExtensions), extractCabalLibrary, findCabalPackage   , libraryToGhciArgs )+import Test.DocTest.Internal.Logging (LogLevel(..)) +import qualified Test.DocTest.Internal.Logging as Logging+ -- | Run doctest with given list of arguments. -- -- Example:@@ -123,56 +122,39 @@   nonExistingMods = Set.toList (wantedMods1 `Set.difference` allMods1)   isSpecifiedMod Module{moduleName} = moduleName `Set.member` wantedMods1 -setSeed :: Bool -> ModuleConfig -> IO ModuleConfig-setSeed quiet cfg@ModuleConfig{cfgRandomizeOrder=True, cfgSeed=Nothing} = do+setSeed :: (?verbosity :: LogLevel) => ModuleConfig -> IO ModuleConfig+setSeed cfg@ModuleConfig{cfgRandomizeOrder=True, cfgSeed=Nothing} = do   -- Using an absolute number to prevent copy+paste errors   seed <- abs <$> randomIO-  unless quiet $-    putStrLn ("Using freshly generated seed to randomize test order: " <> show seed)+  Logging.log Info ("Using freshly generated seed to randomize test order: " <> show seed)   pure cfg{cfgSeed=Just seed}-setSeed _quiet cfg = pure cfg---- | @GHC_PACKAGE_PATH@. Here as a variable to prevent typos.-gHC_PACKAGE_PATH :: String-gHC_PACKAGE_PATH = "GHC_PACKAGE_PATH"---- | Add locally built package to @GHC_PACKAGE_PATH@ if a Nix environment is--- detected.-addLocalNixPackageToGhcPath :: IO ()-addLocalNixPackageToGhcPath = do-  lookupEnv "NIX_BUILD_TOP" >>= \case-    Nothing -> pure ()-    Just _ -> do-      pkgDb <- makeAbsolute ("dist" </> "package.conf.inplace")-      ghcPackagePath <- fromMaybe "" <$> lookupEnv gHC_PACKAGE_PATH--      -- Don't add package db if it is already mentioned on path-      unless ((pkgDb ++ ":") `isInfixOf` ghcPackagePath) $-        -- Only add package db if it exists on disk-        whenM (doesDirectoryExist pkgDb) $-          setEnv gHC_PACKAGE_PATH (pkgDb ++ ":" ++ ghcPackagePath)+setSeed cfg = pure cfg  -- | Run doctest for given library and config. Produce a summary of all tests. run :: Library -> Config -> IO Summary run lib Config{..} = do-  when cfgNix addLocalNixPackageToGhcPath+  nixGhciArgs <- ifM (pure cfgNix) getNixGhciArgs (pure [])    let     implicitPrelude = DisableExtension ImplicitPrelude `notElem` libDefaultExtensions lib     (includeArgs, moduleArgs, otherGhciArgs) = libraryToGhciArgs lib-    evalGhciArgs = otherGhciArgs ++ ["-XNoImplicitPrelude"]+    evalGhciArgs = otherGhciArgs ++ ["-XNoImplicitPrelude"] ++ nixGhciArgs+    parseGhciArgs = includeArgs ++ moduleArgs ++ otherGhciArgs ++ nixGhciArgs -    -- Nix doesn't always expose the GHC library (_specifically_ the GHC lib) even-    -- if a package lists it as a dependency. This simply always exposes it as a-    -- workaround.-    nixGhciArgs-      | cfgNix = ["-package", "ghc"]-      | otherwise = []+  let+    ?verbosity = cfgLogLevel -  modConfig <- setSeed cfgQuiet cfgModuleConfig+  modConfig <- setSeed cfgModuleConfig -  -- get examples from Haddock comments-  allModules <- getDocTests (includeArgs ++ moduleArgs ++ otherGhciArgs ++ nixGhciArgs)-  runModules-    modConfig cfgThreads cfgVerbose implicitPrelude evalGhciArgs-    cfgQuiet (filterModules cfgModules allModules)+  -- Get examples from Haddock comments+  Logging.log Verbose "Parsing comments.."+  Logging.log Debug ("Calling GHC API with: " <> unwords parseGhciArgs)+  allModules <- getDocTests parseGhciArgs++  -- Run tests+  Logging.log Verbose "Running examples.."+  let+    filteredModules = filterModules cfgModules allModules+    filteredModulesMsg = intercalate ", " (map moduleName filteredModules)+  Logging.log Debug ("Running examples in modules: " <> filteredModulesMsg)+  runModules modConfig cfgThreads implicitPrelude evalGhciArgs filteredModules
+ src/Test/DocTest/Internal/GhciWrapper.hs view
@@ -0,0 +1,180 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE MultiWayIf #-}++module Test.DocTest.Internal.GhciWrapper (+  Interpreter+, Config(..)+, defaultConfig+, new+, close+, eval+, evalIt+, evalEcho+) where++import System.IO hiding (stdin, stdout, stderr)+import System.Process+import System.Exit+import Control.Monad+import Control.Exception+import Data.List+import Data.Maybe++import Test.DocTest.Internal.Logging (DebugLogger)++data Config = Config {+  configGhci :: String+, configVerbose :: Bool+, configIgnoreDotGhci :: Bool+} deriving (Eq, Show)++defaultConfig :: Config+defaultConfig = Config {+  configGhci = "ghci"+, configVerbose = False+, configIgnoreDotGhci = True+}++-- | Truly random marker, used to separate expressions.+--+-- IMPORTANT: This module relies upon the fact that this marker is unique.  It+-- has been obtained from random.org.  Do not expect this module to work+-- properly, if you reuse it for any purpose!+marker :: String+marker = show "dcbd2a1e20ae519a1c7714df2859f1890581d57fac96ba3f499412b2f5c928a1"++itMarker :: String+itMarker = "d42472243a0e6fc481e7514cbc9eb08812ed48daa29ca815844d86010b1d113a"++data Interpreter = Interpreter {+    hIn  :: Handle+  , hOut :: Handle+  , process :: ProcessHandle+  , logger :: DebugLogger+  }++new :: DebugLogger -> Config -> [String] -> IO Interpreter+new logger Config{..} args_ = do+  logger ("Calling: " ++ unwords (configGhci:args))+  (Just stdin_, Just stdout_, Nothing, processHandle ) <- createProcess $ (proc configGhci args) {std_in = CreatePipe, std_out = CreatePipe, std_err = Inherit}+  setMode stdin_+  setMode stdout_+  let+    interpreter = Interpreter+      { hIn = stdin_+      , hOut = stdout_+      , process = processHandle+      , logger=logger+      }+  _ <- eval interpreter "import qualified System.IO"+  _ <- eval interpreter "import qualified GHC.IO.Handle"+  -- The buffering of stdout and stderr is NoBuffering+  _ <- eval interpreter "GHC.IO.Handle.hDuplicateTo System.IO.stdout System.IO.stderr"+  -- Now the buffering of stderr is BlockBuffering Nothing+  -- In this situation, GHC 7.7 does not flush the buffer even when+  -- error happens.+  _ <- eval interpreter "GHC.IO.Handle.hSetBuffering System.IO.stdout GHC.IO.Handle.LineBuffering"+  _ <- eval interpreter "GHC.IO.Handle.hSetBuffering System.IO.stderr GHC.IO.Handle.LineBuffering"++  -- this is required on systems that don't use utf8 as default encoding (e.g.+  -- Windows)+  _ <- eval interpreter "GHC.IO.Handle.hSetEncoding System.IO.stdout System.IO.utf8"+  _ <- eval interpreter "GHC.IO.Handle.hSetEncoding System.IO.stderr System.IO.utf8"++  _ <- eval interpreter ":m - System.IO"+  _ <- eval interpreter ":m - GHC.IO.Handle"++  return interpreter+  where+    args = args_ ++ catMaybes [+        if configIgnoreDotGhci then Just "-ignore-dot-ghci" else Nothing+      , if configVerbose then Nothing else Just "-v0"+      ]+    setMode h = do+      hSetBinaryMode h False+      hSetBuffering h LineBuffering+      hSetEncoding h utf8++close :: Interpreter -> IO ()+close repl = do+  hClose $ hIn repl++  -- It is crucial not to close `hOut` before calling `waitForProcess`,+  -- otherwise ghci may not cleanly terminate on SIGINT (ctrl-c) and hang+  -- around consuming 100% CPU.  This happens when ghci tries to print+  -- something to stdout in its signal handler (e.g. when it is blocked in+  -- threadDelay it writes "Interrupted." on SIGINT).+  e <- waitForProcess $ process repl+  hClose $ hOut repl++  when (e /= ExitSuccess) $ do+    throwIO (userError $ "Test.DocTest.Internal.GhciWrapper.close: Interpreter exited with an error (" ++ show e ++ ")")++putExpression :: Interpreter -> Bool -> String -> IO ()+putExpression Interpreter{logger = logger, hIn = stdin} preserveIt e = do+  logger (">>> " ++ e)+  hPutStrLn stdin e++  when preserveIt $ do+    let e1 = "let " ++ itMarker ++ " = it"+    logger (">>> " ++ e1)+    hPutStrLn stdin e1++  hPutStrLn stdin (marker ++ " :: Data.String.String")++  when preserveIt $ do+    let e3 = "let it = " ++ itMarker+    logger (">>> " ++ e3)+    hPutStrLn stdin e3++  hFlush stdin++getResult :: Bool -> Interpreter -> IO String+getResult echoMode Interpreter{logger = logger, hOut = stdout} = do+  result <- go+  unless (result == mempty) $ logger result+  pure result+  where+    go = do+      line <- hGetLine stdout++      if+        | marker `isSuffixOf` line -> do+          let xs = stripMarker line+          echo xs+          return xs+#if __GLASGOW_HASKELL__ < 810+        -- For some (happy) reason newer GHCs don't decide to print this+        -- message - or at least we don't see it.+        | "Loaded package environment from " `isPrefixOf` line -> do+          go+#endif+        | otherwise -> do+          echo (line ++ "\n")+          result <- go+          return (line ++ "\n" ++ result)+    stripMarker l = take (length l - length marker) l++    echo :: String -> IO ()+    echo+      | echoMode = putStr+      | otherwise = (const $ return ())++-- | Evaluate an expression+eval :: Interpreter -> String -> IO String+eval repl expr = do+  putExpression repl False expr+  getResult False repl++-- | Like 'eval', but try to preserve the @it@ variable+evalIt :: Interpreter -> String -> IO String+evalIt repl expr = do+  putExpression repl True expr+  getResult False repl++-- | Evaluate an expression+evalEcho :: Interpreter -> String -> IO String+evalEcho repl expr = do+  putExpression repl False expr+  getResult True repl
src/Test/DocTest/Internal/Interpreter.hs view
@@ -8,22 +8,24 @@ , ghc , interpreterSupported --- exported for testing+-- * exported for testing , ghcInfo , haveInterpreterKey ) where -import           System.Process-import           System.Directory (getPermissions, executable)-import           Control.Monad-import           Control.Exception hiding (handle)-import           Data.Char-import           GHC.Paths (ghc)+import System.Process+import System.Directory (getPermissions, executable)+import Control.Monad+import Control.Exception hiding (handle)+import Data.Char+import GHC.Paths (ghc) -import           Language.Haskell.GhciWrapper+import Test.DocTest.Internal.GhciWrapper+import Test.DocTest.Internal.Logging (DebugLogger)  -- $setup--- >>> import Language.Haskell.GhciWrapper (eval)+-- >>> import Test.DocTest.Internal.GhciWrapper (eval)+-- >>> import Test.DocTest.Internal.Logging (noLogger)  haveInterpreterKey :: String haveInterpreterKey = "Have interpreter"@@ -45,13 +47,14 @@ -- -- Example: ----- >>> withInterpreter [] $ \i -> eval i "23 + 42"+-- >>> withInterpreter noLogger [] $ \i -> eval i "23 + 42" -- "65\n" withInterpreter-  :: [String]               -- ^ List of flags, passed to GHC+  :: DebugLogger            -- ^ Debug logger+  -> [String]               -- ^ List of flags, passed to GHC   -> (Interpreter -> IO a)  -- ^ Action to run   -> IO a                   -- ^ Result of action-withInterpreter flags action = do+withInterpreter logger flags action = do   let     args = flags ++ [         "--interactive"@@ -60,7 +63,7 @@       , "-fno-diagnostics-show-caret" #endif       ]-  bracket (new defaultConfig{configGhci = ghc} args) close action+  bracket (new logger defaultConfig{configGhci = ghc} args) close action  -- | Evaluate an expression; return a Left value on exceptions. --
+ src/Test/DocTest/Internal/Logging.hs view
@@ -0,0 +1,123 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE ImplicitParams #-}+{-# LANGUAGE ViewPatterns #-}++module Test.DocTest.Internal.Logging where++import Control.Applicative (Alternative((<|>)))+import Control.Concurrent (ThreadId, myThreadId)+import Control.DeepSeq (NFData)+import Data.Char (toLower, toUpper)+import Data.List (intercalate)+import GHC.Generics (Generic)+import System.IO (hPutStrLn, stderr)+import Text.Printf (printf)++-- | Convenience type alias - not used in this module, but sprinkled across the+-- project.+type DebugLogger = String -> IO ()++-- | Discards any log message+noLogger :: DebugLogger+noLogger = const (pure ())++data LogLevel+  = Debug+  -- ^ Intended for debug runs+  | Verbose+  -- ^ Intended for debug runs, but without flooding the user with internal messages+  | Info+  -- ^ Default log level - print messages user is likely wanting to see+  | Warning+  -- ^ Only print warnings+  | Error+  -- ^ Only print errors+  deriving (Show, Eq, Enum, Generic, NFData, Ord, Bounded)++-- | Case insensitive+--+-- >>> parseLogLevel "Info"+-- Just Info+-- >>> parseLogLevel "info"+-- Just Info+-- >>> parseLogLevel "errox"+-- Nothing+--+parseLogLevel :: String -> Maybe LogLevel+parseLogLevel (map toLower -> level) =+  foldl (<|>) Nothing (map go [minBound..])+ where+  go :: LogLevel -> Maybe LogLevel+  go l+    | map toLower (show l) == level = Just l+    | otherwise = Nothing++-- | Pretty print a 'LogLevel' in a justified manner, i.e., all outputs take the+-- same amount of characters to display.+--+-- >>> showJustifiedLogLevel Debug+-- "Debug  "+-- >>> showJustifiedLogLevel Verbose+-- "Verbose"+-- >>> showJustifiedLogLevel Info+-- "Info   "+-- >>> showJustifiedLogLevel Warning+-- "Warning"+-- >>> showJustifiedLogLevel Error+-- "Error  "+--+showJustifiedLogLevel :: LogLevel -> String+showJustifiedLogLevel = justifyLeft maxSizeLogLevel ' ' . show+ where+  maxSizeLogLevel :: Int+  maxSizeLogLevel = maximum (map (length . show) [(minBound :: LogLevel)..])++-- | Justify a list with a custom fill symbol+--+-- >>> justifyLeft 10 'x' "foo"+-- "fooxxxxxxx"+-- >>> justifyLeft 3 'x' "foo"+-- "foo"+-- >>> justifyLeft 2 'x' "foo"+-- "foo"+--+justifyLeft :: Int -> a -> [a] -> [a]+justifyLeft n c s = s ++ replicate (n - length s) c++-- | /Prettily/ format a log message+--+-- > threadId <- myThreadId+-- > formatLog Debug threadId "some debug message"+-- "[DEBUG  ] [ThreadId 1277462] some debug message"+--+formatLog :: ThreadId -> LogLevel -> String -> String+formatLog threadId lvl msg = do+  intercalate "\n" (map go (lines msg))+ where+  go line =+    printf+      "[%s] [%s] %s"+      (map toUpper (showJustifiedLogLevel lvl))+      (show threadId)+      line++-- | Like 'formatLog', but instantiates the /thread/ argument with the current 'ThreadId'+--+-- > formatLogHere Debug "some debug message"+-- "[DEBUG  ] [ThreadId 1440849] some debug message"+--+formatLogHere :: LogLevel -> String -> IO String+formatLogHere lvl msg = do+  threadId <- myThreadId+  pure (formatLog threadId lvl msg)++-- | Should a message be printed? For a given verbosity level and message log level.+shouldLog :: (?verbosity :: LogLevel) => LogLevel -> Bool+shouldLog lvl = ?verbosity <= lvl++-- | Basic logging function. Uses 'formatLogHere'. Is not thread-safe.+log :: (?verbosity :: LogLevel) => LogLevel -> String -> IO ()+log lvl msg+  | shouldLog lvl = hPutStrLn stderr =<< formatLogHere lvl msg+  | otherwise = pure ()
+ src/Test/DocTest/Internal/Nix.hs view
@@ -0,0 +1,138 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE LambdaCase #-}++module Test.DocTest.Internal.Nix where++import Control.Monad (msum)+import Control.Monad.Extra (ifM)+import Control.Monad.Trans.Maybe+import Data.Bool (bool)+import Data.List (intercalate, isSuffixOf)+import Data.Maybe (isJust)+import Data.Version+import GHC.Base (mzero)+import System.Directory+import System.Environment (lookupEnv)+import System.FilePath ((</>), isDrive, takeDirectory)+import System.Process (readProcess)++#if __GLASGOW_HASKELL__ >= 900+import GHC.Data.Maybe (liftMaybeT)+import System.Info (fullCompilerVersion)+#else+import Maybes (liftMaybeT)+import System.Info (compilerVersion)++fullCompilerVersion :: Version+fullCompilerVersion =+  case compilerVersion of+    Version majorMinor tags ->+      Version (majorMinor ++ [lvl1]) tags+ where+  lvl1 :: Int+  lvl1 = __GLASGOW_HASKELL_PATCHLEVEL1__+#endif++-- | E.g. @9.0.2@+compilerVersionStr :: String+compilerVersionStr = intercalate "." (map show (versionBranch fullCompilerVersion))++-- | Traverse upwards until one of the following conditions is met:+--+--   * Current working directory is either root or a home directory+--   * The predicate function returns 'Just'+--+findDirectoryUp :: (FilePath -> IO (Maybe a)) -> MaybeT IO a+findDirectoryUp f = do+  home <- liftMaybeT getHomeDirectory+  MaybeT (go home =<< getCurrentDirectory)+ where+  go home cwd+    | isDrive cwd = pure Nothing+    | cwd == home = pure Nothing+    | otherwise =+      f cwd >>= \case+        Just a -> pure (Just a)+        Nothing -> go home (takeDirectory cwd)++-- | Like 'findDirectoryUp', but takes a predicate function instead. If the predicate+-- yields 'True', the filepath is returned.+findDirectoryUpPredicate :: (FilePath -> IO Bool) -> MaybeT IO FilePath+findDirectoryUpPredicate f = findDirectoryUp (\fp -> bool Nothing (Just fp) <$> f fp)++-- | Find the root of the Cabal project relative to the current directory.+findCabalProjectRoot :: MaybeT IO FilePath+findCabalProjectRoot =+  msum+    [ findDirectoryUpPredicate containsCabalProject+    , findDirectoryUpPredicate containsCabalPackage+    ]+ where+  containsCabalPackage :: FilePath -> IO Bool+  containsCabalPackage fp = elem "cabal.project" <$> getDirectoryContents fp++  containsCabalProject :: FilePath -> IO Bool+  containsCabalProject fp = any (".cabal" `isSuffixOf`) <$> getDirectoryContents fp++-- | Find the local package database in @dist-newstyle@.+findLocalPackageDb :: MaybeT IO FilePath+findLocalPackageDb = do+  projectRoot <- findCabalProjectRoot+  let+    relDir = "dist-newstyle" </> "packagedb" </> ("ghc-" ++ compilerVersionStr)+    absDir = projectRoot </> relDir+  ifM+    (liftMaybeT (doesDirectoryExist absDir))+    (return absDir)+    mzero++-- | Are we running in a Nix shell?+inNixShell :: IO Bool+inNixShell = isJust <$> lookupEnv "IN_NIX_SHELL"++-- | Are we running in a Nix build environment?+inNixBuild :: IO Bool+inNixBuild = isJust <$> lookupEnv "NIX_BUILD_TOP"++getLocalCabalPackageDbArgs :: IO [String]+getLocalCabalPackageDbArgs = do+  runMaybeT findLocalPackageDb >>= \case+     Nothing -> pure []+     Just s -> pure ["-package-db", s]++getLocalNixPackageDbArgs :: IO [String]+getLocalNixPackageDbArgs = do+  pkgDb <- makeAbsolute ("dist" </> "package.conf.inplace")+  ifM+    (doesDirectoryExist pkgDb)+    (pure ["-package-db", pkgDb])+    (pure [])++-- | Get global package db; used in a NIX_SHELL context+getGlobalPackageDb :: IO String+getGlobalPackageDb = init <$> readProcess "ghc" ["--print-global-package-db"] ""++-- | Get flags to be used when running in a Nix context (either in a build, or a+-- shell).+getNixGhciArgs :: IO [String]+getNixGhciArgs =+  ifM inNixShell goShell (ifM inNixBuild goBuild (pure []))+ where+  goShell = do+    globalPkgDb <- getGlobalPackageDb+    localPkgDbFlag <- getLocalCabalPackageDbArgs+    let globalDbFlag = ["-package-db", globalPkgDb]+    pure (defaultArgs ++ globalDbFlag ++ localPkgDbFlag)++  goBuild = do+    localDbFlag <- getLocalNixPackageDbArgs+    pure (defaultArgs ++ localDbFlag)++  defaultArgs =+    [ "-package-env", "-"++    -- Nix doesn't always expose the GHC library (_specifically_ the GHC lib) even+    -- if a package lists it as a dependency. This simply always exposes it as a+    -- workaround.+    , "-package", "ghc"+    ]
src/Test/DocTest/Internal/Options.hs view
@@ -11,6 +11,7 @@ import           Control.DeepSeq (NFData) import           Data.List.Compat import           GHC.Generics (Generic)+import           Text.Read (readMaybe)  import qualified Paths_doctest_parallel import           Data.Version (showVersion)@@ -23,7 +24,8 @@  import           Test.DocTest.Internal.Location (Located (Located), Location) import           Test.DocTest.Internal.Interpreter (ghc)-import           Text.Read (readMaybe)+import           Test.DocTest.Internal.Logging (LogLevel(..))+import qualified Test.DocTest.Internal.Logging as Logging  usage :: String usage = unlines [@@ -35,13 +37,15 @@   , ""   , "Options:"   , "   -jN                      number of threads to use"+  , "   --log-level=LEVEL        one of: debug, verbose, info, warning, error. Default: info."   , "†  --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"   , "   --nix                    account for Nix build environments (default)"-  , "   --verbose                print each test as it is run"-  , "   --quiet                  only print errors"+  , "   --quiet                  set log level to `Error`, shorthand for `--log-level=error`"+  , "   --verbose                set log level to `Verbose`, shorthand for `--log-level=verbose`"+  , "   --debug                  set log level to `Debug`, shorthand for `--log-level=debug`"   , "   --help                   display this help and exit"   , "   --version                output version information and exit"   , "   --info                   output machine-readable version information and exit"@@ -89,15 +93,13 @@ type ModuleName = String  data Config = Config-  { cfgVerbose :: Bool-  -- ^ Verbose output (default: @False@)+  { cfgLogLevel :: LogLevel+  -- ^ Verbosity level.   , cfgModules :: [ModuleName]   -- ^ Module names to test. An empty list means "test all modules".   , cfgThreads :: Maybe Int   -- ^ Number of threads to use. Defaults to autodetection based on the number   -- of cores.-  , cfgQuiet :: Bool-  -- ^ Only print error messages, no status or progress messages (default: @False@)   , cfgModuleConfig :: ModuleConfig   -- ^ Options specific to modules   , cfgNix :: Bool@@ -129,10 +131,9 @@  defaultConfig :: Config defaultConfig = Config-  { cfgVerbose = False-  , cfgModules = []+  { cfgModules = []   , cfgThreads = Nothing-  , cfgQuiet = False+  , cfgLogLevel = Info   , cfgModuleConfig = defaultModuleConfig   , cfgNix = True   }@@ -171,11 +172,13 @@       "--help" -> ResultStdout usage       "--info" -> ResultStdout info       "--version" -> ResultStdout versionInfo-      "--verbose" -> go config{cfgVerbose=True} args-      "--quiet" -> go config{cfgQuiet=True} args+      "--quiet" -> go config{cfgLogLevel=Error} args+      "--verbose" -> go config{cfgLogLevel=Verbose} args+      "--debug" -> go config{cfgLogLevel=Debug} args       "--nix" -> go config{cfgNix=True} args       "--no-nix" -> go config{cfgNix=False} args       ('-':_) | Just n <- parseThreads arg -> go config{cfgThreads=Just n} args+      ('-':_) | Just l <- parseLogLevel arg -> go config{cfgLogLevel=l} args       ('-':_)         -- Module specific configuration options         | Just modCfg <- parseModuleOption (cfgModuleConfig config) arg@@ -193,6 +196,16 @@ parseSeed :: String -> Maybe Int parseSeed arg = readMaybe =<< parseSpecificFlag arg "seed" +-- | Parse seed argument+--+-- >>> parseLogLevel "--log-level=Debug"+-- Just Debug+-- >>> parseLogLevel "--log-level=debug"+-- Just Debug+-- >>> parseSeed "---log-level=debug"+-- Nothing+parseLogLevel  :: String -> Maybe LogLevel+parseLogLevel arg = Logging.parseLogLevel =<< parseSpecificFlag arg "log-level"  -- | Parse number of threads argument --
src/Test/DocTest/Internal/Runner.hs view
@@ -1,12 +1,13 @@ {-# LANGUAGE CPP #-}-{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE ImplicitParams #-} {-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE TypeApplications #-}  module Test.DocTest.Internal.Runner where  import           Prelude hiding (putStr, putStrLn, error) -import           Control.Concurrent (Chan, writeChan, readChan, newChan, forkIO)+import           Control.Concurrent (Chan, writeChan, readChan, newChan, forkIO, ThreadId, myThreadId) import           Control.Exception (SomeException, catch) import           Control.Monad hiding (forM_) import           Data.Foldable (forM_)@@ -28,11 +29,12 @@   ( ModuleName, ModuleConfig (cfgPreserveIt), cfgSeed, cfgPreserveIt   , cfgRandomizeOrder, cfgImplicitModuleImport, parseLocatedModuleOptions) import           Test.DocTest.Internal.Location-import Test.DocTest.Internal.Property-    ( runProperty, PropertyResult(Failure, Success, Error) )+import qualified Test.DocTest.Internal.Property as Property import           Test.DocTest.Internal.Runner.Example+import           Test.DocTest.Internal.Logging (LogLevel (..), formatLog, shouldLog)  import           System.IO.CodePage (withCP65001)+import Control.Monad.Extra (whenM)  #if __GLASGOW_HASKELL__ < 804 import Data.Semigroup@@ -71,22 +73,19 @@  -- | Run all examples from a list of modules. runModules-  :: ModuleConfig+  :: (?verbosity::LogLevel)+  => ModuleConfig   -- ^ Configuration options specific to module   -> Maybe Int   -- ^ Number of threads to use. Defaults to 'getNumProcessors'.   -> Bool-  -- ^ Verbose-  -> Bool   -- ^ Implicit Prelude   -> [String]   -- ^ Arguments passed to the GHCi process.-  -> Bool-  -- ^ Quiet mode activated   -> [Module [Located DocTest]]   -- ^ Modules under test   -> IO Summary-runModules modConfig nThreads verbose implicitPrelude args quiet modules = do+runModules modConfig nThreads implicitPrelude args modules = do   isInteractive <- hIsTerminalDevice stderr    -- Start a thread pool. It sends status updates to this thread through 'output'.@@ -104,33 +103,37 @@     initState = ReportState       { reportStateCount = 0       , reportStateInteractive = isInteractive-      , reportStateVerbose = verbose-      , reportStateQuiet = quiet       , reportStateSummary = mempty{sExamples=nExamples}       } +  threadId <- myThreadId+  let ?threadId = threadId+   ReportState{reportStateSummary} <- (`execStateT` initState) $ do     consumeUpdates output (length modules)-    unless quiet $ do-      verboseReport "# Final summary:"-      gets (show . reportStateSummary) >>= report+    gets (show . reportStateSummary) >>= report Info    return reportStateSummary  where+  consumeUpdates ::+    (?threadId :: ThreadId) =>+    Chan (ThreadId, ReportUpdate) ->+    Int ->+    StateT ReportState IO ()   consumeUpdates _output 0 = pure ()   consumeUpdates output modsLeft = do-    update <- liftIO (readChan output)+    (threadId, update) <- liftIO (readChan output)+    let ?threadId = threadId     consumeUpdates output =<<       case update of         UpdateInternalError fs loc e -> reportInternalError fs loc e >> pure (modsLeft - 1)         UpdateImportError modName result -> reportImportError modName result >> pure (modsLeft - 1)-        UpdateSuccess fs loc -> reportSuccess fs loc >> reportProgress >> pure modsLeft+        UpdateSuccess fs -> reportSuccess fs >> reportProgress >> pure modsLeft         UpdateFailure fs loc expr errs -> reportFailure fs loc expr errs >> pure modsLeft         UpdateError fs loc expr err -> reportError fs loc expr err >> pure modsLeft         UpdateOptionError loc err -> reportOptionError loc err >> pure modsLeft-        UpdateVerbose msg -> verboseReport msg >> pure modsLeft-        UpdateStart loc expr msg -> reportStart loc expr msg >> pure modsLeft         UpdateModuleDone -> pure (modsLeft - 1)+        UpdateLog lvl msg -> report lvl msg >> pure modsLeft  -- | Count number of expressions in given module. count :: Module [Located DocTest] -> Int@@ -142,30 +145,36 @@ data ReportState = ReportState {   reportStateCount        :: Int     -- ^ characters on the current line , reportStateInteractive  :: Bool    -- ^ should intermediate results be printed?-, reportStateVerbose      :: Bool-, reportStateQuiet        :: Bool , reportStateSummary      :: Summary -- ^ test summary }  -- | Add output to the report.-report :: String -> Report ()-report msg = do-  overwrite msg+report ::+  ( ?verbosity :: LogLevel+  , ?threadId :: ThreadId+  ) =>+  LogLevel ->+  String ->+  Report ()+report lvl msg0 =+  when (shouldLog lvl) $ do+    let msg1 = formatLog ?threadId lvl msg0+    overwrite msg1 -  -- add a newline, this makes the output permanent-  liftIO $ hPutStrLn stderr ""-  modify (\st -> st {reportStateCount = 0})+    -- add a newline, this makes the output permanent+    liftIO $ hPutStrLn stderr ""+    modify (\st -> st {reportStateCount = 0})  -- | Add intermediate output to the report. -- -- This will be overwritten by subsequent calls to `report`/`report_`. -- Intermediate out may not contain any newlines.-report_ :: String -> Report ()-report_ msg = do-  f <- gets reportStateInteractive-  when f $ do-    overwrite msg-    modify (\st -> st {reportStateCount = length msg})+report_ :: (?verbosity :: LogLevel) => LogLevel -> String -> Report ()+report_ lvl msg =+  when (shouldLog lvl) $ do+    whenM (gets reportStateInteractive) $ do+      overwrite msg+      modify (\st -> st {reportStateCount = length msg})  -- | Add output to the report, overwrite any intermediate out. overwrite :: String -> Report ()@@ -187,13 +196,16 @@   :: ModuleConfig   -> Bool   -> [String]-  -> Chan ReportUpdate+  -> Chan (ThreadId, ReportUpdate)   -> Module [Located DocTest]   -> IO () runModule modConfig0 implicitPrelude ghciArgs output mod_ = do+  threadId <- myThreadId+  let update r = writeChan output (threadId, r)+   case modConfig2 of     Left (loc, flag) ->-      writeChan output (UpdateOptionError loc flag)+      update (UpdateOptionError loc flag)      Right modConfig3 -> do       let@@ -228,7 +240,8 @@             Example e _ -> void $ safeEvalWith preserveIt repl e  -      Interpreter.withInterpreter ghciArgs $ \repl -> withCP65001 $ do+      let logger = update . UpdateLog Debug+      Interpreter.withInterpreter logger ghciArgs $ \repl -> withCP65001 $ do         -- Try to import this module, if it fails, something is off         importResult <-           case importModule of@@ -240,20 +253,20 @@             -- Run setup group             successes <-               mapM-                (runTestGroup FromSetup preserveIt repl (reload repl) output)+                (runTestGroup FromSetup preserveIt repl (reload repl) update)                 setup              -- only run tests, if setup does not produce any errors/failures             when               (and successes)               (mapM_-                (runTestGroup NotFromSetup preserveIt repl (setup_ repl) output)+                (runTestGroup NotFromSetup preserveIt repl (setup_ repl) update)                 examples1)           _ ->-            writeChan output (UpdateImportError module_ importResult)+            update (UpdateImportError module_ importResult)    -- Signal main thread a module has been tested-  writeChan output UpdateModuleDone+  update UpdateModuleDone    pure () @@ -262,137 +275,112 @@   modConfig2 = parseLocatedModuleOptions module_ modConfig0 modArgs  data ReportUpdate-  = UpdateSuccess FromSetup Location+  = UpdateSuccess FromSetup   -- ^ Test succeeded   | UpdateFailure FromSetup Location Expression [String]   -- ^ Test failed with unexpected result   | UpdateError FromSetup Location Expression String   -- ^ Test failed with an error-  | UpdateVerbose String-  -- ^ Message to send when verbose output is activated   | UpdateModuleDone   -- ^ All examples tested in module-  | UpdateStart Location Expression String-  -- ^ Indicate test has started executing (verbose output)   | UpdateInternalError FromSetup (Module [Located DocTest]) SomeException   -- ^ Exception caught while executing internal code   | UpdateImportError ModuleName (Either String String)   -- ^ Could not import module   | UpdateOptionError Location String   -- ^ Unrecognized flag in module specific option+  | UpdateLog LogLevel String+  -- ^ Unstructured message  makeThreadPool ::   Int ->-  (Chan ReportUpdate -> Module [Located DocTest] -> IO ()) ->-  IO (Chan (Module [Located DocTest]), Chan ReportUpdate)+  (Chan (ThreadId, ReportUpdate) -> Module [Located DocTest] -> IO ()) ->+  IO (Chan (Module [Located DocTest]), Chan (ThreadId, ReportUpdate)) makeThreadPool nThreads mutator = do   input <- newChan   output <- newChan   forM_ [1..nThreads] $ \_ ->     forkIO $ forever $ do       i <- readChan input+      threadId <- myThreadId       catch         (mutator output i)-        (\e -> writeChan output (UpdateInternalError NotFromSetup i e))+        (\e -> writeChan output (threadId, UpdateInternalError NotFromSetup i e))   return (input, output) -reportStart :: Location -> Expression -> String -> Report ()-reportStart loc expression testType = do-  quiet <- gets reportStateQuiet-  unless quiet $-    verboseReport-      (printf "### Started execution at %s.\n### %s:\n%s" (show loc) testType expression)--reportFailure :: FromSetup -> Location -> Expression -> [String] -> Report ()+reportFailure :: (?verbosity::LogLevel, ?threadId::ThreadId) => FromSetup -> Location -> Expression -> [String] -> Report () reportFailure fromSetup loc expression err = do-  report (printf "%s: failure in expression `%s'" (show loc) expression)-  mapM_ report err-  report ""+  report Error (printf "%s: failure in expression `%s'" (show loc) expression)+  mapM_ (report Error) err+  report Error ""   updateSummary fromSetup (Summary 0 1 0 1) -reportError :: FromSetup -> Location -> Expression -> String -> Report ()+reportError :: (?verbosity::LogLevel, ?threadId::ThreadId) => FromSetup -> Location -> Expression -> String -> Report () reportError fromSetup loc expression err = do-  report (printf "%s: error in expression `%s'" (show loc) expression)-  report err-  report ""+  report Error (printf "%s: error in expression `%s'" (show loc) expression)+  report Error err+  report Error ""   updateSummary fromSetup (Summary 0 1 1 0) -reportOptionError :: Location -> String -> Report ()+reportOptionError :: (?verbosity::LogLevel, ?threadId::ThreadId) => Location -> String -> Report () reportOptionError loc opt = do-  report (printf "%s: unrecognized option: %s. Try --help to see all options." (show loc) opt)-  report ""+  report Error (printf "%s: unrecognized option: %s. Try --help to see all options." (show loc) opt)+  report Error ""   updateSummary FromSetup (Summary 0 1 1 0) -reportInternalError :: FromSetup -> Module a -> SomeException -> Report ()+reportInternalError :: (?verbosity::LogLevel, ?threadId::ThreadId) => FromSetup -> Module a -> SomeException -> Report () reportInternalError fs mod_ err = do-  report (printf "Internal error when executing tests in %s" (moduleName mod_))-  report (show err)-  report ""+  report Error (printf "Internal error when executing tests in %s" (moduleName mod_))+  report Error (show err)+  report Error ""   updateSummary fs emptySummary{sErrors=1} -reportImportError :: ModuleName -> Either String String -> Report ()+reportImportError :: (?verbosity::LogLevel, ?threadId::ThreadId) => ModuleName -> Either String String -> Report () reportImportError modName importResult = 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'. 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'"-  report "   * Add 'write-ghc-environment-files: always' to your cabal.project"-  report ""-  report " 3. For Cabal users: Cabal did not generate a GHC environment file in time. This"-  report "    can happen if you use 'cabal test' instead of 'cabal run doctests'. See"-  report "    https://github.com/martijnbastiaan/doctest-parallel/issues/22."-  report ""-  report " 4. The testsuite executable does not have a dependency on your project library. Please"-  report "    add it to the 'build-depends' section of the testsuite executable."-  report ""-  report "See the example project at https://github.com/martijnbastiaan/doctest-parallel/blob/main/example/README.md for more information."-  report ""-  report "The original reason given by GHCi was:"-  report ""+  report Error ("Could not import module: " <> modName <> ". This can be caused by a number of issues: ")+  report Error ""+  report Error " 1. A module found by GHC contained tests, but was not in 'exposed-modules'. If you want"+  report Error "    to test non-exposed modules follow the instructions here:"+  report Error "    https://github.com/martijnbastiaan/doctest-parallel#test-non-exposed-modules"+  report Error ""+  report Error " 2. For Cabal users: Cabal did not generate a GHC environment file. Either:"+  report Error "   * Run with '--write-ghc-environment-files=always'"+  report Error "   * Add 'write-ghc-environment-files: always' to your cabal.project"+  report Error ""+  report Error " 3. For Cabal users: Cabal did not generate a GHC environment file in time. This"+  report Error "    can happen if you use 'cabal test' instead of 'cabal run doctests'. See"+  report Error "    https://github.com/martijnbastiaan/doctest-parallel/issues/22."+  report Error ""+  report Error " 4. The testsuite executable does not have a dependency on your project library. Please"+  report Error "    add it to the 'build-depends' section of the testsuite executable."+  report Error ""+  report Error "See the example project at https://github.com/martijnbastiaan/doctest-parallel/blob/main/example/README.md for more information."+  report Error ""+  report Error "The original reason given by GHCi was:"+  report Error ""   case importResult of     Left out -> do-      report "Unexpected output:"-      report out+      report Error "Unexpected output:"+      report Error out     Right err -> do-      report "Error:"-      report err+      report Error "Error:"+      report Error err    updateSummary FromSetup emptySummary{sErrors=1} -reportSuccess :: FromSetup -> Location -> Report ()-reportSuccess fromSetup loc = do-  quiet <- gets reportStateQuiet-  unless quiet $-    verboseReport (printf "### Successful `%s'!\n" (show loc))-  updateSummary fromSetup (Summary 0 1 0 0)--verboseReport :: String -> Report ()-verboseReport xs = do-  verbose <- gets reportStateVerbose-  quiet <- gets reportStateQuiet-  unless quiet $-    when verbose $-      report xs+reportSuccess :: (?verbosity::LogLevel, ?threadId::ThreadId) => FromSetup -> Report ()+reportSuccess fromSetup = updateSummary fromSetup (Summary 0 1 0 0)  updateSummary :: FromSetup -> Summary -> Report () updateSummary FromSetup summary =   -- Suppress counts, except for errors and unexpected outputs   updateSummary NotFromSetup summary{sExamples=0, sTried=0} updateSummary NotFromSetup summary = do-  ReportState n f v q s <- get-  put (ReportState n f v q $ s `mappend` summary)+  ReportState n f s <- get+  put (ReportState n f $ s `mappend` summary) -reportProgress :: Report ()-reportProgress = do-  verbose <- gets reportStateVerbose-  quiet <- gets reportStateQuiet-  unless quiet $-    unless verbose $-      gets (show . reportStateSummary) >>= report_+reportProgress :: (?verbosity::LogLevel) => Report ()+reportProgress = gets (show . reportStateSummary) >>= report_ Info  -- | Run given test group. --@@ -403,29 +391,28 @@   Bool ->   Interpreter ->   IO () ->-  Chan ReportUpdate ->+  (ReportUpdate -> IO ()) ->   [Located DocTest] ->   IO Bool-runTestGroup fromSetup preserveIt repl setup output tests = do-+runTestGroup fromSetup preserveIt repl setup update tests = do   setup-  successExamples <- runExampleGroup fromSetup preserveIt repl output examples+  successExamples <- runExampleGroup fromSetup preserveIt repl update examples    successesProperties <- forM properties $ \(loc, expression) -> do     r <- do       setup-      writeChan output (UpdateStart loc expression "property")-      runProperty repl expression+      update (UpdateLog Verbose ("Started property at " ++ show loc))+      Property.runProperty repl expression      case r of-      Success -> do-        writeChan output (UpdateSuccess fromSetup loc)+      Property.Success -> do+        update (UpdateSuccess fromSetup)         pure True-      Error err -> do-        writeChan output (UpdateError fromSetup loc expression err)+      Property.Error err -> do+        update (UpdateError fromSetup loc expression err)         pure False-      Failure msg -> do-        writeChan output (UpdateFailure fromSetup loc expression [msg])+      Property.Failure msg -> do+        update (UpdateFailure fromSetup loc expression [msg])         pure False    pure (successExamples && and successesProperties)@@ -442,26 +429,28 @@   FromSetup ->   Bool ->   Interpreter ->-  Chan ReportUpdate ->+  (ReportUpdate -> IO ()) ->   [Located Interaction] ->   IO Bool-runExampleGroup fromSetup preserveIt repl output = go+runExampleGroup fromSetup preserveIt repl update examples = do+  threadId <- myThreadId+  go threadId examples   where-    go ((Located loc (expression, expected)) : xs) = do-      writeChan output (UpdateStart loc expression "example")+    go threadId ((Located loc (expression, expected)) : xs) = do+      update (UpdateLog Verbose ("Started example at " ++ show loc))       r <- fmap lines <$> safeEvalWith preserveIt repl expression       case r of         Left err -> do-          writeChan output (UpdateError fromSetup loc expression err)+          update (UpdateError fromSetup loc expression err)           pure False         Right actual -> case mkResult expected actual of           NotEqual err -> do-            writeChan output (UpdateFailure fromSetup loc expression err)+            update (UpdateFailure fromSetup loc expression err)             pure False           Equal -> do-            writeChan output (UpdateSuccess fromSetup loc)-            go xs-    go [] =+            update (UpdateSuccess fromSetup)+            go threadId xs+    go _ [] =       pure True  safeEvalWith :: Bool -> Interpreter -> String -> IO (Either String String)
+ test/GhciWrapperSpec.hs view
@@ -0,0 +1,111 @@+{-# LANGUAGE CPP #-}++module GhciWrapperSpec (main, spec) where++import           Test.Hspec+import           System.IO.Silently++import           Control.Exception+import           Data.List (isInfixOf)++import           Test.DocTest.Internal.GhciWrapper (Interpreter, Config(..), defaultConfig)+import qualified Test.DocTest.Internal.GhciWrapper as Interpreter+import           Test.DocTest.Internal.Logging (noLogger)++main :: IO ()+main = hspec spec++withInterpreterConfig :: Config -> (Interpreter -> IO a) -> IO a+withInterpreterConfig config = bracket (Interpreter.new noLogger config []) Interpreter.close++withInterpreter :: ((String -> IO String) -> IO a) -> IO a+withInterpreter action = withInterpreterConfig defaultConfig $ action . Interpreter.eval++spec :: Spec+spec = do+  describe "evalEcho" $ do+    it "prints result to stdout" $ do+      withInterpreterConfig defaultConfig $ \ghci -> do+        (capture $ Interpreter.evalEcho ghci ("putStr" ++ show "foo\nbar")) `shouldReturn` ("foo\nbar", "foo\nbar")++  describe "evalIt" $ do+    it "preserves it" $ do+      withInterpreterConfig defaultConfig $ \ghci -> do+        Interpreter.evalIt ghci "23" `shouldReturn` "23\n"+        Interpreter.eval ghci "it" `shouldReturn` "23\n"++  describe "eval" $ do+    it "shows literals" $ withInterpreter $ \ghci -> do+      ghci "23" `shouldReturn` "23\n"++    it "shows string literals containing Unicode" $ withInterpreter $ \ghci -> do+      ghci "\"λ\"" `shouldReturn` "\"\\955\"\n"++    it "evaluates simple expressions" $ withInterpreter $ \ghci -> do+      ghci "23 + 42" `shouldReturn` "65\n"++    it "supports let bindings" $ withInterpreter $ \ghci -> do+      ghci "let x = 10" `shouldReturn` ""+      ghci "x" `shouldReturn` "10\n"++    it "allows import statements" $ withInterpreter $ \ghci -> do+      ghci "import Data.Maybe" `shouldReturn` ""+      ghci "fromJust (Just 20)" `shouldReturn` "20\n"++    it "captures stdout" $ withInterpreter $ \ghci -> do+      ghci "putStr \"foo\"" `shouldReturn` "foo"++    it "captures stdout (Unicode)" $ withInterpreter $ \ghci -> do+      ghci "putStrLn \"λ\"" `shouldReturn` "λ\n"++    it "captures stdout (empty line)" $ withInterpreter $ \ghci -> do+      ghci "putStrLn \"\"" `shouldReturn` "\n"++    it "captures stdout (multiple lines)" $ withInterpreter $ \ghci -> do+      ghci "putStrLn \"foo\" >> putStrLn \"bar\" >> putStrLn \"baz\""+        `shouldReturn` "foo\nbar\nbaz\n"++    it "captures stderr" $ withInterpreter $ \ghci -> do+      ghci "import System.IO" `shouldReturn` ""+      ghci "hPutStrLn stderr \"foo\"" `shouldReturn` "foo\n"++    it "captures stderr (Unicode)" $ withInterpreter $ \ghci -> do+      ghci "import System.IO" `shouldReturn` ""+      ghci "hPutStrLn stderr \"λ\"" `shouldReturn` "λ\n"++    it "shows exceptions" $ withInterpreter $ \ghci -> do+      ghci "import Control.Exception" `shouldReturn` ""+      ghci "throwIO DivideByZero" `shouldReturn` "*** Exception: divide by zero\n"++    it "shows exceptions (ExitCode)" $ withInterpreter $ \ghci -> do+      ghci "import System.Exit" `shouldReturn` ""+      ghci "exitWith $ ExitFailure 10" `shouldReturn` "*** Exception: ExitFailure 10\n"++    it "gives an error message for identifiers that are not in scope" $ withInterpreter $ \ghci -> do+#if __GLASGOW_HASKELL__ >= 800+      ghci "foo" >>= (`shouldSatisfy` isInfixOf "Variable not in scope: foo")+#else+      ghci "foo" >>= (`shouldSatisfy` isSuffixOf "Not in scope: \8216foo\8217\n")+#endif+    context "when configVerbose is True" $ do+      it "prints prompt" $ do+        withInterpreterConfig defaultConfig{configVerbose = True} $ \ghci -> do+          Interpreter.eval ghci "print 23" >>= (`shouldSatisfy`+            (`elem` [ "Prelude> 23\nPrelude> "+                    ,  "ghci> 23\nghci> "+                    ]))++    context "with -XOverloadedStrings, -Wall and -Werror" $ do+      it "does not fail on marker expression (bug fix)" $ withInterpreter $ \ghci -> do+        ghci ":set -XOverloadedStrings -Wall -Werror" `shouldReturn` ""+        ghci "putStrLn \"foo\"" `shouldReturn` "foo\n"++    context "with NoImplicitPrelude" $ do+      it "works" $ withInterpreter $ \ghci -> do+        ghci ":set -XNoImplicitPrelude" `shouldReturn` ""+        ghci "putStrLn \"foo\"" `shouldReturn` "foo\n"++    context "with a strange String type" $ do+      it "works" $ withInterpreter $ \ghci -> do+        ghci "type String = Int" `shouldReturn` ""+        ghci "putStrLn \"foo\"" `shouldReturn` "foo\n"
test/InterpreterSpec.hs view
@@ -8,6 +8,7 @@ import qualified Test.DocTest.Internal.Interpreter as Interpreter import           Test.DocTest.Internal.Interpreter   (haveInterpreterKey, ghcInfo, withInterpreter)+import           Test.DocTest.Internal.Logging (noLogger)  main :: IO () main = hspec spec@@ -25,8 +26,8 @@         (||) <$> (== Just "YES") <*> (== Just "NO")    describe "safeEval" $ do-    it "evaluates an expression" $ withInterpreter [] $ \ghci -> do+    it "evaluates an expression" $ withInterpreter noLogger [] $ \ghci -> do       Interpreter.safeEval ghci "23 + 42" `shouldReturn` Right "65\n" -    it "returns Left on unterminated multiline command" $ withInterpreter [] $ \ghci -> do+    it "returns Left on unterminated multiline command" $ withInterpreter noLogger [] $ \ghci -> do       Interpreter.safeEval ghci ":{\n23 + 42" `shouldReturn` Left "unterminated multiline command"
test/OptionsSpec.hs view
@@ -6,6 +6,7 @@ import           Test.Hspec  import           Test.DocTest.Internal.Options+import Test.DocTest.Internal.Logging (LogLevel(..))  spec :: Spec spec = do@@ -57,8 +58,8 @@     describe "--verbose" $ do       context "without --verbose" $ do         it "is not verbose by default" $ do-          cfgVerbose <$> parseOptions [] `shouldBe` Result False+          cfgLogLevel <$> parseOptions [] `shouldBe` Result Info        context "with --verbose" $ do         it "parses verbose option" $ do-          cfgVerbose <$> parseOptions ["--verbose"] `shouldBe` Result True+          cfgLogLevel <$> parseOptions ["--verbose"] `shouldBe` Result Verbose
test/PropertySpec.hs view
@@ -1,11 +1,12 @@ {-# LANGUAGE CPP, OverloadedStrings #-} module PropertySpec (main, spec) where -import           Test.Hspec-import           Data.String.Builder+import Test.Hspec+import Data.String.Builder -import           Test.DocTest.Internal.Property-import           Test.DocTest.Internal.Interpreter (withInterpreter)+import Test.DocTest.Internal.Property+import Test.DocTest.Internal.Interpreter (withInterpreter)+import Test.DocTest.Internal.Logging (noLogger)  main :: IO () main = hspec spec@@ -17,54 +18,54 @@ spec :: Spec spec = do   describe "runProperty" $ do-    it "reports a failing property" $ withInterpreter [] $ \repl -> do+    it "reports a failing property" $ withInterpreter noLogger [] $ \repl -> do       runProperty repl "False" `shouldReturn` Failure "*** Failed! Falsified (after 1 test):" -    it "runs a Bool property" $ withInterpreter [] $ \repl -> do+    it "runs a Bool property" $ withInterpreter noLogger [] $ \repl -> do       runProperty repl "True" `shouldReturn` Success -    it "runs a Bool property with an explicit type signature" $ withInterpreter [] $ \repl -> do+    it "runs a Bool property with an explicit type signature" $ withInterpreter noLogger [] $ \repl -> do       runProperty repl "True :: Bool" `shouldReturn` Success -    it "runs an implicitly quantified property" $ withInterpreter [] $ \repl -> do+    it "runs an implicitly quantified property" $ withInterpreter noLogger [] $ \repl -> do       runProperty repl "(reverse . reverse) xs == (xs :: [Int])" `shouldReturn` Success      it "runs an implicitly quantified property even with GHC 7.4" $       -- ghc will include a suggestion (did you mean `id` instead of `is`) in       -- the error message-      withInterpreter [] $ \repl -> do+      withInterpreter noLogger [] $ \repl -> do         runProperty repl "foldr (+) 0 is == sum (is :: [Int])" `shouldReturn` Success -    it "runs an explicitly quantified property" $ withInterpreter [] $ \repl -> do+    it "runs an explicitly quantified property" $ withInterpreter noLogger [] $ \repl -> do       runProperty repl "\\xs -> (reverse . reverse) xs == (xs :: [Int])" `shouldReturn` Success -    it "allows to mix implicit and explicit quantification" $ withInterpreter [] $ \repl -> do+    it "allows to mix implicit and explicit quantification" $ withInterpreter noLogger [] $ \repl -> do       runProperty repl "\\x -> x + y == y + x" `shouldReturn` Success -    it "reports the value for which a property fails" $ withInterpreter [] $ \repl -> do+    it "reports the value for which a property fails" $ withInterpreter noLogger [] $ \repl -> do       runProperty repl "x == 23" `shouldReturn` Failure "*** Failed! Falsified (after 1 test):\n0" -    it "reports the values for which a property that takes multiple arguments fails" $ withInterpreter [] $ \repl -> do+    it "reports the values for which a property that takes multiple arguments fails" $ withInterpreter noLogger [] $ \repl -> do       let vals x = case x of (Failure r) -> tail (lines r); _ -> error "Property did not fail!"       vals `fmap` runProperty repl "x == True && y == 10 && z == \"foo\"" `shouldReturn` ["False", "0", show ("" :: String)] -    it "defaults ambiguous type variables to Integer" $ withInterpreter [] $ \repl -> do+    it "defaults ambiguous type variables to Integer" $ withInterpreter noLogger [] $ \repl -> do       runProperty repl "reverse xs == xs" >>= (`shouldSatisfy` isFailure)    describe "freeVariables" $ do-    it "finds a free variables in a term" $ withInterpreter [] $ \repl -> do+    it "finds a free variables in a term" $ withInterpreter noLogger [] $ \repl -> do       freeVariables repl "x" `shouldReturn` ["x"] -    it "ignores duplicates" $ withInterpreter [] $ \repl -> do+    it "ignores duplicates" $ withInterpreter noLogger [] $ \repl -> do       freeVariables repl "x == x" `shouldReturn` ["x"] -    it "works for terms with multiple names" $ withInterpreter [] $ \repl -> do+    it "works for terms with multiple names" $ withInterpreter noLogger [] $ \repl -> do       freeVariables repl "\\z -> x + y + z == foo 23" `shouldReturn` ["x", "y", "foo"] -    it "works for names that contain a prime" $ withInterpreter [] $ \repl -> do+    it "works for names that contain a prime" $ withInterpreter noLogger [] $ \repl -> do       freeVariables repl "x' == y''" `shouldReturn` ["x'", "y''"] -    it "works for names that are similar to other names that are in scope" $ withInterpreter [] $ \repl -> do+    it "works for names that are similar to other names that are in scope" $ withInterpreter noLogger [] $ \repl -> do       freeVariables repl "length_" `shouldReturn` ["length_"]    describe "parseNotInScope" $ do
test/RunnerSpec.hs view
@@ -1,25 +1,31 @@-{-# LANGUAGE CPP, OverloadedStrings #-}+{-# LANGUAGE CPP, OverloadedStrings, ImplicitParams #-} module RunnerSpec (main, spec) where -import           Test.Hspec+import Test.Hspec -import           System.IO-import           System.IO.Silently (hCapture)-import           Control.Monad.Trans.State-import           Test.DocTest.Internal.Runner+import Control.Concurrent+import Control.Monad.Trans.State+import System.IO+import System.IO.Silently (hCapture)+import Test.DocTest.Internal.Logging+import Test.DocTest.Internal.Runner+import Text.Printf (printf)  main :: IO () main = hspec spec  capture :: Report a -> IO String-capture = fmap fst . hCapture [stderr] . (`execStateT` ReportState 0 True False False mempty)+capture = fmap fst . hCapture [stderr] . (`execStateT` ReportState 0 True mempty)  -- like capture, but with interactivity set to False capture_ :: Report a -> IO String-capture_ = fmap fst . hCapture [stderr] . (`execStateT` ReportState 0 False False False mempty)+capture_ = fmap fst . hCapture [stderr] . (`execStateT` ReportState 0 False mempty)  spec :: Spec spec = do+  threadId <- runIO myThreadId+  let ?threadId = threadId+  let ?verbosity = Info    describe "report" $ do @@ -27,61 +33,61 @@        it "writes to stderr" $ do         capture $ do-          report "foobar"-        `shouldReturn` "foobar\n"+          report Info "foobar"+        `shouldReturn` printf "[INFO   ] [%s] foobar\n" (show threadId)        it "overwrites any intermediate output" $ do         capture $ do-          report_ "foo"-          report  "bar"-        `shouldReturn` "foo\rbar\n"+          report_ Info "foo"+          report Info  "bar"+        `shouldReturn` printf "foo\r[INFO   ] [%s] bar\n" (show threadId)        it "blank out intermediate output if necessary" $ do         capture $ do-          report_ "foobar"-          report  "baz"-        `shouldReturn` "foobar\rbaz   \n"+          report_ Info "foobarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr"+          report Info  "baz"+        `shouldReturn` printf "foobarrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr\r[INFO   ] [%s] baz             \n" (show threadId)      context "when mode is non-interactive" $ do       it "writes to stderr" $ do         capture_ $ do-          report "foobar"-        `shouldReturn` "foobar\n"+          report Info "foobar"+        `shouldReturn` printf "[INFO   ] [%s] foobar\n" (show threadId) -  describe "report_" $ do+  describe "report_ Info" $ do      context "when mode is interactive" $ do       it "writes intermediate output to stderr" $ do         capture $ do-          report_ "foobar"+          report_ Info "foobar"         `shouldReturn` "foobar"        it "overwrites any intermediate output" $ do         capture $ do-          report_ "foo"-          report_ "bar"+          report_ Info "foo"+          report_ Info "bar"         `shouldReturn` "foo\rbar"        it "blank out intermediate output if necessary" $ do         capture $ do-          report_ "foobar"-          report_  "baz"+          report_ Info "foobar"+          report_ Info  "baz"         `shouldReturn` "foobar\rbaz   "      context "when mode is non-interactive" $ do       it "is ignored" $ do         capture_ $ do-          report_ "foobar"+          report_ Info "foobar"         `shouldReturn` "" -      it "does not influence a subsequent call to `report`" $ do+      it "does not influence a subsequent call to `report Info`" $ do         capture_ $ do-          report_ "foo"-          report  "bar"-        `shouldReturn` "bar\n"+          report_ Info "foo"+          report Info  "bar"+        `shouldReturn` printf "[INFO   ] [%s] bar\n" (show threadId) -      it "does not require `report` to blank out any intermediate output" $ do+      it "does not require `report Info` to blank out any intermediate output" $ do         capture_ $ do-          report_ "foobar"-          report  "baz"-        `shouldReturn` "baz\n"+          report_ Info "foobar"+          report Info  "baz"+        `shouldReturn` printf "[INFO   ] [%s] baz\n" (show threadId)
test/Spec.hs view
@@ -1,1 +1,32 @@-{-# OPTIONS_GHC -F -pgmF hspec-discover #-}+module Main where++import Test.Hspec++import qualified ExtractSpec+import qualified GhciWrapperSpec+import qualified InterpreterSpec+import qualified LocationSpec+import qualified MainSpec+import qualified OptionsSpec+import qualified ParseSpec+import qualified PropertySpec+import qualified RunnerSpec+import qualified RunSpec+import qualified UtilSpec++main :: IO ()+main = hspec spec++spec :: Spec+spec = do+  describe "ExtractSpec"     ExtractSpec.spec+  describe "GhciWrapperSpec" GhciWrapperSpec.spec+  describe "InterpreterSpec" InterpreterSpec.spec+  describe "LocationSpec"    LocationSpec.spec+  describe "MainSpec"        MainSpec.spec+  describe "OptionsSpec"     OptionsSpec.spec+  describe "ParseSpec"       ParseSpec.spec+  describe "PropertySpec"    PropertySpec.spec+  describe "RunnerSpec"      RunnerSpec.spec+  describe "RunSpec"         RunSpec.spec+  describe "UtilSpec"        UtilSpec.spec