doctest 0.9.13 → 0.25.0
raw patch · 129 files changed
Files
- CHANGES.markdown +224/−0
- LICENSE +1/−1
- README.md +612/−0
- doctest.cabal +273/−83
- driver/Main.hs +0/−6
- driver/cabal-doctest.hs +8/−0
- driver/doctest.hs +8/−0
- src/Cabal.hs +77/−0
- src/Cabal/Options.hs +78/−0
- src/Cabal/Paths.hs +96/−0
- src/Cabal/ReplOptions.hs +195/−0
- src/Extract.hs +145/−103
- src/GhcUtil.hs +37/−46
- src/Help.hs +0/−27
- src/Imports.hs +34/−0
- src/Info.hs +63/−0
- src/Interpreter.hs +41/−153
- src/Language/Haskell/GhciWrapper.hs +160/−0
- src/Location.hs +14/−11
- src/Options.hs +168/−0
- src/PackageDBs.hs +64/−0
- src/Parse.hs +17/−17
- src/Property.hs +12/−12
- src/Run.hs +137/−64
- src/Runner.hs +164/−94
- src/Runner/Example.hs +118/−30
- src/Sandbox.hs +0/−81
- src/Test/DocTest.hs +1/−2
- src/Test/DocTest/Internal/Cabal.hs +10/−0
- src/Test/DocTest/Internal/Extract.hs +4/−0
- src/Test/DocTest/Internal/Location.hs +4/−0
- src/Test/DocTest/Internal/Parse.hs +4/−0
- src/Test/DocTest/Internal/Run.hs +4/−0
- src/Util.hs +3/−0
- test/Cabal/OptionsSpec.hs +60/−0
- test/Cabal/PathsSpec.hs +22/−0
- test/Cabal/ReplOptionsSpec.hs +99/−0
- test/ExtractSpec.hs +95/−0
- test/InfoSpec.hs +17/−0
- test/InterpreterSpec.hs +40/−0
- test/Language/Haskell/GhciWrapperSpec.hs +115/−0
- test/LocationSpec.hs +53/−0
- test/MainSpec.hs +204/−0
- test/OptionsSpec.hs +108/−0
- test/PackageDBsSpec.hs +40/−0
- test/ParseSpec.hs +205/−0
- test/PropertySpec.hs +126/−0
- test/RunSpec.hs +187/−0
- test/Runner/ExampleSpec.hs +162/−0
- test/RunnerSpec.hs +44/−0
- test/UtilSpec.hs +23/−0
- test/doctests.hs +0/−13
- test/extract/argument-list/Foo.hs +6/−0
- test/extract/comment-order/Foo.hs +28/−0
- test/extract/declaration/Foo.hs +5/−0
- test/extract/dos-line-endings/Foo.hs +8/−0
- test/extract/export-list/Foo.hs +12/−0
- test/extract/imported-module/Bar.hs +7/−0
- test/extract/imported-module/Baz.hs +5/−0
- test/extract/module-header/Foo.hs +5/−0
- test/extract/named-chunks/Foo.hs +15/−0
- test/extract/regression/Fixity.hs +4/−0
- test/extract/regression/ForeignImport.hs +10/−0
- test/extract/regression/ParallelListComp.hs +5/−0
- test/extract/regression/ParallelListCompClass.hs +8/−0
- test/extract/regression/RewriteRules.hs +7/−0
- test/extract/regression/RewriteRulesWithSigs.hs +7/−0
- test/extract/setup/Foo.hs +16/−0
- test/extract/th/Bar.hs +8/−0
- test/extract/th/Foo.hs +8/−0
- test/extract/type-class-args/Foo.hs +8/−0
- test/extract/type-class/Foo.hs +7/−0
- test/extract/type-families/Foo.hs +6/−0
- test/integration/bugfixImportHierarchical/ModuleA.hs +6/−0
- test/integration/bugfixImportHierarchical/ModuleB.hs +12/−0
- test/integration/bugfixMultipleModules/ModuleA.hs +6/−0
- test/integration/bugfixMultipleModules/ModuleB.hs +20/−0
- test/integration/bugfixOutputToStdErr/Fib.hs +9/−0
- test/integration/bugfixWorkingDirectory/Fib.hs +10/−0
- test/integration/bugfixWorkingDirectory/description +10/−0
- test/integration/bugfixWorkingDirectory/examples/Fib.hs +22/−0
- test/integration/color/Foo.hs +8/−0
- test/integration/custom-package-conf/Bar.hs +10/−0
- test/integration/custom-package-conf/foo/Foo.hs +3/−0
- test/integration/custom-package-conf/foo/doctest-foo.cabal +8/−0
- test/integration/dos-line-endings/Fib.hs +10/−0
- test/integration/fail-fast/Bar.hs +8/−0
- test/integration/fail-fast/Foo.hs +24/−0
- test/integration/fail-fast/SetupBar.hs +12/−0
- test/integration/fail-fast/SetupFoo.hs +29/−0
- test/integration/failing-multiple/Foo.hs +16/−0
- test/integration/failing/Foo.hs +8/−0
- test/integration/it/Foo.hs +20/−0
- test/integration/it/Setup.hs +23/−0
- test/integration/local-stderr-binding/A.hs +11/−0
- test/integration/multiline/Multiline.hs +61/−0
- test/integration/parse-error/Foo.hs +5/−0
- test/integration/property-bool-with-type-signature/Foo.hs +5/−0
- test/integration/property-bool/Foo.hs +5/−0
- test/integration/property-failing/Foo.hs +5/−0
- test/integration/property-implicitly-quantified/Foo.hs +5/−0
- test/integration/property-quantified/Foo.hs +5/−0
- test/integration/property-setup/Foo.hs +9/−0
- test/integration/setup-skip-on-failure/Foo.hs +17/−0
- test/integration/setup/Foo.hs +10/−0
- test/integration/system-io-imported/A.hs +10/−0
- test/integration/template-haskell-bugfix/Main.hs +10/−0
- test/integration/template-haskell-bugfix/Printf.hs +25/−0
- test/integration/template-haskell/Foo.hs +16/−0
- test/integration/test-options/Foo.hs +14/−0
- test/integration/testBlankline/Fib.hs +10/−0
- test/integration/testCPP/Foo.hs +14/−0
- test/integration/testCombinedExample/Fib.hs +20/−0
- test/integration/testCommentLocation/Foo.hs +79/−0
- test/integration/testDocumentationForArguments/Fib.hs +7/−0
- test/integration/testFailOnMultiline/Fib.hs +13/−0
- test/integration/testImport/ModuleA.hs +6/−0
- test/integration/testImport/ModuleB.hs +12/−0
- test/integration/testPutStr/Fib.hs +13/−0
- test/integration/testSimple/Fib.hs +10/−0
- test/integration/trailing-whitespace/Foo.hs +8/−0
- test/integration/with-cbits/Bar.hs +9/−0
- test/parse/multiple-examples/Foo.hs +11/−0
- test/parse/no-examples/Fib.hs +10/−0
- test/parse/non-exported/Fib.hs +16/−0
- test/parse/property/Fib.hs +15/−0
- test/parse/setup-empty/Foo.hs +10/−0
- test/parse/setup-only/Foo.hs +9/−0
- test/parse/simple/Fib.hs +13/−0
+ CHANGES.markdown view
@@ -0,0 +1,224 @@+Changes in 0.25.0+ - Full GHC 9.14 compatibility / `-unit`-support++Changes in 0.24.3+ - Allow building with GHC 9.14 (#478)++Changes in 0.24.2+ - Use `GHC.ResponseFile.expandResponse`++Changes in 0.24.1+ - Interpret GHC response files++Changes in 0.24.0+ - cabal-doctest: Add support for cabal-install 3.14.*++Changes in 0.23.0+ - Add `--fail-fast`++Changes in 0.22.10+ - Make progress reporting more robust++Changes in 0.22.9+ - Use `-fprint-error-index-links=never` for GHC `>=9.10`++Changes in 0.22.8+ - cabal-doctest: Fix handling of options with optional arguments++Changes in 0.22.7+ - cabal-doctest: Accept component+ - cabal-doctest: Get rid of separate `cabal build` step+ - cabal-doctest: Add support for `--list-options`++Changes in 0.22.6+ - cabal-doctest: Take `with-compiler:` from `cabal-project` into account+ - cabal-doctest: Add support for `--with-compiler`+ - cabal-doctest: Fix `ghc-pkg` discovery logic+ - cabal-doctest: Cache `doctest` executables++Changes in 0.22.5+ - Add (experimental) `cabal-doctest` executable. This is guarded behind a+ flag for now, use `cabal install doctest -f cabal-doctest` to install it.++Changes in 0.22.4+ - Use `-Wno-unused-packages` for GHC `8.10` / `9.0` / `9.2`++Changes in 0.22.3+ - Use `-Wno-unused-packages` when extracting comments++Changes in 0.22.2+ - GHC 9.8 compatibility++Changes in 0.22.1+ - Add `Test.DocTest.Internal.Run.doctestWithRepl`++Changes in 0.22.0+ - Export more internals++Changes in 0.21.1+ - GHC 9.6 compatibility.++Changes in 0.21.0+ - Accept `--fast`, `--preserve-it` and `--verbose` via `--repl-options`++Changes in 0.20.1+ - GHC 9.4 compatibility. (#382)++Changes in 0.20.0+ - Allow doctest to be invoked via `cabal repl --with-compiler=doctest`+ - Include `ghc --info` output in `--info`+ - Make `--info` output formatting consistent with GHC++Changes in 0.19.0+ - Better support for `cabal v2-*`++Changes in 0.18.2+ - GHC 9.2 compatibility. (#305, thanks to Ryan Scott and Matthew Pickering)++Changes in 0.18.1+ - GHC 9.0 compatibility. (#275)++Changes in 0.18+ - Don't use unqualified references to `stderr` or `stdout` which may collide with definitions in user code. (#201)+ - Remove support for cabal-install sandboxes. They have been obsoleted in practice by Nix-style builds in cabal-install (i.e., the `v2-*` commands) and stack.++Changes in 0.17+ - #266:+ - doctest now annotates its internal marker string as a `String`, to prevent misbehaviour in `OverloadedStrings` environments. This has a theoretical chance of breakage; if you're affected, please open an issue.+ - `evalEcho` no longer preserves `it`.++Changes in 0.16.3+ - Add a cursor to highlight the differing portion between the+ expected and actual output. (#249)+ - GHC 8.10 compatibility. (#247, #257)++Changes in 0.16.2+ - Add doctest's necessary-for-operation options to GHC's command+ line at the end, so that they over-ride anything provided by the+ user. (#233)+ - Allow GHC 8.8.++Changes in 0.16.1+ - Fix loading plugins in doctests. (#224)+ - Require QuickCheck 2.13.1 or newer.+ - Remove dependency on `with-location`++Changes in 0.16.0.1+ - Bump bounds to allow GHC 8.6. (#210)++Changes in 0.16.0+ - Output format has changed to (hopefully) be more machine consumable. (#200)++Changes in 0.15.0+ - Add `--verbose` for printing each test as it is run++Changes in 0.14.1+ - Add test assets to source tarball (see #189)++Changes in 0.14.0+ - GHC 8.4 compatibility.++Changes in 0.13.0+ - Add `--preserve-it` for allowing the `it` variable to be preserved between examples++Changes in 0.12.0+ - Preserve the 'it' variable between examples++Changes in 0.11.4+ - Add `--fast`, which disables running `:reload` between example groups++Changes in 0.11.3+ - Add `--info`+ - Add `--no-magic`++Changes in 0.11.2+ - Make `...` match zero lines++Changes in 0.11.1+ - Fix an issue with Unicode output on Windows (see #149)++Changes in 0.11.0+ - Support for GHC 8.0.1-rc2++Changes in 0.10.1+ - Automatically expand directories into contained Haskell source files (thanks @snoyberg)+ - Add cabal_macros.h and autogen dir by default (thanks @snoyberg)++Changes in 0.10.0+ - Support HASKELL_PACKAGE_SANDBOXES (thanks @snoyberg)++Changes in 0.9.13+ - Add ellipsis as wildcard++Changes in 0.9.12+ - Add support for GHC 7.10++Changes in 0.9.11+ - Defaults ambiguous type variables to Integer (#74)++Changes in 0.9.10+ - Add support for the upcoming GHC 7.8 release++Changes in 0.9.9+ - Add support for multi-line statements++Changes in 0.9.8+ - Support for GHC HEAD (7.7)++Changes in 0.9.7+ - Ignore trailing whitespace when matching example output++Changes in 0.9.6+ - Fail gracefully if GHCi is not supported (#46)++Changes in 0.9.5+ - Fix a GHC panic with GHC 7.6.1 (#41)++Changes in 0.9.4+ - Respect HASKELL_PACKAGE_SANDBOX (#39)+ - Print path to ghc on --version++Changes in 0.9.3+ - Properly handle additional object files (#38)++Changes in 0.9.2+ - Add support for QuickCheck properties++Changes in 0.9.1+ - Fix an issue with GHC 7.6.1 and type families++Changes in 0.9.0+ - Add support for setup code (see README).+ - There is no distinction between example/interaction anymore. Each+ expression is counted as an example in the summary.++Changes in 0.8.0+ - Doctest now directly accepts arbitrary GHC options, prefixing GHC options+ with --optghc is no longer necessary++Changes in 0.7.0+ - Print source location for failing tests+ - Output less clutter on failing examples+ - Expose Doctest's functionality through a very simplistic API, which can be+ used for cabal integration++Changes in 0.6.1+ - Fix a parser bug with CR+LF line endings++Changes in 0.6.0+ - Support for ghc-7.4+ - Doctest now comes with it's own parser and does not depend on Haddock+ anymore++Changes in 0.5.2+ - Proper handling of singular/plural when printing stats+ - Improve handling of invalid command line options++Changes in 0.5.1+ - Adapted for ghc-7.2++Changes in 0.5.0+ - Print number of interactions to stderr before running tests+ - Exit with exitFailure on failed tests+ - Improve documentation+ - Give a useful error message if ghc is not executable
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2009-2015 Simon Hengel <sol@typeful.net>+Copyright (c) 2009-2026 Simon Hengel <sol@typeful.net> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal
+ README.md view
@@ -0,0 +1,612 @@+# Doctest: Test interactive Haskell examples++`doctest` is a tool that checks+[examples](https://haskell-haddock.readthedocs.io/latest/markup.html#examples)+and+[properties](https://haskell-haddock.readthedocs.io/latest/markup.html#properties)+in Haddock comments.+It is similar in spirit to the [popular Python module with the same name](https://docs.python.org/3/library/doctest.html).++* [Getting started](#getting-started)+ * [Installation](#installation)+ * [A basic example](#a-basic-example)+* [Running doctest for a Cabal package](#running-doctest-for-a-cabal-package)+ * [Passing doctest options to cabal repl](#passing-doctest-options-to-cabal-repl)+ * [Cabal integration](#cabal-integration)+* [Writing examples and properties](#writing-examples-and-properties)+ * [Example groups](#example-groups)+ * [A note on performance](#a-note-on-performance)+ * [Setup code](#setup-code)+ * [Multi-line input](#multi-line-input)+ * [Multi-line output](#multi-line-output)+ * [Matching arbitrary output](#matching-arbitrary-output)+ * [QuickCheck properties](#quickcheck-properties)+ * [Hiding examples from Haddock](#hiding-examples-from-haddock)+ * [Using GHC extensions](#using-ghc-extensions)+* [Limitations](#limitations)+* [Doctest in the wild](#doctest-in-the-wild)+* [Development](#development)+* [Contributors](#contributors)+++# Getting started++## Installation++`doctest` is available from+[Hackage](https://hackage.haskell.org/package/doctest).+Install it with:++ cabal update && cabal install --ignore-project doctest++Make sure that Cabal's `installdir` is on your `PATH`.++On Linux / macOS / BSD:++```bash+ # requires cabal-install version 3.12, or later+export PATH="$(cabal -v0 path --installdir):$PATH"+```+or+```bash+export PATH="$HOME/.local/bin:$PATH"+```++On Windows with PowerShell:++```pwsh+# requires cabal-install version 3.12, or later+$Env:PATH = "$(cabal -v0 path --installdir)" + ";" + $Env:PATH+```++## A basic example++Below is a small Haskell module.+The module contains a Haddock comment with some examples of interaction.+The examples demonstrate how the module is supposed to be used.++```haskell+-- src/Fib.hs+module Fib where++-- | Compute Fibonacci numbers+--+-- Examples:+--+-- >>> fib 10+-- 55+--+-- >>> fib 5+-- 5+fib :: Int -> Int+fib 0 = 0+fib 1 = 1+fib n = fib (n - 1) + fib (n - 2)++```++(A comment line starting with `>>>` denotes an _expression_.+All comment lines following an expression denote the _result_ of that expression.+Result is defined by what a+[REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop) (e.g. ghci)+prints to `stdout` and `stderr` when evaluating that expression.)++With `doctest` you can check whether the implementation satisfies the given+examples:++```+doctest src/Fib.hs+```+++# Running `doctest` for a Cabal package++The easiest way to run `doctest` for a Cabal package is via `cabal repl --with-compiler=doctest`.++This doesn't make a big difference for a simple package, but in more involved+situations `cabal` will make sure that all dependencies are available and it+will pass any required GHC options to `doctest`.++A simple `.cabal` file for `Fib` looks like this:++```cabal+-- fib.cabal+cabal-version: 1.12++name: fib+version: 0.0.0+build-type: Simple++library+ build-depends: base == 4.*+ hs-source-dirs: src+ exposed-modules: Fib+ default-language: Haskell2010++```++With a `.cabal` file in place, it is possible to run `doctest` via `cabal repl`:++```bash+$ cabal repl --with-compiler=doctest+...+Examples: 2 Tried: 2 Errors: 0 Failures: 0+```+++Notes:++- If you use properties you need to pass `--build-depends=QuickCheck` and+ `--build-depends=template-haskell` to `cabal repl`.++- You likely want to reset the warning strategy for `cabal repl` with+ `--repl-options='-w -Wdefault'`.++- `doctest` always uses the version of GHC it was compiled with. Reinstalling+ `doctest` with `cabal install doctest --overwrite-policy=always` before each+ invocation ensures that it uses the same version of GHC as is on the `PATH`.++- Technically, `cabal build` is not necessary. `cabal repl --with-compiler=doctest`+ will build any dependencies as needed. However, it's more robust to run+ `cabal build` first (specifically it is not a good idea to build+ `ghc-paths` with `--with-compiler=doctest`).++So a more robust way to call `doctest` is as follows:++```+cabal install doctest --ignore-project --overwrite-policy=always && cabal build && cabal repl --build-depends=QuickCheck --build-depends=template-haskell --with-compiler=doctest --repl-options='-w -Wdefault'+```++(This is what you want to use on CI.)++## Passing `doctest` options to `cabal repl`++You can pass `doctest` options like `--fast`, `--preserve-it` and `--verbose` to+`cabal repl` via `--repl-options`.++Example:++```bash+$ cabal repl --with-compiler=doctest --repl-options=--verbose+### Started execution at src/Fib.hs:7.+### example:+fib 10+### Successful!++### Started execution at src/Fib.hs:10.+### example:+fib 5+### Successful!++# Final summary:+Examples: 2 Tried: 2 Errors: 0 Failures: 0+```++## Cabal integration++***NOTE:*** This feature is experimental.++***NOTE:*** This feature requires `cabal-install` version 3.12 or later.+++```bash+$ cabal install --ignore-project doctest --flag cabal-doctest+```++```bash+$ cabal doctest+Examples: 2 Tried: 2 Errors: 0 Failures: 0+```++```bash+$ cabal doctest -w ghc-8.6.5+Examples: 2 Tried: 2 Errors: 0 Failures: 0+```++```bash+$ cabal doctest --repl-options=--verbose+### Started execution at src/Fib.hs:7.+### example:+fib 10+### Successful!++### Started execution at src/Fib.hs:10.+### example:+fib 5+### Successful!++# Final summary:+Examples: 2 Tried: 2 Errors: 0 Failures: 0+```++```bash+$ cabal doctest --build-depends transformers+Examples: 2 Tried: 2 Errors: 0 Failures: 0+```++# Writing examples and properties++## Example groups++Examples from a single Haddock comment are grouped together and share the same+scope. E.g. the following works:++```haskell+-- |+-- >>> let x = 23+-- >>> x + 42+-- 65+```++If an example fails, subsequent examples from the same group are skipped. E.g.+for++```haskell+-- |+-- >>> let x = 23+-- >>> let n = x + y+-- >>> print n+```++`print n` is skipped, because `let n = x + y` fails (as `y` is not in scope).++### A note on performance++By default, `doctest` calls `:reload` between each group to clear GHCi's scope+of any local definitions. This ensures that previous examples cannot influence+later ones. However, it can lead to performance penalties if you are using+`doctest` in a project with many modules. One possible remedy is to pass the+`--fast` flag to `doctest`, which disables calling `:reload` between groups.+If `doctest`s are running too slowly, you might consider using `--fast`.+(With the caveat that the order in which groups appear now matters!)++However, note that due to a+[bug on GHC 8.2.1 or later](https://gitlab.haskell.org/ghc/ghc/-/issues/14052),+the performance of `--fast` suffers significantly when combined with the+`--preserve-it` flag (which keeps the value of GHCi's `it` value between+examples).++## Setup code++You can put setup code in a [named chunk][named-chunks] with the name `$setup`.+The setup code is run before each example group. If the setup code produces+any errors/failures, all tests from that module are skipped.++Here is an example:++```haskell+module Foo where++import Bar.Baz++-- $setup+-- >>> let x = 23 :: Int++-- |+-- >>> foo + x+-- 65+foo :: Int+foo = 42+```++Note that you should not place setup code inbetween the module header (`module+... where`) and import declarations. GHC will not be able to parse it ([issue+ #167](https://github.com/sol/doctest/issues/167)). It is best to place setup+code right after import declarations, but due to its declarative nature you can+place it anywhere inbetween top level declarations as well.+++## Multi-line input+GHCi supports commands which span multiple lines, and the same syntax works for doctest:++```haskell+-- |+-- >>> :{+-- let+-- x = 1+-- y = 2+-- in x + y + multiline+-- :}+-- 6+multiline = 3+```++Note that `>>>` can be left off for the lines following the first: this is so that+haddock does not strip leading whitespace. The expected output has whitespace+stripped relative to the :}.++Some peculiarities on the ghci side mean that whitespace at the very start is lost.+This breaks the example `broken`, since the x and y aren't aligned from ghci's+perspective. A workaround is to avoid leading space, or add a newline such+that the indentation does not matter:++```haskell+{- | >>> :{+let x = 1+ y = 2+ in x + y + works+:}+6+-}+works = 3++{- | >>> :{+ let x = 1+ y = 2+ in x + y + broken+:}+3+-}+broken = 3+```++## Multi-line output+If there are no blank lines in the output, multiple lines are handled+automatically.++```haskell+-- | >>> putStr "Hello\nWorld!"+-- Hello+-- World!+```++If however the output contains blank lines, they must be noted+explicitly with `<BLANKLINE>`. For example,++```haskell+import Data.List ( intercalate )++-- | Double-space a paragraph.+--+-- Examples:+--+-- >>> let s1 = "\"Every one of whom?\""+-- >>> let s2 = "\"Every one of whom do you think?\""+-- >>> let s3 = "\"I haven't any idea.\""+-- >>> let paragraph = unlines [s1,s2,s3]+-- >>> putStrLn $ doubleSpace paragraph+-- "Every one of whom?"+-- <BLANKLINE>+-- "Every one of whom do you think?"+-- <BLANKLINE>+-- "I haven't any idea."+--+doubleSpace :: String -> String+doubleSpace = (intercalate "\n\n") . lines+```++## Matching arbitrary output+Any lines containing only three dots (`...`) will match one or more lines with+arbitrary content. For instance,++```haskell+-- |+-- >>> putStrLn "foo\nbar\nbaz"+-- foo+-- ...+-- baz+```++If a line contains three dots and additional content, the three dots will match+anything *within that line*:++```haskell+-- |+-- >>> putStrLn "foo bar baz"+-- foo ... baz+```++## QuickCheck properties++Haddock has markup support for properties. Doctest can verify properties with+QuickCheck. A simple property looks like this:++```haskell+-- |+-- prop> \xs -> sort xs == (sort . sort) (xs :: [Int])+```++The lambda abstraction is optional and can be omitted:++```haskell+-- |+-- prop> sort xs == (sort . sort) (xs :: [Int])+```++A complete example that uses setup code is below:++```haskell+module Fib where++-- $setup+-- >>> import Control.Applicative+-- >>> import Test.QuickCheck+-- >>> newtype Small = Small Int deriving Show+-- >>> instance Arbitrary Small where arbitrary = Small . (`mod` 10) <$> arbitrary++-- | Compute Fibonacci numbers+--+-- The following property holds:+--+-- prop> \(Small n) -> fib n == fib (n + 2) - fib (n + 1)+fib :: Int -> Int+fib 0 = 0+fib 1 = 1+fib n = fib (n - 1) + fib (n - 2)+```++If you see an error like the following, ensure that+[QuickCheck](https://hackage.haskell.org/package/QuickCheck) is visible to+`doctest` (e.g. by passing `--build-depends=QuickCheck` to `cabal repl`).++```haskell+<interactive>:39:3:+ Not in scope: ‘polyQuickCheck’+ In the splice: $(polyQuickCheck (mkName "doctest_prop"))++<interactive>:39:3:+ GHC stage restriction:+ ‘polyQuickCheck’ is used in a top-level splice or annotation,+ and must be imported, not defined locally+ In the expression: polyQuickCheck (mkName "doctest_prop")+ In the splice: $(polyQuickCheck (mkName "doctest_prop"))+```++## Hiding examples from Haddock++You can put examples into [named chunks][named-chunks], and not refer to them+in the export list. That way they will not be part of the generated Haddock+documentation, but Doctest will still find them.++```haskell+-- $+-- >>> 1 + 1+-- 2+```++[named-chunks]: https://haskell-haddock.readthedocs.io/latest/markup.html#named-chunks++## Using GHC extensions++There's two sets of GHC extensions involved when running Doctest:++1. The set of GHC extensions that are active when compiling the module code+ (excluding the doctest examples). The easiest way to specify these+ extensions is through [LANGUAGE pragmas][language-pragma] in your source+ files.++1. The set of GHC extensions that are active when executing the Doctest+ examples. (These are not influenced by the LANGUAGE pragmas in the file.)+ The recommended way to enable extensions for Doctest examples is to switch+ them on like this:++```haskell+-- |+-- >>> :seti -XTupleSections+-- >>> fst' $ (1,) 2+-- 1+fst' :: (a, b) -> a+fst' = fst+```++Alternatively you can pass any GHC options to Doctest, e.g.:++ doctest -XCPP Foo.hs++These options will affect both the loading of the module and the execution of+the Doctest examples.++If you want to omit the information which language extensions are enabled from+the Doctest examples you can use the method described in [Hiding examples from+Haddock](#hiding-examples-from-haddock), e.g.:++```haskell+-- $+-- >>> :seti -XTupleSections+```++[language-pragma]: https://downloads.haskell.org/ghc/latest/docs/users_guide/exts/pragmas.html#language-pragma++# Limitations+++- Doctests only works on platforms that have support for GHC's `--interactive` mode (`ghci`).+++- Due to [a GHC bug](https://gitlab.haskell.org/ghc/ghc/-/issues/20670), running+ `:set -XTemplateHaskell` within `ghci` may unload any modules that were+ specified on the command-line.++ To address this `doctest >= 0.19.0` does two things:++ 1. Doctest always enables `-XTemplateHaskell`. So it is safe to use Template+ Haskell in examples without enabling the extension explicitly.+ 1. Doctest filters out `-XTemplateHaskell` from single-line `:set`-statements.+ So it is still safe to include `:set -XTemplateHaskell` in examples for+ documentation purposes. It may just not work as intended in `ghci` due to+ that GHC bug.++ Doctest does not filter out `-XTemplateHaskell` from multi-line+ `:set`-statements. So if you e.g. use++ ```+ >>> :{+ :set -XTemplateHaskell+ :}+ ```+ then you are on your own.++ Note that all platforms that support `--interactive` also support+ `-XTemplateHaskell`. So this approach does not reduce Doctest's platform+ support.++- Modules that are rejected by `haddock` will not work with `doctest`. This+ can mean that `doctest` fails on input that is accepted by GHC (e.g.+ [#251](https://github.com/sol/doctest/issues/251)).++- Doctest works best with UTF-8. If your locale is e.g. `LC_ALL=C`, you may+ want to invoke `doctest` with `LC_ALL=C.UTF-8`.++# Doctest in the wild++You can find real world examples of `Doctest` being used below:++ * [base Data/Maybe.hs](https://github.com/ghc/ghc/blob/669cbef03c220de43b0f88f2b2238bf3c02ed64c/libraries/base/Data/Maybe.hs#L36-L79)+ * [base Data/Functor.hs](https://github.com/ghc/ghc/blob/669cbef03c220de43b0f88f2b2238bf3c02ed64c/libraries/base/Data/Functor.hs#L34-L64)+++# Development++Discuss your ideas first, ideally by opening an issue on GitHub.++Add tests for new features, and make sure that the test suite passes with your+changes.++ cabal build && cabal exec $(cabal list-bin spec)+++# Contributors++ * Simon Hengel+ * quasicomputational+ * Kazu Yamamoto+ * Andreas Abel+ * Michael Snoyman+ * Michael Orlitzky+ * Sakari Jokinen+ * Adam Vogt+ * Ryan Scott+ * Oleg Grenrus+ * Sönke Hahn+ * Edward Kmett+ * Elliot Marsden+ * Greg Pfeil+ * Ignat Insarov+ * Julian K. Arni+ * Takano Akio+ * Joachim Breitner+ * Alan Zimmerman+ * Alexander Bernauer+ * Alexandre Esteves+ * Anders Persson+ * Ankit Ahuja+ * Artyom Kazak+ * Gabor Greif+ * Guillaume Bouchard+ * Hiroki Hattori+ * Jens Petersen+ * John Chee+ * João Cristóvão+ * Leon Schoorl+ * Levent Erkok+ * Luke Murphy+ * Matvey Aksenov+ * Mitchell Rosen+ * Nick Smallbone+ * Nikos Baxevanis+ * Tamar Christina+ * Veronika Romashkina++For up-to-date list, query++ git shortlog -s
doctest.cabal view
@@ -1,114 +1,304 @@-name: doctest-version: 0.9.13-synopsis: Test interactive Haskell examples-description: The doctest program checks examples in source code comments.- It is modeled after doctest for Python- (<http://docs.python.org/library/doctest.html>).- .- Documentation is at- <https://github.com/sol/doctest#readme>.-category: Testing-bug-reports: https://github.com/sol/doctest/issues-homepage: https://github.com/sol/doctest#readme-license: MIT-license-file: LICENSE-copyright: (c) 2009-2015 Simon Hengel-author: Simon Hengel <sol@typeful.net>-maintainer: Simon Hengel <sol@typeful.net>-build-type: Simple-cabal-version: >= 1.8-extra-source-files: example/example.cabal- , example/src/Example.hs- , example/test/doctests.hs+cabal-version: 1.12 +-- This file has been generated from package.yaml by hpack version 0.39.1.+--+-- see: https://github.com/sol/hpack++name: doctest+version: 0.25.0+synopsis: Test interactive Haskell examples+description: `doctest` is a tool that checks [examples](https://www.haskell.org/haddock/doc/html/ch03s08.html#idm140354810775744)+ and [properties](https://www.haskell.org/haddock/doc/html/ch03s08.html#idm140354810771856)+ in Haddock comments.+ It is similar in spirit to the [popular Python module with the same name](https://docs.python.org/3/library/doctest.html).+ .+ Documentation is at <https://github.com/sol/doctest#readme>.+category: Testing+bug-reports: https://github.com/sol/doctest/issues+homepage: https://github.com/sol/doctest#readme+license: MIT+license-file: LICENSE+copyright: (c) 2009-2026 Simon Hengel+author: Simon Hengel <sol@typeful.net>+maintainer: Simon Hengel <sol@typeful.net>+build-type: Simple+extra-source-files:+ example/example.cabal+ example/src/Example.hs+ example/test/doctests.hs+ test/parse/multiple-examples/Foo.hs+ test/parse/no-examples/Fib.hs+ test/parse/non-exported/Fib.hs+ test/parse/property/Fib.hs+ test/parse/setup-empty/Foo.hs+ test/parse/setup-only/Foo.hs+ test/parse/simple/Fib.hs+ test/extract/argument-list/Foo.hs+ test/extract/comment-order/Foo.hs+ test/extract/declaration/Foo.hs+ test/extract/dos-line-endings/Foo.hs+ test/extract/export-list/Foo.hs+ test/extract/imported-module/Bar.hs+ test/extract/imported-module/Baz.hs+ test/extract/module-header/Foo.hs+ test/extract/named-chunks/Foo.hs+ test/extract/regression/Fixity.hs+ test/extract/regression/ForeignImport.hs+ test/extract/regression/ParallelListComp.hs+ test/extract/regression/ParallelListCompClass.hs+ test/extract/regression/RewriteRules.hs+ test/extract/regression/RewriteRulesWithSigs.hs+ test/extract/setup/Foo.hs+ test/extract/th/Bar.hs+ test/extract/th/Foo.hs+ test/extract/type-class-args/Foo.hs+ test/extract/type-class/Foo.hs+ test/extract/type-families/Foo.hs+ test/integration/bugfixImportHierarchical/ModuleA.hs+ test/integration/bugfixImportHierarchical/ModuleB.hs+ test/integration/bugfixMultipleModules/ModuleA.hs+ test/integration/bugfixMultipleModules/ModuleB.hs+ test/integration/bugfixOutputToStdErr/Fib.hs+ test/integration/bugfixWorkingDirectory/description+ test/integration/bugfixWorkingDirectory/examples/Fib.hs+ test/integration/bugfixWorkingDirectory/Fib.hs+ test/integration/color/Foo.hs+ test/integration/custom-package-conf/Bar.hs+ test/integration/custom-package-conf/foo/doctest-foo.cabal+ test/integration/custom-package-conf/foo/Foo.hs+ test/integration/dos-line-endings/Fib.hs+ test/integration/fail-fast/Bar.hs+ test/integration/fail-fast/Foo.hs+ test/integration/fail-fast/SetupBar.hs+ test/integration/fail-fast/SetupFoo.hs+ test/integration/failing-multiple/Foo.hs+ test/integration/failing/Foo.hs+ test/integration/it/Foo.hs+ test/integration/it/Setup.hs+ test/integration/local-stderr-binding/A.hs+ test/integration/multiline/Multiline.hs+ test/integration/parse-error/Foo.hs+ test/integration/property-bool-with-type-signature/Foo.hs+ test/integration/property-bool/Foo.hs+ test/integration/property-failing/Foo.hs+ test/integration/property-implicitly-quantified/Foo.hs+ test/integration/property-quantified/Foo.hs+ test/integration/property-setup/Foo.hs+ test/integration/setup-skip-on-failure/Foo.hs+ test/integration/setup/Foo.hs+ test/integration/system-io-imported/A.hs+ test/integration/template-haskell-bugfix/Main.hs+ test/integration/template-haskell-bugfix/Printf.hs+ test/integration/template-haskell/Foo.hs+ test/integration/test-options/Foo.hs+ test/integration/testBlankline/Fib.hs+ test/integration/testCombinedExample/Fib.hs+ test/integration/testCommentLocation/Foo.hs+ test/integration/testCPP/Foo.hs+ test/integration/testDocumentationForArguments/Fib.hs+ test/integration/testFailOnMultiline/Fib.hs+ test/integration/testImport/ModuleA.hs+ test/integration/testImport/ModuleB.hs+ test/integration/testPutStr/Fib.hs+ test/integration/testSimple/Fib.hs+ test/integration/trailing-whitespace/Foo.hs+ test/integration/with-cbits/Bar.hs+ test/integration/with-cbits/foo.c+ CHANGES.markdown+ README.md+ source-repository head type: git location: https://github.com/sol/doctest +flag cabal-doctest+ description: Install (experimental) cabal-doctest executable+ manual: True+ default: False+ library- exposed-modules:- Test.DocTest- ghc-options:- -Wall+ ghc-options: -Wall hs-source-dirs: src+ default-extensions:+ NamedFieldPuns+ RecordWildCards+ DeriveFunctor+ NoImplicitPrelude+ exposed-modules:+ Test.DocTest+ Test.DocTest.Internal.Extract+ Test.DocTest.Internal.Location+ Test.DocTest.Internal.Parse+ Test.DocTest.Internal.Run+ Test.DocTest.Internal.Cabal other-modules:+ Cabal+ Cabal.Options+ Cabal.Paths+ Cabal.ReplOptions Extract- , GhcUtil- , Interpreter- , Location- , Help- , Parse- , Paths_doctest- , Property- , Runner- , Runner.Example- , Run- , Util- , Sandbox+ GhcUtil+ Imports+ Info+ Interpreter+ Language.Haskell.GhciWrapper+ Location+ Options+ PackageDBs+ Parse+ Property+ Run+ Runner+ Runner.Example+ Util+ Paths_doctest build-depends:- base == 4.*- , ghc >= 7.0 && < 7.12- , syb >= 0.3 && < 0.5+ base >=4.12 && <5+ , code-page >=0.1+ , containers , deepseq , directory+ , exceptions , filepath+ , ghc >=8.6 && <9.16+ , ghc-paths >=0.1.0.9 , process- , ghc-paths >= 0.1.0.9+ , syb >=0.3+ , temporary , transformers+ default-language: Haskell2010+ if impl(ghc >= 9.0)+ ghc-options: -fwarn-unused-packages+ if impl(ghc >= 9.8)+ ghc-options: -fno-warn-x-partial +executable cabal-doctest+ main-is: driver/cabal-doctest.hs+ other-modules:+ Paths_doctest+ default-extensions:+ NamedFieldPuns+ RecordWildCards+ DeriveFunctor+ NoImplicitPrelude+ ghc-options: -Wall -threaded+ build-depends:+ base >=4.12 && <5+ , doctest+ default-language: Haskell2010+ if impl(ghc >= 9.0)+ ghc-options: -fwarn-unused-packages+ if impl(ghc >= 9.8)+ ghc-options: -fno-warn-x-partial+ if flag(cabal-doctest)+ buildable: True+ else+ buildable: False+ executable doctest- main-is:- Main.hs- ghc-options:- -Wall -threaded- hs-source-dirs:- driver+ main-is: driver/doctest.hs+ other-modules:+ Paths_doctest+ ghc-options: -Wall -threaded+ default-extensions:+ NamedFieldPuns+ RecordWildCards+ DeriveFunctor+ NoImplicitPrelude build-depends:- base == 4.*+ base >=4.12 && <5 , doctest+ default-language: Haskell2010+ if impl(ghc >= 9.0)+ ghc-options: -fwarn-unused-packages+ if impl(ghc >= 9.8)+ ghc-options: -fno-warn-x-partial test-suite spec- main-is:- Spec.hs- type:- exitcode-stdio-1.0- ghc-options:- -Wall -threaded- cpp-options:- -DTEST+ main-is: Spec.hs+ other-modules:+ Cabal.OptionsSpec+ Cabal.PathsSpec+ Cabal.ReplOptionsSpec+ ExtractSpec+ InfoSpec+ InterpreterSpec+ Language.Haskell.GhciWrapperSpec+ LocationSpec+ MainSpec+ OptionsSpec+ PackageDBsSpec+ ParseSpec+ PropertySpec+ Runner.ExampleSpec+ RunnerSpec+ RunSpec+ UtilSpec+ Cabal+ Cabal.Options+ Cabal.Paths+ Cabal.ReplOptions+ Extract+ GhcUtil+ Imports+ Info+ Interpreter+ Language.Haskell.GhciWrapper+ Location+ Options+ PackageDBs+ Parse+ Property+ Run+ Runner+ Runner.Example+ Test.DocTest+ Test.DocTest.Internal.Cabal+ Test.DocTest.Internal.Extract+ Test.DocTest.Internal.Location+ Test.DocTest.Internal.Parse+ Test.DocTest.Internal.Run+ Util+ Paths_doctest+ type: exitcode-stdio-1.0+ ghc-options: -Wall -threaded+ cpp-options: -DTEST hs-source-dirs:- src, test+ test+ src+ default-extensions:+ NamedFieldPuns+ RecordWildCards+ DeriveFunctor+ NoImplicitPrelude c-sources: test/integration/with-cbits/foo.c+ build-tool-depends:+ hspec-discover:hspec-discover build-depends:- base- , ghc- , syb+ HUnit+ , QuickCheck >=2.13.1+ , base >=4.12 && <5+ , code-page >=0.1+ , containers , deepseq , directory+ , exceptions , filepath+ , ghc >=8.6 && <9.16+ , ghc-paths >=0.1.0.9+ , hspec >=2.3.0+ , hspec-core >=2.3.0+ , mockery , process- , ghc-paths+ , silently >=1.2.4+ , stringbuilder >=0.4+ , syb >=0.3+ , temporary , transformers-- , base-compat >= 0.4.2- , HUnit- , hspec >= 1.5.1- , QuickCheck >= 2.5- , stringbuilder >= 0.4- , silently >= 1.2.4- , setenv--test-suite doctests- main-is:- doctests.hs- type:- exitcode-stdio-1.0- ghc-options:- -Wall -threaded- hs-source-dirs:- test- build-depends:- base- , doctest+ default-language: Haskell2010+ if impl(ghc >= 9.0)+ ghc-options: -fwarn-unused-packages+ if impl(ghc >= 9.8)+ ghc-options: -fno-warn-x-partial
− driver/Main.hs
@@ -1,6 +0,0 @@-module Main (main) where-import Test.DocTest-import System.Environment (getArgs)--main :: IO ()-main = getArgs >>= doctest
+ driver/cabal-doctest.hs view
@@ -0,0 +1,8 @@+module Main (main) where++import Prelude+import qualified Test.DocTest.Internal.Cabal as Cabal+import System.Environment (getArgs)++main :: IO ()+main = getArgs >>= Cabal.doctest
+ driver/doctest.hs view
@@ -0,0 +1,8 @@+module Main (main) where++import Prelude+import Test.DocTest+import System.Environment (getArgs)++main :: IO ()+main = getArgs >>= doctest
+ src/Cabal.hs view
@@ -0,0 +1,77 @@+{-# LANGUAGE LambdaCase #-}+module Cabal (externalCommand) where++import Imports++import Data.List+import Data.Version (makeVersion)+import System.IO+import System.IO.Temp+import System.Environment+import System.Directory+import System.FilePath+import System.Process++import qualified Info+import Cabal.Paths+import Cabal.Options++externalCommand :: [String] -> IO ()+externalCommand args = do+ lookupEnv "CABAL" >>= \ case+ Nothing -> run "cabal" args+ Just cabal -> run cabal (drop 1 args)++run :: String -> [String] -> IO ()+run cabal args = do+ rejectUnsupportedOptions args++ Paths{..} <- paths cabal (discardReplOptions args)++ let+ doctest = cache </> "doctest" <> "-" <> Info.version+ script = cache </> "init-ghci-" <> Info.version++ doesFileExist doctest >>= \ case+ True -> pass+ False -> callProcess cabal [+ "install" , "doctest-" <> Info.version+ , "--flag", "-cabal-doctest"+ , "--ignore-project"+ , "--installdir", cache+ , "--program-suffix", "-" <> Info.version+ , "--install-method=copy"+ , "--with-compiler", ghc+ ]++ doesFileExist script >>= \ case+ True -> pass+ False -> writeFileAtomically script ":seti -w -Wdefault"++ callProcess doctest ["--version"]++ let+ repl extraArgs = call cabal ("repl"+ : "--build-depends=QuickCheck"+ : "--build-depends=template-haskell"+ : ("--repl-options=-ghci-script=" <> script)+ : args ++ extraArgs)++ case ghcVersion < makeVersion [9,4] of+ True -> do+ callProcess cabal ("build" : "--only-dependencies" : discardReplOptions args)+ repl ["--with-compiler", doctest, "--with-hc-pkg", ghcPkg]++ False -> do+ withSystemTempDirectory "cabal-doctest" $ \ dir -> do+ repl ["--keep-temp-files", "--repl-multi-file", dir]+ files <- filter (isSuffixOf "-inplace") <$> listDirectory dir+ let options = concatMap (\ file -> ["-unit", '@' : dir </> file]) files+ call doctest ("--no-magic" : options)++writeFileAtomically :: FilePath -> String -> IO ()+writeFileAtomically name contents = do+ (tmp, h) <- openTempFile (takeDirectory name) (takeFileName name)+ hPutStr h contents+ hClose h+ renameFile tmp name
+ src/Cabal/Options.hs view
@@ -0,0 +1,78 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE LambdaCase #-}+module Cabal.Options (+ rejectUnsupportedOptions+, discardReplOptions++#ifdef TEST+, replOnlyOptions+#endif+) where++import Imports++import System.Exit+import System.Console.GetOpt++import Data.Set (Set)+import qualified Data.Set as Set++import qualified Cabal.ReplOptions as Repl++replOnlyOptions :: Set String+replOnlyOptions = Set.fromList [+ "-z"+ , "--ignore-project"+ , "--repl-no-load"+ , "--repl-options"+ , "--repl-multi-file"+ , "-b"+ , "--build-depends"+ , "--no-transitive-deps"+ , "--enable-multi-repl"+ , "--disable-multi-repl"+ , "--with-repl"+ ]++rejectUnsupportedOptions :: [String] -> IO ()+rejectUnsupportedOptions args = case getOpt' Permute options args of+ (xs, _, _, _) | ListOptions `elem` xs -> do+ let+ names :: [String]+ names = concat [map (\ c -> ['-', c]) short ++ map ("--" <> ) long | Option short long _ _ <- documentedOptions]+ putStr (unlines names)+ exitSuccess+ (_, _, unsupported : _, _) -> do+ die $ "Error: cabal: unrecognized 'doctest' option `" <> unsupported <> "'"+ _ -> pass++data Argument = Argument String (Maybe String) | ListOptions+ deriving (Eq, Show)++options :: [OptDescr Argument]+options =+ Option [] ["list-options"] (NoArg ListOptions) ""+ : documentedOptions++documentedOptions :: [OptDescr Argument]+documentedOptions = map toOptDescr Repl.options+ where+ toOptDescr :: Repl.Option -> OptDescr Argument+ toOptDescr (Repl.Option long short arg help) = Option (maybeToList short) [long] (toArgDescr long arg) help++ toArgDescr :: String -> Repl.Argument -> ArgDescr Argument+ toArgDescr long = \ case+ Repl.Argument name -> ReqArg (argument . Just) name+ Repl.NoArgument -> NoArg (argument Nothing)+ Repl.OptionalArgument name -> OptArg argument name+ where+ argument :: Maybe String -> Argument+ argument value = Argument ("--" <> long) value++discardReplOptions :: [String] -> [String]+discardReplOptions args = case getOpt Permute options args of+ (xs, _, _) -> [renderArgument name value | Argument name value <- xs, Set.notMember name replOnlyOptions]+ where+ renderArgument name = \ case+ Nothing -> name+ Just value -> name <> "=" <> value
+ src/Cabal/Paths.hs view
@@ -0,0 +1,96 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE StrictData #-}+module Cabal.Paths (+ Paths(..)+, paths+) where++import Imports++import Data.Char+import Data.Tuple+import Data.Version hiding (parseVersion)+import qualified Data.Version as Version+import System.Exit hiding (die)+import System.Directory+import System.FilePath+import System.IO+import System.Process+import Text.ParserCombinators.ReadP++data Paths = Paths {+ ghcVersion :: Version+, ghc :: FilePath+, ghcPkg :: FilePath+, cache :: FilePath+} deriving (Eq, Show)++paths :: FilePath -> [String] -> IO Paths+paths cabal args = do+ cabalVersion <- strip <$> readProcess cabal ["--numeric-version"] ""++ let+ required :: Version+ required = makeVersion [3, 12]++ when (parseVersion cabalVersion < Just required) $ do+ die $ "'cabal-install' version " <> showVersion required <> " or later is required, but 'cabal --numeric-version' returned " <> cabalVersion <> "."++ values <- parseFields <$> readProcess cabal ("path" : args ++ ["-v0"]) ""++ let+ getPath :: String -> String -> IO FilePath+ getPath subject key = case lookup key values of+ Nothing -> die $ "Cannot determine the path to " <> subject <> ". Running 'cabal path' did not return a value for '" <> key <> "'."+ Just path -> canonicalizePath path++ ghc <- getPath "'ghc'" "compiler-path"++ ghcVersionString <- strip <$> readProcess ghc ["--numeric-version"] ""++ ghcVersion <- case parseVersion ghcVersionString of+ Nothing -> die $ "Cannot determine GHC version from '" <> ghcVersionString <> "'."+ Just version -> return version++ let+ ghcPkg :: FilePath+ ghcPkg = takeDirectory ghc </> "ghc-pkg-" <> ghcVersionString+#ifdef mingw32_HOST_OS+ <.> "exe"+#endif++ doesFileExist ghcPkg >>= \ case+ True -> pass+ False -> die $ "Cannot determine the path to 'ghc-pkg' from '" <> ghc <> "'. File '" <> ghcPkg <> "' does not exist."++ abi <- strip <$> readProcess ghcPkg ["--no-user-package-db", "field", "base", "abi", "--simple-output"] ""++ cache_home <- getPath "Cabal's cache directory" "cache-home"+ let cache = cache_home </> "doctest" </> "ghc-" <> ghcVersionString <> "-" <> abi++ createDirectoryIfMissing True cache++ return Paths {+ ghcVersion+ , ghc+ , ghcPkg+ , cache+ }+ where+ parseFields :: String -> [(String, FilePath)]+ parseFields = map parseField . lines++ parseField :: String -> (String, FilePath)+ parseField input = case break (== ':') input of+ (key, ':' : value) -> (key, dropWhile isSpace value)+ (key, _) -> (key, "")++die :: String -> IO a+die message = do+ hPutStrLn stderr "Error: [cabal-doctest]"+ hPutStrLn stderr message+ exitFailure++parseVersion :: String -> Maybe Version+parseVersion = lookup "" . map swap . readP_to_S Version.parseVersion
+ src/Cabal/ReplOptions.hs view
@@ -0,0 +1,195 @@+module Cabal.ReplOptions (+ Option(..)+, Argument(..)+, options+) where++import Imports++data Option = Option {+ optionName :: String+, optionShortName :: Maybe Char+, optionArgument :: Argument+, optionHelp :: String+} deriving (Eq, Show)++data Argument = Argument String | NoArgument | OptionalArgument String+ deriving (Eq, Show)++options :: [Option]+options = [+ Option "help" (Just 'h') NoArgument "Show this help text"+ , Option "verbose" (Just 'v') (OptionalArgument "n") "Control verbosity (n is 0--3, default verbosity level is 1)"+ , Option "builddir" Nothing (Argument "DIR") "The directory where Cabal puts generated build files (default dist)"+ , Option "keep-temp-files" Nothing NoArgument "Keep temporary files."+ , Option "ghc" (Just 'g') NoArgument "compile with GHC"+ , Option "ghcjs" Nothing NoArgument "compile with GHCJS"+ , Option "uhc" Nothing NoArgument "compile with UHC"+ , Option "with-compiler" (Just 'w') (Argument "PATH") "give the path to a particular compiler"+ , Option "with-hc-pkg" Nothing (Argument "PATH") "give the path to the package tool"+ , Option "prefix" Nothing (Argument "DIR") "bake this prefix in preparation of installation"+ , Option "bindir" Nothing (Argument "DIR") "installation directory for executables"+ , Option "libdir" Nothing (Argument "DIR") "installation directory for libraries"+ , Option "libsubdir" Nothing (Argument "DIR") "subdirectory of libdir in which libs are installed"+ , Option "dynlibdir" Nothing (Argument "DIR") "installation directory for dynamic libraries"+ , Option "libexecdir" Nothing (Argument "DIR") "installation directory for program executables"+ , Option "libexecsubdir" Nothing (Argument "DIR") "subdirectory of libexecdir in which private executables are installed"+ , Option "datadir" Nothing (Argument "DIR") "installation directory for read-only data"+ , Option "datasubdir" Nothing (Argument "DIR") "subdirectory of datadir in which data files are installed"+ , Option "docdir" Nothing (Argument "DIR") "installation directory for documentation"+ , Option "htmldir" Nothing (Argument "DIR") "installation directory for HTML documentation"+ , Option "haddockdir" Nothing (Argument "DIR") "installation directory for haddock interfaces"+ , Option "sysconfdir" Nothing (Argument "DIR") "installation directory for configuration files"+ , Option "program-prefix" Nothing (Argument "PREFIX") "prefix to be applied to installed executables"+ , Option "program-suffix" Nothing (Argument "SUFFIX") "suffix to be applied to installed executables"+ , Option "enable-library-vanilla" Nothing NoArgument "Enable Vanilla libraries"+ , Option "disable-library-vanilla" Nothing NoArgument "Disable Vanilla libraries"+ , Option "enable-library-profiling" (Just 'p') NoArgument "Enable Library profiling"+ , Option "disable-library-profiling" Nothing NoArgument "Disable Library profiling"+ , Option "enable-shared" Nothing NoArgument "Enable Shared library"+ , Option "disable-shared" Nothing NoArgument "Disable Shared library"+ , Option "enable-static" Nothing NoArgument "Enable Static library"+ , Option "disable-static" Nothing NoArgument "Disable Static library"+ , Option "enable-executable-dynamic" Nothing NoArgument "Enable Executable dynamic linking"+ , Option "disable-executable-dynamic" Nothing NoArgument "Disable Executable dynamic linking"+ , Option "enable-executable-static" Nothing NoArgument "Enable Executable fully static linking"+ , Option "disable-executable-static" Nothing NoArgument "Disable Executable fully static linking"+ , Option "enable-profiling" Nothing NoArgument "Enable Executable and library profiling"+ , Option "disable-profiling" Nothing NoArgument "Disable Executable and library profiling"+ , Option "enable-profiling-shared" Nothing NoArgument "Enable Build profiling shared libraries"+ , Option "disable-profiling-shared" Nothing NoArgument "Disable Build profiling shared libraries"+ , Option "enable-executable-profiling" Nothing NoArgument "Enable Executable profiling (DEPRECATED)"+ , Option "disable-executable-profiling" Nothing NoArgument "Disable Executable profiling (DEPRECATED)"+ , Option "profiling-detail" Nothing (Argument "level") "Profiling detail level for executable and library (default, none, exported-functions, toplevel-functions, all-functions, late)."+ , Option "library-profiling-detail" Nothing (Argument "level") "Profiling detail level for libraries only."+ , Option "enable-optimization" (Just 'O') (OptionalArgument "n") "Build with optimization (n is 0--2, default is 1)"+ , Option "disable-optimization" Nothing NoArgument "Build without optimization"+ , Option "enable-debug-info" Nothing (OptionalArgument "n") "Emit debug info (n is 0--3, default is 0)"+ , Option "disable-debug-info" Nothing NoArgument "Don't emit debug info"+ , Option "enable-build-info" Nothing NoArgument "Enable build information generation during project building"+ , Option "disable-build-info" Nothing NoArgument "Disable build information generation during project building"+ , Option "enable-library-for-ghci" Nothing NoArgument "Enable compile library for use with GHCi"+ , Option "disable-library-for-ghci" Nothing NoArgument "Disable compile library for use with GHCi"+ , Option "enable-split-sections" Nothing NoArgument "Enable compile library code such that unneeded definitions can be dropped from the final executable (GHC 7.8+)"+ , Option "disable-split-sections" Nothing NoArgument "Disable compile library code such that unneeded definitions can be dropped from the final executable (GHC 7.8+)"+ , Option "enable-split-objs" Nothing NoArgument "Enable split library into smaller objects to reduce binary sizes (GHC 6.6+)"+ , Option "disable-split-objs" Nothing NoArgument "Disable split library into smaller objects to reduce binary sizes (GHC 6.6+)"+ , Option "enable-executable-stripping" Nothing NoArgument "Enable strip executables upon installation to reduce binary sizes"+ , Option "disable-executable-stripping" Nothing NoArgument "Disable strip executables upon installation to reduce binary sizes"+ , Option "enable-library-stripping" Nothing NoArgument "Enable strip libraries upon installation to reduce binary sizes"+ , Option "disable-library-stripping" Nothing NoArgument "Disable strip libraries upon installation to reduce binary sizes"+ , Option "configure-option" Nothing (Argument "OPT") "Extra option for configure"+ , Option "user" Nothing NoArgument "Enable doing a per-user installation"+ , Option "global" Nothing NoArgument "Disable doing a per-user installation"+ , Option "package-db" Nothing (Argument "DB") "Append the given package database to the list of package databases used (to satisfy dependencies and register into). May be a specific file, 'global' or 'user'. The initial list is ['global'], ['global', 'user'], or ['global', $sandbox], depending on context. Use 'clear' to reset the list to empty. See the user guide for details."+ , Option "flags" (Just 'f') (Argument "FLAGS") "Force values for the given flags in Cabal conditionals in the .cabal file. E.g., --flags=\"debug -usebytestrings\" forces the flag \"debug\" to true and \"usebytestrings\" to false."+ , Option "extra-include-dirs" Nothing (Argument "PATH") "A list of directories to search for header files"+ , Option "enable-deterministic" Nothing NoArgument "Enable Try to be as deterministic as possible (used by the test suite)"+ , Option "disable-deterministic" Nothing NoArgument "Disable Try to be as deterministic as possible (used by the test suite)"+ , Option "ipid" Nothing (Argument "IPID") "Installed package ID to compile this package as"+ , Option "cid" Nothing (Argument "CID") "Installed component ID to compile this component as"+ , Option "extra-lib-dirs" Nothing (Argument "PATH") "A list of directories to search for external libraries"+ , Option "extra-lib-dirs-static" Nothing (Argument "PATH") "A list of directories to search for external libraries when linking fully static executables"+ , Option "extra-framework-dirs" Nothing (Argument "PATH") "A list of directories to search for external frameworks (OS X only)"+ , Option "extra-prog-path" Nothing (Argument "PATH") "A list of directories to search for required programs (in addition to the normal search locations)"+ , Option "instantiate-with" Nothing (Argument "NAME=MOD") "A mapping of signature names to concrete module instantiations."+ , Option "enable-tests" Nothing NoArgument "Enable dependency checking and compilation for test suites listed in the package description file."+ , Option "disable-tests" Nothing NoArgument "Disable dependency checking and compilation for test suites listed in the package description file."+ , Option "enable-coverage" Nothing NoArgument "Enable build package with Haskell Program Coverage. (GHC only)"+ , Option "disable-coverage" Nothing NoArgument "Disable build package with Haskell Program Coverage. (GHC only)"+ , Option "enable-library-coverage" Nothing NoArgument "Enable build package with Haskell Program Coverage. (GHC only) (DEPRECATED)"+ , Option "disable-library-coverage" Nothing NoArgument "Disable build package with Haskell Program Coverage. (GHC only) (DEPRECATED)"+ , Option "enable-benchmarks" Nothing NoArgument "Enable dependency checking and compilation for benchmarks listed in the package description file."+ , Option "disable-benchmarks" Nothing NoArgument "Disable dependency checking and compilation for benchmarks listed in the package description file."+ , Option "enable-relocatable" Nothing NoArgument "Enable building a package that is relocatable. (GHC only)"+ , Option "disable-relocatable" Nothing NoArgument "Disable building a package that is relocatable. (GHC only)"+ , Option "disable-response-files" Nothing NoArgument "enable workaround for old versions of programs like \"ar\" that do not support @file arguments"+ , Option "allow-depending-on-private-libs" Nothing NoArgument "Allow depending on private libraries. If set, the library visibility check MUST be done externally."+ , Option "coverage-for" Nothing (Argument "UNITID") "A list of unit-ids of libraries to include in the Haskell Program Coverage report."+ , Option "ignore-build-tools" Nothing NoArgument "Ignore build tool dependencies. If set, declared build tools needn't be found for compilation to proceed."+ , Option "cabal-lib-version" Nothing (Argument "VERSION") "Select which version of the Cabal lib to use to build packages (useful for testing)."+ , Option "enable-append" Nothing NoArgument "Enable appending the new config to the old config file"+ , Option "disable-append" Nothing NoArgument "Disable appending the new config to the old config file"+ , Option "enable-backup" Nothing NoArgument "Enable the backup of the config file before any alterations"+ , Option "disable-backup" Nothing NoArgument "Disable the backup of the config file before any alterations"+ , Option "constraint" (Just 'c') (Argument "CONSTRAINT") "Specify constraints on a package (version, installed/source, flags)"+ , Option "preference" Nothing (Argument "CONSTRAINT") "Specify preferences (soft constraints) on the version of a package"+ , Option "solver" Nothing (Argument "SOLVER") "Select dependency solver to use (default: modular). Choices: modular."+ , Option "allow-older" Nothing (OptionalArgument "DEPS") "Ignore lower bounds in all dependencies or DEPS"+ , Option "allow-newer" Nothing (OptionalArgument "DEPS") "Ignore upper bounds in all dependencies or DEPS"+ , Option "write-ghc-environment-files" Nothing (Argument "always|never|ghc8.4.4+") "Whether to create a .ghc.environment file after a successful build (v2-build only)"+ , Option "enable-documentation" Nothing NoArgument "Enable building of documentation"+ , Option "disable-documentation" Nothing NoArgument "Disable building of documentation"+ , Option "doc-index-file" Nothing (Argument "TEMPLATE") "A central index of haddock API documentation (template cannot use $pkgid)"+ , Option "dry-run" Nothing NoArgument "Do not install anything, only print what would be installed."+ , Option "only-download" Nothing NoArgument "Do not build anything, only fetch the packages."+ , Option "max-backjumps" Nothing (Argument "NUM") "Maximum number of backjumps allowed while solving (default: 4000). Use a negative number to enable unlimited backtracking. Use 0 to disable backtracking completely."+ , Option "reorder-goals" Nothing NoArgument "Try to reorder goals according to certain heuristics. Slows things down on average, but may make backtracking faster for some packages."+ , Option "count-conflicts" Nothing NoArgument "Try to speed up solving by preferring goals that are involved in a lot of conflicts (default)."+ , Option "fine-grained-conflicts" Nothing NoArgument "Skip a version of a package if it does not resolve the conflicts encountered in the last version, as a solver optimization (default)."+ , Option "minimize-conflict-set" Nothing NoArgument "When there is no solution, try to improve the error message by finding a minimal conflict set (default: false). May increase run time significantly."+ , Option "independent-goals" Nothing NoArgument "Treat several goals on the command line as independent. If several goals depend on the same package, different versions can be chosen."+ , Option "prefer-oldest" Nothing NoArgument "Prefer the oldest (instead of the latest) versions of packages available. Useful to determine lower bounds in the build-depends section."+ , Option "shadow-installed-packages" Nothing NoArgument "If multiple package instances of the same version are installed, treat all but one as shadowed."+ , Option "strong-flags" Nothing NoArgument "Do not defer flag choices (this used to be the default in cabal-install <= 1.20)."+ , Option "allow-boot-library-installs" Nothing NoArgument "Allow cabal to install base, ghc-prim, integer-simple, integer-gmp, and template-haskell."+ , Option "reject-unconstrained-dependencies" Nothing (Argument "none|all") "Require these packages to have constraints on them if they are to be selected (default: none)."+ , Option "reinstall" Nothing NoArgument "Install even if it means installing the same version again."+ , Option "avoid-reinstalls" Nothing NoArgument "Do not select versions that would destructively overwrite installed packages."+ , Option "force-reinstalls" Nothing NoArgument "Reinstall packages even if they will most likely break other installed packages."+ , Option "upgrade-dependencies" Nothing NoArgument "Pick the latest version for all dependencies, rather than trying to pick an installed version."+ , Option "only-dependencies" Nothing NoArgument "Install only the dependencies necessary to build the given packages"+ , Option "dependencies-only" Nothing NoArgument "A synonym for --only-dependencies"+ , Option "index-state" Nothing (Argument "STATE") "Use source package index state as it existed at a previous time. Accepts unix-timestamps (e.g. '@1474732068'), ISO8601 UTC timestamps (e.g. '2016-09-24T17:47:48Z'), or 'HEAD' (default: 'HEAD')."+ , Option "root-cmd" Nothing (Argument "COMMAND") "(No longer supported, do not use.)"+ , Option "build-summary" Nothing (Argument "TEMPLATE") "Save build summaries to file (name template can use $pkgid, $compiler, $os, $arch)"+ , Option "build-log" Nothing (Argument "TEMPLATE") "Log all builds to file (name template can use $pkgid, $compiler, $os, $arch)"+ , Option "remote-build-reporting" Nothing (Argument "LEVEL") "Generate build reports to send to a remote server (none, anonymous or detailed)."+ , Option "report-planning-failure" Nothing NoArgument "Generate build reports when the dependency solver fails. This is used by the Hackage build bot."+ , Option "enable-per-component" Nothing NoArgument "Enable Per-component builds when possible"+ , Option "disable-per-component" Nothing NoArgument "Disable Per-component builds when possible"+ , Option "run-tests" Nothing NoArgument "Run package test suites during installation."+ , Option "semaphore" Nothing NoArgument "Use a semaphore so GHC can compile components in parallel"+ , Option "jobs" (Just 'j') (OptionalArgument "NUM") "Run NUM jobs simultaneously (or '$ncpus' if no NUM is given)."+ , Option "keep-going" Nothing NoArgument "After a build failure, continue to build other unaffected packages."+ , Option "offline" Nothing NoArgument "Don't download packages from the Internet."+ , Option "haddock-hoogle" Nothing NoArgument "Generate a hoogle database"+ , Option "haddock-html" Nothing NoArgument "Generate HTML documentation (the default)"+ , Option "haddock-html-location" Nothing (Argument "URL") "Location of HTML documentation for pre-requisite packages"+ , Option "haddock-for-hackage" Nothing NoArgument "Collection of flags to generate documentation suitable for upload to hackage"+ , Option "haddock-executables" Nothing NoArgument "Run haddock for Executables targets"+ , Option "haddock-tests" Nothing NoArgument "Run haddock for Test Suite targets"+ , Option "haddock-benchmarks" Nothing NoArgument "Run haddock for Benchmark targets"+ , Option "haddock-all" Nothing NoArgument "Run haddock for all targets"+ , Option "haddock-internal" Nothing NoArgument "Run haddock for internal modules and include all symbols"+ , Option "haddock-css" Nothing (Argument "PATH") "Use PATH as the haddock stylesheet"+ , Option "haddock-hyperlink-source" Nothing NoArgument "Hyperlink the documentation to the source code"+ , Option "haddock-quickjump" Nothing NoArgument "Generate an index for interactive documentation navigation"+ , Option "haddock-hscolour-css" Nothing (Argument "PATH") "Use PATH as the HsColour stylesheet"+ , Option "haddock-contents-location" Nothing (Argument "URL") "Bake URL in as the location for the contents page"+ , Option "haddock-base-url" Nothing (Argument "URL") "Base URL for static files."+ , Option "haddock-resources-dir" Nothing (Argument "DIR") "location of Haddocks static / auxiliary files"+ , Option "haddock-output-dir" Nothing (Argument "DIR") "Generate haddock documentation into this directory. This flag is provided as a technology preview and is subject to change in the next releases."+ , Option "haddock-use-unicode" Nothing NoArgument "Pass --use-unicode option to haddock"+ , Option "test-log" Nothing (Argument "TEMPLATE") "Log all test suite results to file (name template can use $pkgid, $compiler, $os, $arch, $test-suite, $result)"+ , Option "test-machine-log" Nothing (Argument "TEMPLATE") "Produce a machine-readable log file (name template can use $pkgid, $compiler, $os, $arch, $result)"+ , Option "test-show-details" Nothing (Argument "FILTER") "'always': always show results of individual test cases. 'never': never show results of individual test cases. 'failures': show results of failing test cases. 'streaming': show results of test cases in real time.'direct': send results of test cases in real time; no log file."+ , Option "test-keep-tix-files" Nothing NoArgument "keep .tix files for HPC between test runs"+ , Option "test-wrapper" Nothing (Argument "FILE") "Run test through a wrapper."+ , Option "test-fail-when-no-test-suites" Nothing NoArgument "Exit with failure when no test suites are found."+ , Option "test-options" Nothing (Argument "TEMPLATES") "give extra options to test executables (split on spaces, use \"\" to prevent splitting; name templates can use $pkgid, $compiler, $os, $arch, $test-suite)"+ , Option "test-option" Nothing (Argument "TEMPLATE") "give extra option to test executables (passed directly as a single argument; name template can use $pkgid, $compiler, $os, $arch, $test-suite)"+ , Option "benchmark-options" Nothing (Argument "TEMPLATES") "give extra options to benchmark executables (split on spaces, use \"\" to prevent splitting; name templates can use $pkgid, $compiler, $os, $arch, $benchmark)"+ , Option "benchmark-option" Nothing (Argument "TEMPLATE") "give extra option to benchmark executables (passed directly as a single argument; name template can use $pkgid, $compiler, $os, $arch, $benchmark)"+ , Option "project-dir" Nothing (Argument "DIR") "Set the path of the project directory"+ , Option "project-file" Nothing (Argument "FILE") "Set the path of the cabal.project file (relative to the project directory when relative)"+ , Option "ignore-project" (Just 'z') NoArgument "Ignore local project configuration (unless --project-dir or --project-file is also set)"+ , Option "repl-no-load" Nothing NoArgument "Disable loading of project modules at REPL startup."+ , Option "repl-options" Nothing (Argument "FLAG") "Use the option(s) for the repl"+ , Option "repl-multi-file" Nothing (Argument "DIR") "Write repl options to this directory rather than starting repl mode"+ , Option "with-repl" Nothing (Argument "PATH") "Give the path to a program to use for REPL"+ , Option "build-depends" (Just 'b') (Argument "DEPENDENCIES") "Include additional packages in the environment presented to GHCi."+ , Option "no-transitive-deps" Nothing NoArgument "Don't automatically include transitive dependencies of requested packages."+ , Option "enable-multi-repl" Nothing NoArgument "Enable multi-component repl sessions"+ , Option "disable-multi-repl" Nothing NoArgument "Disable multi-component repl sessions"+ ]
src/Extract.hs view
@@ -1,36 +1,33 @@-{-# LANGUAGE CPP, DeriveDataTypeable, DeriveFunctor #-}+{-# LANGUAGE CPP #-} module Extract (Module(..), extract) where -import Prelude hiding (mod, concat)-import Control.Monad-import Control.Applicative-import Control.Exception+import Imports hiding (mod, concat) import Data.List (partition, isSuffixOf)-import Data.Maybe-#if __GLASGOW_HASKELL__ < 710-import Data.Foldable (concat)-#endif import Control.DeepSeq (deepseq, NFData(rnf)) import Data.Generics -#if __GLASGOW_HASKELL__ < 707-import GHC hiding (flags, Module, Located)-import MonadUtils (liftIO, MonadIO)-#else+#if __GLASGOW_HASKELL__ < 900 import GHC hiding (Module, Located) import DynFlags import MonadUtils (liftIO)+#else+import GHC hiding (Module, Located)+import GHC.Driver.Session+import GHC.Utils.Monad (liftIO) #endif++#if __GLASGOW_HASKELL__ < 900+import Digraph (flattenSCCs) import Exception (ExceptionMonad)+#else+import GHC.Data.Graph.Directed (flattenSCCs)+import GHC.Utils.Exception (ExceptionMonad)+import Control.Monad.Catch (generalBracket)+#endif+ import System.Directory import System.FilePath-#if __GLASGOW_HASKELL__ < 710-import NameSet (NameSet)-#endif-import Coercion (Coercion)-import FastString (unpackFS)-import Digraph (flattenSCCs) import System.Posix.Internals (c_getpid) @@ -38,11 +35,23 @@ import Location hiding (unLoc) import Util (convertDosLineEndings)-import Sandbox (getSandboxArguments)+import PackageDBs (getPackageDBArgs) +#if __GLASGOW_HASKELL__ < 900+import DynamicLoading (initializePlugins)+#else+import GHC.Runtime.Loader (initializePlugins)+#endif++#if __GLASGOW_HASKELL__ >= 901+import GHC.Unit.Module.Graph+#endif+ -- | A wrapper around `SomeException`, to allow for a custom `Show` instance. newtype ExtractError = ExtractError SomeException+#if __GLASGOW_HASKELL__ < 912 deriving Typeable+#endif instance Show ExtractError where show (ExtractError e) =@@ -67,54 +76,46 @@ moduleName :: String , moduleSetup :: Maybe a , moduleContent :: [a]-} deriving (Eq, Functor)+} deriving (Eq, Show, Functor) instance NFData a => NFData (Module a) where rnf (Module name setup content) = name `deepseq` setup `deepseq` content `deepseq` () -- | Parse a list of modules.-parse :: [String] -> IO [TypecheckedModule]+parse :: [String] -> IO [ParsedModule] parse args = withGhc args $ \modules_ -> withTempOutputDir $ do -- ignore additional object files let modules = filter (not . isSuffixOf ".o") modules_ - mapM (`guessTarget` Nothing) modules >>= setTargets+ setTargets =<< forM modules (\ m -> guessTarget m+#if __GLASGOW_HASKELL__ >= 903+ Nothing+#endif+ Nothing) mods <- depanal [] False - mods' <- if needsTemplateHaskell mods then enableCompilation mods else return mods-- let sortedMods = flattenSCCs (topSortModuleGraph False mods' Nothing)- reverse <$> mapM (parseModule >=> typecheckModule >=> loadModule) sortedMods- where- -- copied from Haddock/Interface.hs- enableCompilation :: ModuleGraph -> Ghc ModuleGraph- enableCompilation modGraph = do-#if __GLASGOW_HASKELL__ < 707- let enableComp d = d { hscTarget = defaultObjectTarget }-#else- let enableComp d = let platform = targetPlatform d- in d { hscTarget = defaultObjectTarget platform }+ let sortedMods =+#if __GLASGOW_HASKELL__ >= 914+ mapMaybe maybeModSummaryFromModuleNodeInfo $ #endif- modifySessionDynFlags enableComp- -- We need to update the DynFlags of the ModSummaries as well.- let upd m = m { ms_hspp_opts = enableComp (ms_hspp_opts m) }- let modGraph' = map upd modGraph- return modGraph'+ flattenSCCs+#if __GLASGOW_HASKELL__ >= 901+ $ filterToposortToModules+#endif+ $ topSortModuleGraph False mods Nothing+ reverse <$> mapM (loadModPlugins >=> parseModule) sortedMods+ where -- copied from Haddock/GhcUtils.hs modifySessionDynFlags :: (DynFlags -> DynFlags) -> Ghc () modifySessionDynFlags f = do dflags <- getSessionDynFlags-#if __GLASGOW_HASKELL__ < 707- _ <- setSessionDynFlags (f dflags)-#else -- GHCi 7.7 now uses dynamic linking. let dflags' = case lookup "GHC Dynamic" (compilerInfo dflags) of Just "YES" -> gopt_set dflags Opt_BuildDynamicToo _ -> dflags _ <- setSessionDynFlags (f dflags')-#endif return () withTempOutputDir :: Ghc a -> Ghc a@@ -131,25 +132,51 @@ -- | A variant of 'gbracket' where the return value from the first computation -- is not required. gbracket_ :: ExceptionMonad m => m a -> m b -> m c -> m c+#if __GLASGOW_HASKELL__ < 900 gbracket_ before_ after thing = gbracket before_ (const after) (const thing)+#else+ gbracket_ before_ after thing = fst <$> generalBracket before_ (\ _ _ -> after) (const thing)+#endif setOutputDir f d = d { objectDir = Just f , hiDir = Just f , stubDir = Just f- , includePaths = f : includePaths d+ , includePaths = addQuoteInclude (includePaths d) [f] } + -- Since GHC 8.6, plugins are initialized on a per module basis+ loadModPlugins modsum = do+ _ <- setSessionDynFlags (GHC.ms_hspp_opts modsum)+ hsc_env <- getSession++# if __GLASGOW_HASKELL__ >= 902+ hsc_env' <- liftIO (initializePlugins hsc_env)+ setSession hsc_env'+ return modsum+# else+ dynflags' <- liftIO (initializePlugins hsc_env (GHC.ms_hspp_opts modsum))+ return $ modsum { ms_hspp_opts = dynflags' }+# endif+ -- | Extract all docstrings from given list of files/modules. -- -- This includes the docstrings of all local modules that are imported from -- those modules (possibly indirect). extract :: [String] -> IO [Module (Located String)] extract args = do- sandboxArgs <- getSandboxArguments- let args' = args ++ sandboxArgs+ packageDBArgs <- getPackageDBArgs+ let+ args' = args +++#if __GLASGOW_HASKELL__ >= 810+ -- `ghci` ignores unused packages in certain situation. This ensures+ -- that we don't fail in situations where `ghci` would not.+ "-Wno-unused-packages" :+#endif+ packageDBArgs+ mods <- parse args'- let docs = map (fmap (fmap convertDosLineEndings) . extractFromModule . tm_parsed_module) mods+ let docs = map (fmap (fmap convertDosLineEndings) . extractFromModule) mods (docs `deepseq` return docs) `catches` [ -- Re-throw AsyncException, otherwise execution will not terminate on@@ -168,87 +195,94 @@ (setup, docs) = partition isSetup (docStringsFromModule m) name = (moduleNameString . GHC.moduleName . ms_mod . pm_mod_summary) m +#if __GLASGOW_HASKELL__ >= 914+maybeModSummaryFromModuleNodeInfo :: ModuleNodeInfo -> Maybe ModSummary+maybeModSummaryFromModuleNodeInfo i = case i of+ ModuleNodeCompile summary -> Just summary+ ModuleNodeFixed _ _ -> Nothing+#endif++#if __GLASGOW_HASKELL__ >= 904+unpackHDS :: HsDocString -> String+unpackHDS = renderHsDocString+#endif+ -- | Extract all docstrings from given module. docStringsFromModule :: ParsedModule -> [(Maybe String, Located String)]-docStringsFromModule mod = map (fmap (toLocated . fmap unpackDocString)) docs+docStringsFromModule mod = map (fmap (toLocated . fmap unpackHDS)) docs where source = (unLoc . pm_parsed_source) mod -- we use dlist-style concatenation here+ docs :: [(Maybe String, LHsDocString)] docs = header ++ exports ++ decls -- We process header, exports and declarations separately instead of -- traversing the whole source in a generic way, to ensure that we get -- everything in source order.+#if __GLASGOW_HASKELL__ >= 906+ header = [(Nothing, hsDocString <$> x) | Just x <- [hsmodHaddockModHeader (hsmodExt source)]]+#elif __GLASGOW_HASKELL__ >= 904+ header = [(Nothing, hsDocString <$> x) | Just x <- [hsmodHaddockModHeader (source)]]+#else header = [(Nothing, x) | Just x <- [hsmodHaddockModHeader source]]-#if __GLASGOW_HASKELL__ < 710- exports = [(Nothing, L loc doc) | L loc (IEDoc doc) <- concat (hsmodExports source)]+#endif+ exports :: [(Maybe String, LHsDocString)]+#if __GLASGOW_HASKELL__ >= 904+ exports = [ (Nothing, L (locA loc) (hsDocString (unLoc doc))) #else- exports = [(Nothing, L loc doc) | L loc (IEDoc doc) <- maybe [] unLoc (hsmodExports source)]+ exports = [ (Nothing, L (locA loc) doc) #endif+ | L loc (IEDoc _ doc) <- maybe [] unLoc (hsmodExports source)+ ]+ decls :: [(Maybe String, LHsDocString)] decls = extractDocStrings (hsmodDecls source) -type Selector a = a -> ([(Maybe String, LHsDocString)], Bool)---- | Ignore a subtree.-ignore :: Selector a-ignore = const ([], True)---- | Collect given value and descend into subtree.-select :: a -> ([a], Bool)-select x = ([x], False)- -- | Extract all docstrings from given value. extractDocStrings :: Data a => a -> [(Maybe String, LHsDocString)]-extractDocStrings = everythingBut (++) (([], False) `mkQ` fromLHsDecl- `extQ` fromLDocDecl- `extQ` fromLHsDocString-#if __GLASGOW_HASKELL__ < 710- `extQ` (ignore :: Selector NameSet)- `extQ` (ignore :: Selector PostTcKind)-#endif-- -- HsExpr never contains any documentation, but it may contain error thunks.- --- -- Problematic are (non comprehensive):- --- -- * parallel list comprehensions- -- * infix operators- --- `extQ` (ignore :: Selector (HsExpr RdrName))+extractDocStrings d =+#if __GLASGOW_HASKELL__ >= 904+ let+ docStrs = extractAll extractDocDocString d+ docStrNames = catMaybes $ extractAll extractDocName d+ in+ flip fmap docStrs $ \docStr -> (lookup (getLoc docStr) docStrNames, docStr)+ where+ extractAll z = everything (++) (mkQ [] ((:[]) . z)) - -- undefined before type checking- `extQ` (ignore :: Selector Coercion)+ extractDocDocString :: LHsDoc GhcPs -> LHsDocString+ extractDocDocString = fmap hsDocString -#if __GLASGOW_HASKELL__ >= 706-#if __GLASGOW_HASKELL__ < 710- -- hswb_kvs and hswb_tvs may be error thunks- `extQ` (ignore :: Selector (HsWithBndrs [LHsType RdrName]))- `extQ` (ignore :: Selector (HsWithBndrs [LHsType Name]))- `extQ` (ignore :: Selector (HsWithBndrs (LHsType RdrName)))- `extQ` (ignore :: Selector (HsWithBndrs (LHsType Name)))+ extractDocName :: DocDecl GhcPs -> Maybe (SrcSpan, String)+ extractDocName docDecl = case docDecl of+ DocCommentNamed name y ->+ Just (getLoc y, name)+ _ ->+ Nothing #else- -- hswb_kvs and hswb_tvs may be error thunks- `extQ` (ignore :: Selector (HsWithBndrs RdrName [LHsType RdrName]))- `extQ` (ignore :: Selector (HsWithBndrs Name [LHsType Name]))- `extQ` (ignore :: Selector (HsWithBndrs RdrName (LHsType RdrName)))- `extQ` (ignore :: Selector (HsWithBndrs Name (LHsType Name)))-#endif-#endif- )+ everythingBut (++) (([], False) `mkQ` fromLHsDecl+ `extQ` fromLDocDecl+ `extQ` fromLHsDocString+ ) d where- fromLHsDecl :: Selector (LHsDecl RdrName)+ fromLHsDecl :: Selector (LHsDecl GhcPs) fromLHsDecl (L loc decl) = case decl of -- Top-level documentation has to be treated separately, because it has -- no location information attached. The location information is -- attached to HsDecl instead.- DocD x -> select (fromDocDecl loc x)+ DocD _ x+ -> select (fromDocDecl (locA loc) x) _ -> (extractDocStrings decl, True) - fromLDocDecl :: Selector LDocDecl- fromLDocDecl (L loc x) = select (fromDocDecl loc x)+ fromLDocDecl :: Selector+#if __GLASGOW_HASKELL__ >= 901+ (LDocDecl GhcPs)+#else+ LDocDecl+#endif+ fromLDocDecl (L loc x) = select (fromDocDecl (locA loc) x) fromLHsDocString :: Selector LHsDocString fromLHsDocString x = select (Nothing, x)@@ -258,6 +292,14 @@ DocCommentNamed name doc -> (Just name, L loc doc) _ -> (Nothing, L loc $ docDeclDoc x) --- | Convert a docstring to a plain string.-unpackDocString :: HsDocString -> String-unpackDocString (HsDocString s) = unpackFS s+type Selector a = a -> ([(Maybe String, LHsDocString)], Bool)++-- | Collect given value and descend into subtree.+select :: a -> ([a], Bool)+select x = ([x], False)+#endif++#if __GLASGOW_HASKELL__ < 901+locA :: SrcSpan -> SrcSpan+locA = id+#endif
src/GhcUtil.hs view
@@ -1,70 +1,59 @@ {-# LANGUAGE CPP #-}-module GhcUtil (withGhc) where+{-# LANGUAGE LambdaCase #-}+module GhcUtil (withGhc, expandUnits) where +import Imports+ import GHC.Paths (libdir)-#if __GLASGOW_HASKELL__ < 707-import Control.Exception-import GHC hiding (flags)-import DynFlags (dopt_set)-#else import GHC+#if __GLASGOW_HASKELL__ < 900 import DynFlags (gopt_set)+#else+import GHC.Driver.Session (gopt_set) #endif-import Panic (throwGhcException) -import MonadUtils (liftIO)-import System.Exit (exitFailure)--#if __GLASGOW_HASKELL__ < 702-import StaticFlags (v_opt_C_ready)-import Data.IORef (writeIORef)-#elif __GLASGOW_HASKELL__ < 707-import StaticFlags (saveStaticFlagGlobals, restoreStaticFlagGlobals)+#if __GLASGOW_HASKELL__ < 900+import Panic (throwGhcException) #else-import StaticFlags (discardStaticFlags)+import GHC.Utils.Panic (throwGhcException) #endif ---- | Save static flag globals, run action, and restore them.-bracketStaticFlags :: IO a -> IO a-#if __GLASGOW_HASKELL__ < 702--- GHC < 7.2 does not provide saveStaticFlagGlobals/restoreStaticFlagGlobals,--- so we need to modifying v_opt_C_ready directly-bracketStaticFlags action = action `finally` writeIORef v_opt_C_ready False-#elif __GLASGOW_HASKELL__ < 707-bracketStaticFlags action = bracket saveStaticFlagGlobals restoreStaticFlagGlobals (const action)+#if __GLASGOW_HASKELL__ < 900+import MonadUtils (liftIO) #else-bracketStaticFlags action = action+import GHC.Utils.Monad (liftIO) #endif +import GHC.ResponseFile (expandResponse)+import System.Exit (exitFailure)+ -- Catch GHC source errors, print them and exit. handleSrcErrors :: Ghc a -> Ghc a handleSrcErrors action' = flip handleSourceError action' $ \err -> do-#if __GLASGOW_HASKELL__ < 702- printExceptionAndWarnings err-#else printException err-#endif liftIO exitFailure -- | Run a GHC action in Haddock mode withGhc :: [String] -> ([String] -> Ghc a) -> IO a-withGhc flags action = bracketStaticFlags $ do- flags_ <- handleStaticFlags flags+withGhc flags action = runGhc (Just libdir) $ do+ liftIO (expandUnits flags) >>= handleDynamicFlags >>= handleSrcErrors . action - runGhc (Just libdir) $ do- handleDynamicFlags flags_ >>= handleSrcErrors . action+expandUnits :: [String] -> IO [String]+expandUnits = \ case+ [] -> return []+ "-unit" : file@('@' : _) : args -> (++) <$> expandResponse [file] <*> expandUnits args+ file@('@' : _) : args -> (++) <$> expandResponse [file] <*> expandUnits args+ x : xs -> (:) x <$> expandUnits xs -handleStaticFlags :: [String] -> IO [Located String]-#if __GLASGOW_HASKELL__ < 707-handleStaticFlags flags = fst `fmap` parseStaticFlags (map noLoc flags)+handleDynamicFlags :: GhcMonad m => [String] -> m [String]+handleDynamicFlags flags = do+#if __GLASGOW_HASKELL__ >= 901+ logger <- getLogger+ let parseDynamicFlags' = parseDynamicFlags logger #else-handleStaticFlags flags = return $ map noLoc $ discardStaticFlags flags+ let parseDynamicFlags' = parseDynamicFlags #endif--handleDynamicFlags :: GhcMonad m => [Located String] -> m [String]-handleDynamicFlags flags = do- (dynflags, locSrcs, _) <- (setHaddockMode `fmap` getSessionDynFlags) >>= flip parseDynamicFlags flags+ (dynflags, locSrcs, _) <- (setHaddockMode `fmap` getSessionDynFlags) >>= (`parseDynamicFlags'` map noLoc flags) _ <- setSessionDynFlags dynflags -- We basically do the same thing as `ghc/Main.hs` to distinguish@@ -76,12 +65,14 @@ _ -> return srcs setHaddockMode :: DynFlags -> DynFlags-#if __GLASGOW_HASKELL__ < 707-setHaddockMode dynflags = (dopt_set dynflags Opt_Haddock) {-#else setHaddockMode dynflags = (gopt_set dynflags Opt_Haddock) {-#endif+#if __GLASGOW_HASKELL__ >= 906+ backend = noBackend+#elif __GLASGOW_HASKELL__ >= 901+ backend = NoBackend+#else hscTarget = HscNothing+#endif , ghcMode = CompManager , ghcLink = NoLink }
− src/Help.hs
@@ -1,27 +0,0 @@-module Help (- usage-, printVersion-) where--import Paths_doctest (version)-import Data.Version (showVersion)-import Config as GHC-import Interpreter (ghc)--usage :: String-usage = unlines [- "Usage:"- , " doctest [ GHC OPTION | MODULE ]..."- , " doctest --help"- , " doctest --version"- , ""- , "Options:"- , " --help display this help and exit"- , " --version output version information and exit"- ]--printVersion :: IO ()-printVersion = do- putStrLn ("doctest version " ++ showVersion version)- putStrLn ("using version " ++ GHC.cProjectVersion ++ " of the GHC API")- putStrLn ("using " ++ ghc)
+ src/Imports.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE LambdaCase #-}+module Imports (module Imports) where++import Prelude as Imports+import Data.Monoid as Imports+import Data.Maybe as Imports+import Control.Monad as Imports hiding (forM_)+import Control.Exception as Imports+import Data.Foldable as Imports (forM_)+import Control.Arrow as Imports++import Data.Char+import System.Exit+import System.Process++import Data.Functor as Imports ((<&>))++pass :: Monad m => m ()+pass = return ()++equals :: Eq a => a -> a -> Bool+equals = (==)++strip :: String -> String+strip = reverse . dropWhile isSpace . reverse . dropWhile isSpace++call :: FilePath -> [FilePath] -> IO ()+call name args = rawSystem name args >>= \ case+ ExitSuccess -> pass+ err -> exitWith err++exec :: FilePath -> [FilePath] -> IO ()+exec name args = rawSystem name args >>= exitWith
+ src/Info.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE CPP #-}+module Info (+ versionInfo+, info+, version+#ifdef TEST+, formatInfo+#endif+) where++import Imports++import Data.List+import System.Process+import System.IO.Unsafe++#if __GLASGOW_HASKELL__ < 900+import Config as GHC+#else+import GHC.Settings.Config as GHC+#endif++import Interpreter (ghc)++#ifdef TEST++version :: String+version = "0.0.0"++#else++import Data.Version (showVersion)+import qualified Paths_doctest++version :: String+version = showVersion Paths_doctest.version++#endif++ghcVersion :: String+ghcVersion = GHC.cProjectVersion++versionInfo :: String+versionInfo = unlines [+ "doctest version " ++ version+ , "using version " ++ ghcVersion ++ " of the GHC API"+ , "using " ++ ghc+ ]++info :: String+info = formatInfo $+ ("version", version)+ : ("ghc_version", ghcVersion)+ : ("ghc", ghc)+ : ghcInfo++type Info = [(String, String)]++ghcInfo :: Info+ghcInfo = read $ unsafePerformIO (readProcess ghc ["--info"] "")++formatInfo :: Info -> String+formatInfo xs = " [" ++ (intercalate "\n ," $ map show xs) ++ "\n ]\n"
src/Interpreter.hs view
@@ -1,7 +1,9 @@+{-# LANGUAGE CPP #-} module Interpreter ( Interpreter-, eval+, PreserveIt(..) , safeEval+, safeEvalWith , withInterpreter , ghc , interpreterSupported@@ -9,34 +11,16 @@ -- exported for testing , ghcInfo , haveInterpreterKey+, filterExpression ) where -import System.IO+import Imports+ import System.Process-import System.Exit import System.Directory (getPermissions, executable)-import Control.Monad (when, unless)-import Control.Applicative-import Control.Exception hiding (handle)-import Data.Char-import Data.List- import GHC.Paths (ghc)-import Sandbox (getSandboxArguments) --- | 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"--data Interpreter = Interpreter {- hIn :: Handle- , hOut :: Handle- , process :: ProcessHandle- }+import Language.Haskell.GhciWrapper haveInterpreterKey :: String haveInterpreterKey = "Have interpreter"@@ -52,147 +36,51 @@ unless (executable x) $ do fail $ ghc ++ " is not executable!" - maybe False (== "YES") . lookup haveInterpreterKey <$> ghcInfo--newInterpreter :: [String] -> IO Interpreter-newInterpreter flags = do- sandboxFlags <- getSandboxArguments- let myFlags = ghciFlags ++ flags ++ sandboxFlags- -- get examples from Haddock comments- (Just stdin_, Just stdout_, Nothing, processHandle ) <- createProcess $ (proc ghc myFlags) {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 System.IO"- _ <- eval interpreter "import GHC.IO.Handle"- -- The buffering of stdout and stderr is NoBuffering- _ <- eval interpreter "hDuplicateTo stdout 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 "hSetBuffering stdout LineBuffering"- _ <- eval interpreter "hSetBuffering stderr LineBuffering"-- -- this is required on systems that don't use utf8 as default encoding (e.g.- -- Windows)- _ <- eval interpreter "hSetEncoding stdout utf8"- _ <- eval interpreter "hSetEncoding stderr utf8"-- return interpreter- where- ghciFlags = ["-v0", "--interactive", "-ignore-dot-ghci"]- setMode handle = do- hSetBinaryMode handle False- hSetBuffering handle LineBuffering- hSetEncoding handle utf8-+ (== Just "YES") . lookup haveInterpreterKey <$> ghcInfo --- | Run an interpreter session.------ Example:------ >>> withInterpreter [] $ \i -> eval i "23 + 42"--- "65\n" withInterpreter- :: [String] -- ^ List of flags, passed to GHC+ :: (String, [String]) -> (Interpreter -> IO a) -- ^ Action to run -> IO a -- ^ Result of action-withInterpreter flags = bracket (newInterpreter flags) closeInterpreter---closeInterpreter :: Interpreter -> IO ()-closeInterpreter 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+withInterpreter (command, flags) action = do+ let+ args = flags ++ [+ xTemplateHaskell+ , "-fdiagnostics-color=never"+ , "-fno-diagnostics-show-caret"+#if __GLASGOW_HASKELL__ >= 810 && __GLASGOW_HASKELL__ < 904+ , "-Wno-unused-packages"+#endif+#if __GLASGOW_HASKELL__ >= 910+ , "-fprint-error-index-links=never"+#endif+ ]+ bracket (new defaultConfig{configGhci = command} args) close action - when (e /= ExitSuccess) $ error $ "Interpreter exited with an error: " ++ show e- return ()+xTemplateHaskell :: String+xTemplateHaskell = "-XTemplateHaskell" -putExpression :: Interpreter -> String -> IO ()-putExpression repl e = do- hPutStrLn stdin_ $ filterExpression e- hPutStrLn stdin_ marker- hFlush stdin_- return ()- where- stdin_ = hIn repl+-- | Evaluate an expression; return a Left value on exceptions.+--+-- An exception may e.g. be caused on unterminated multiline expressions.+safeEval :: Interpreter -> String -> IO (Either String String)+safeEval = safeEvalWith NoPreserveIt +safeEvalWith :: PreserveIt -> Interpreter -> String -> IO (Either String String)+safeEvalWith preserveIt repl = either (return . Left) (fmap Right . evalWith preserveIt repl) . filterExpression --- | Fail on unterminated multiline commands.------ Examples:------ >>> filterExpression ""--- ""------ >>> filterExpression "foobar"--- "foobar"------ >>> filterExpression ":{"--- "*** Exception: unterminated multiline command------ >>> filterExpression " :{ "--- "*** Exception: unterminated multiline command------ >>> filterExpression " :{ \nfoobar"--- "*** Exception: unterminated multiline command------ >>> filterExpression " :{ \nfoobar \n :} "--- " :{ \nfoobar \n :} "----filterExpression :: String -> String+filterExpression :: String -> Either String String filterExpression e = case lines e of- [] -> e- l -> if firstLine == ":{" && lastLine /= ":}" then fail_ else e+ [] -> Right e+ l -> if firstLine == ":{" && lastLine /= ":}" then err else Right (filterXTemplateHaskell e) where firstLine = strip $ head l lastLine = strip $ last l- fail_ = error "unterminated multiline command"- where- strip :: String -> String- strip = dropWhile isSpace . reverse . dropWhile isSpace . reverse---getResult :: Interpreter -> IO String-getResult repl = do- line <- hGetLine stdout_- if marker `isSuffixOf` line- then- return $ stripMarker line- else do- result <- getResult repl- return $ line ++ '\n' : result- where- stdout_ = hOut repl- stripMarker l = take (length l - length marker) l---- | Evaluate an expresion-eval- :: Interpreter- -> String -- Expression- -> IO String -- Result-eval repl expr = do- putExpression repl expr- getResult repl---- | Evaluate an expression; return a Left value on exceptions.------ An exception may e.g. be caused on unterminated multiline expressions.-safeEval :: Interpreter -> String -> IO (Either String String)-safeEval repl expression = (Right `fmap` Interpreter.eval repl expression) `catches` [- -- Re-throw AsyncException, otherwise execution will not terminate on- -- SIGINT (ctrl-c). All AsyncExceptions are re-thrown (not just- -- UserInterrupt) because all of them indicate severe conditions and- -- should not occur during normal test runs.- Handler $ \e -> throw (e :: AsyncException),+ err = Left "unterminated multi-line command" - Handler $ \e -> (return . Left . show) (e :: SomeException)- ]+filterXTemplateHaskell :: String -> String+filterXTemplateHaskell input = case words input of+ [":set", setting] | setting == xTemplateHaskell -> ""+ ":set" : xs | xTemplateHaskell `elem` xs -> unwords $ ":set" : filter (/= xTemplateHaskell) xs+ _ -> input
+ src/Language/Haskell/GhciWrapper.hs view
@@ -0,0 +1,160 @@+{-# LANGUAGE ViewPatterns #-}+module Language.Haskell.GhciWrapper (+ Interpreter+, Config(..)+, defaultConfig+, PreserveIt(..)+, new+, close+, eval+, evalWith+, evalEcho+) where++import Imports++import System.IO hiding (stdin, stdout, stderr)+import System.Process+import System.Exit+import Data.List (isSuffixOf)++data Config = Config {+ configGhci :: String+, configVerbose :: Bool+, configIgnoreDotGhci :: Bool+} deriving (Eq, Show)++defaultConfig :: Config+defaultConfig = Config {+ configGhci = "ghci"+, configVerbose = False+, configIgnoreDotGhci = True+}++data PreserveIt = NoPreserveIt | PreserveIt+ deriving Eq++-- | 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}+ evalThrow interpreter "import qualified System.IO"+ evalThrow interpreter "import qualified GHC.IO.Encoding"+ evalThrow interpreter "import qualified GHC.IO.Handle"+ -- The buffering of stdout and stderr is NoBuffering+ evalThrow 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.+ evalThrow interpreter "GHC.IO.Handle.hSetBuffering System.IO.stdout GHC.IO.Handle.LineBuffering"+ evalThrow 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)+ evalThrow interpreter "GHC.IO.Handle.hSetEncoding System.IO.stdout GHC.IO.Encoding.utf8"+ evalThrow interpreter "GHC.IO.Handle.hSetEncoding System.IO.stderr GHC.IO.Encoding.utf8"++ evalThrow interpreter ":m - System.IO"+ evalThrow interpreter ":m - GHC.IO.Encoding"+ evalThrow 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++ evalThrow :: Interpreter -> String -> IO ()+ evalThrow interpreter expr = do+ output <- eval interpreter expr+ unless (null output || configVerbose) $ do+ close interpreter+ throwIO (ErrorCall output)++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 -> PreserveIt -> String -> IO ()+putExpression Interpreter{hIn = stdin} (equals PreserveIt -> 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+ then do+ let xs = stripMarker line+ echo xs+ return xs+ else 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 = \ _ -> return ()++-- | Evaluate an expression+eval :: Interpreter -> String -> IO String+eval = evalWith NoPreserveIt++-- | Like 'eval', but try to preserve the @it@ variable+evalWith :: PreserveIt -> Interpreter -> String -> IO String+evalWith preserveIt repl expr = do+ putExpression repl preserveIt expr+ getResult False repl++-- | Evaluate an expression+evalEcho :: Interpreter -> String -> IO String+evalEcho repl expr = do+ putExpression repl NoPreserveIt expr+ getResult True repl
src/Location.hs view
@@ -1,13 +1,18 @@-{-# LANGUAGE CPP, DeriveFunctor #-}+{-# LANGUAGE CPP #-} module Location where +import Imports+ import Control.DeepSeq (deepseq, NFData(rnf))++#if __GLASGOW_HASKELL__ < 900 import SrcLoc hiding (Located) import qualified SrcLoc as GHC import FastString (unpackFS)--#if __GLASGOW_HASKELL__ < 702-import Outputable (showPpr)+#else+import GHC.Types.SrcLoc hiding (Located)+import qualified GHC.Types.SrcLoc as GHC+import GHC.Data.FastString (unpackFS) #endif -- | A thing with a location attached.@@ -54,14 +59,12 @@ -- | Convert a GHC source span to a location. toLocation :: SrcSpan -> Location-#if __GLASGOW_HASKELL__ < 702-toLocation loc- | isGoodSrcLoc start = Location (unpackFS $ srcLocFile start) (srcLocLine start)- | otherwise = (UnhelpfulLocation . showPpr) start- where- start = srcSpanStart loc-#else+#if __GLASGOW_HASKELL__ < 900 toLocation loc = case loc of UnhelpfulSpan str -> UnhelpfulLocation (unpackFS str) RealSrcSpan sp -> Location (unpackFS . srcSpanFile $ sp) (srcSpanStartLine sp)+#else+toLocation loc = case loc of+ UnhelpfulSpan str -> UnhelpfulLocation (unpackFS $ unhelpfulSpanFS str)+ RealSrcSpan sp _ -> Location (unpackFS . srcSpanFile $ sp) (srcSpanStartLine sp) #endif
+ src/Options.hs view
@@ -0,0 +1,168 @@+{-# LANGUAGE CPP #-}+module Options (+ Result(..)+, Run(..)+, Config(..)+, defaultConfig+, parseOptions+#ifdef TEST+, defaultRun+, usage+, info+, versionInfo+, nonInteractiveGhcOptions+#endif+) where++import Imports++import Control.Monad.Trans.RWS (RWS, execRWS)+import qualified Control.Monad.Trans.RWS as RWS++import Data.List (stripPrefix)++import GHC.Paths (ghc)++import Info++usage :: String+usage = unlines [+ "Usage:"+ , " doctest [ --fast | --preserve-it | --fail-fast | --no-magic | --verbose | GHC OPTION | MODULE ]..."+ , " doctest --help"+ , " doctest --version"+ , " doctest --info"+ , ""+ , "Options:"+ , " --fast disable :reload between example groups"+ , " --preserve-it preserve the `it` variable between examples"+ , " --fail-fast abort on first failure"+ , " --no-magic disable magic mode"+ , " --verbose print each test as it is run"+ , " --help display this help and exit"+ , " --version output version information and exit"+ , " --info output machine-readable version information and exit"+ ]++data Result a = ProxyToGhc [String] | Output String | Result a+ deriving (Eq, Show, Functor)++type Warning = String++data Run = Run {+ runWarnings :: [Warning]+, runMagicMode :: Bool+, runConfig :: Config+} deriving (Eq, Show)++data Config = Config {+ ghcOptions :: [String]+, fastMode :: Bool+, preserveIt :: Bool+, failFast :: Bool+, verbose :: Bool+, repl :: (String, [String])+} deriving (Eq, Show)++defaultConfig :: Config+defaultConfig = Config {+ ghcOptions = []+, fastMode = False+, preserveIt = False+, failFast = False+, verbose = False+, repl = (ghc, ["--interactive"])+}++nonInteractiveGhcOptions :: [String]+nonInteractiveGhcOptions = [+ "--numeric-version"+ , "--supported-languages"+ , "--info"+ , "--print-global-package-db"+ , "--print-libdir"+ , "-c"+ , "-o"+ , "--make"+ , "--abi-hash"+ ]++defaultRun :: Run+defaultRun = Run {+ runWarnings = []+, runMagicMode = False+, runConfig = defaultConfig+}++modifyWarnings :: ([String] -> [String]) -> Run -> Run+modifyWarnings f run = run { runWarnings = f (runWarnings run) }++setOptions :: [String] -> Run -> Run+setOptions ghcOptions run@Run{..} = run { runConfig = runConfig { ghcOptions } }++setMagicMode :: Bool -> Run -> Run+setMagicMode magic run = run { runMagicMode = magic }++setFastMode :: Bool -> Run -> Run+setFastMode fastMode run@Run{..} = run { runConfig = runConfig { fastMode } }++setPreserveIt :: Bool -> Run -> Run+setPreserveIt preserveIt run@Run{..} = run { runConfig = runConfig { preserveIt } }++setFailFastMode :: Bool -> Run -> Run+setFailFastMode failFast run@Run{..} = run { runConfig = runConfig { failFast } }++setVerbose :: Bool -> Run -> Run+setVerbose verbose run@Run{..} = run { runConfig = runConfig { verbose } }++parseOptions :: [String] -> Result Run+parseOptions args+ | on "--info" = Output info+ | on "--interactive" = runRunOptionsParser (discard "--interactive" args) defaultRun $ do+ commonRunOptions+ | on `any` nonInteractiveGhcOptions = ProxyToGhc args+ | on "--help" = Output usage+ | on "--version" = Output versionInfo+ | otherwise = runRunOptionsParser args defaultRun {runMagicMode = True} $ do+ commonRunOptions+ parseFlag "--no-magic" (setMagicMode False)+ parseOptGhc+ where+ on option = option `elem` args++type RunOptionsParser = RWS () (Endo Run) [String] ()++runRunOptionsParser :: [String] -> Run -> RunOptionsParser -> Result Run+runRunOptionsParser args def parse = case execRWS parse () args of+ (xs, Endo setter) ->+ Result (setOptions xs $ setter def)++commonRunOptions :: RunOptionsParser+commonRunOptions = do+ parseFlag "--fast" (setFastMode True)+ parseFlag "--preserve-it" (setPreserveIt True)+ parseFlag "--fail-fast" (setFailFastMode True)+ parseFlag "--verbose" (setVerbose True)++parseFlag :: String -> (Run -> Run) -> RunOptionsParser+parseFlag flag setter = do+ args <- RWS.get+ when (flag `elem` args) $+ RWS.tell (Endo setter)+ RWS.put (discard flag args)++parseOptGhc :: RunOptionsParser+parseOptGhc = do+ issueWarning <- RWS.state go+ when issueWarning $+ RWS.tell $ Endo $ modifyWarnings (++ [warning])+ where+ go args = case args of+ [] -> (False, [])+ "--optghc" : opt : rest -> (True, opt : snd (go rest))+ opt : rest -> maybe (fmap (opt :)) (\x (_, xs) -> (True, x : xs)) (stripPrefix "--optghc=" opt) (go rest)++ warning = "WARNING: --optghc is deprecated, doctest now accepts arbitrary GHC options\ndirectly."++discard :: String -> [String] -> [String]+discard flag = filter (/= flag)
+ src/PackageDBs.hs view
@@ -0,0 +1,64 @@+{-# LANGUAGE CPP #-}+-- | Manage GHC package databases+module PackageDBs (+ getPackageDBArgs+#ifdef TEST+, PackageDBs (..)+, getPackageDBsFromEnv+#endif+) where++import Imports++import System.Environment (getEnvironment)+import System.FilePath (splitSearchPath, searchPathSeparator)++-- | Full stack of GHC package databases+data PackageDBs = PackageDBs+ { includeUser :: Bool+ , includeGlobal :: Bool+ , extraDBs :: [FilePath]+ }+ deriving (Show, Eq)++-- | Determine command line arguments to be passed to GHC to set databases correctly+--+-- >>> dbArgs (PackageDBs False True [])+-- ["-no-user-package-db"]+--+-- >>> dbArgs (PackageDBs True True ["somedb"])+-- ["-package-db","somedb"]+dbArgs :: PackageDBs -> [String]+dbArgs (PackageDBs user global extras) =+ (if user then id else ("-no-user-package-db":)) $+ (if global then id else ("-no-global-package-db":)) $+ concatMap (\extra -> ["-package-db", extra]) extras++-- | Determine the PackageDBs based on the environment.+getPackageDBsFromEnv :: IO PackageDBs+getPackageDBsFromEnv = do+ env <- getEnvironment+ return $ case () of+ ()+ | Just packageDBs <- lookup "GHC_PACKAGE_PATH" env+ -> fromEnvMulti packageDBs+ | otherwise+ -> PackageDBs True True []+ where+ fromEnvMulti s = PackageDBs+ { includeUser = False+ , includeGlobal = global+ , extraDBs = splitSearchPath s'+ }+ where+ (s', global) =+ case reverse s of+ c:rest | c == searchPathSeparator -> (reverse rest, True)+ _ -> (s, False)++-- | Get the package DB flags for the current GHC version and from the+-- environment.+getPackageDBArgs :: IO [String]+getPackageDBArgs = do+ dbs <- getPackageDBsFromEnv+ return $ dbArgs dbs
src/Parse.hs view
@@ -1,27 +1,27 @@-{-# LANGUAGE PatternGuards #-}+{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} module Parse ( Module (..) , DocTest (..)-, Interaction , Expression , ExpectedResult , ExpectedLine (..) , LineChunk (..)-, getDocTests+, extractDocTests+, parseModules --- * exported for testing+#ifdef TEST , parseInteractions , parseProperties , mkLineChunks+#endif ) where +import Imports+ import Data.Char (isSpace)-import Data.List-import Data.Maybe+import Data.List (isPrefixOf, stripPrefix) import Data.String-import Control.Applicative- import Extract import Location @@ -46,13 +46,18 @@ type Interaction = (Expression, ExpectedResult) - -- | -- Extract 'DocTest's from all given modules and all modules included by the -- given modules.-getDocTests :: [String] -> IO [Module [Located DocTest]] -- ^ Extracted 'DocTest's-getDocTests args = do- filter (not . isEmpty) . map parseModule <$> extract args+--+-- @+-- extractDocTests = fmap `parseModules` . `extract`+-- @+extractDocTests :: [String] -> IO [Module [Located DocTest]] -- ^ Extracted 'DocTest's+extractDocTests = fmap parseModules . extract++parseModules :: [Module (Located String)] -> [Module [Located DocTest]]+parseModules = filter (not . isEmpty) . map parseModule where isEmpty (Module _ setup tests) = null tests && isNothing setup @@ -169,8 +174,3 @@ then (0, c : replicate count '.' ++ acc, res) else (0, c : acc, res) finish (count, acc, res) = mkChunk (replicate count '.' ++ acc) ++ res----- | Remove leading and trailing whitespace.-strip :: String -> String-strip = dropWhile isSpace . reverse . dropWhile isSpace . reverse
src/Property.hs view
@@ -8,7 +8,10 @@ #endif ) where +import Imports+ import Data.List+import Data.Foldable import Util import Interpreter (Interpreter)@@ -24,10 +27,9 @@ runProperty :: Interpreter -> Expression -> IO PropertyResult runProperty repl expression = do- _ <- Interpreter.eval repl "import Test.QuickCheck ((==>))"- _ <- Interpreter.eval repl "import Test.QuickCheck.All (polyQuickCheck)"- _ <- Interpreter.eval repl "import Language.Haskell.TH (mkName)"- _ <- Interpreter.eval repl ":set -XTemplateHaskell"+ _ <- Interpreter.safeEval repl "import Test.QuickCheck ((==>))"+ _ <- Interpreter.safeEval repl "import Test.QuickCheck.All (polyQuickCheck)"+ _ <- Interpreter.safeEval repl "import Language.Haskell.TH (mkName)" r <- freeVariables repl expression >>= (Interpreter.safeEval repl . quickCheck expression) case r of@@ -53,19 +55,17 @@ -- | Parse and return all variables that are not in scope from a ghc error -- message.------ >>> parseNotInScope "<interactive>:4:1: Not in scope: `foo'"--- ["foo"] parseNotInScope :: String -> [String]-parseNotInScope = nub . map extractVariable . filter ("Not in scope: " `isInfixOf`) . lines+parseNotInScope = nub . mapMaybe extractVariable . lines where -- | Extract variable name from a "Not in scope"-error.- extractVariable :: String -> String- extractVariable = unquote . takeWhileEnd (/= ' ')+ extractVariable :: String -> Maybe String+ extractVariable x+ | "Not in scope: " `isInfixOf` x = Just . unquote . takeWhileEnd (/= ' ') $ x+ | Just y <- (asum $ map (stripPrefix "Variable not in scope: ") (tails x)) = Just (takeWhile (/= ' ') y)+ | otherwise = Nothing -- | Remove quotes from given name, if any. unquote ('`':xs) = init xs-#if __GLASGOW_HASKELL__ >= 707 unquote ('\8216':xs) = init xs-#endif unquote xs = xs
src/Run.hs view
@@ -1,98 +1,171 @@ {-# LANGUAGE CPP #-}+{-# LANGUAGE LambdaCase #-} module Run ( doctest+, doctestWithRepl++, Config(..)+, defaultConfig+, doctestWith++, Result+, Summary(..)+, formatSummary+, isSuccess+, evaluateResult+, doctestWithResult++, runDocTests #ifdef TEST-, doctest_-, Summary-, stripOptGhc+, expandDirs #endif ) where -import Data.List-import Control.Monad (when, unless)+import Imports++import System.Directory (doesFileExist, doesDirectoryExist, getDirectoryContents)+import System.Environment (getEnvironment) import System.Exit (exitFailure, exitSuccess)+import System.FilePath ((</>), takeExtension) import System.IO-import System.Environment (getEnvironment)+import System.IO.CodePage (withCP65001) -import Control.Applicative import qualified Control.Exception as E++#if __GLASGOW_HASKELL__ < 900 import Panic+#else+import GHC.Utils.Panic+#endif +#if __GLASGOW_HASKELL__ < 904+import GhcUtil (expandUnits)+#endif++import PackageDBs import Parse-import Help+import Options hiding (Result(..))+import qualified Options import Runner+import Location import qualified Interpreter -ghcPackageDbFlag :: String-#if __GLASGOW_HASKELL__ >= 706-ghcPackageDbFlag = "-package-db"-#else-ghcPackageDbFlag = "-package-conf"-#endif- -- | Run doctest with given list of arguments. -- -- Example: -- -- >>> doctest ["-iexample/src", "example/src/Example.hs"]+-- ... -- Examples: 2 Tried: 2 Errors: 0 Failures: 0 -- -- This can be used to create a Cabal test suite that runs doctest for your -- project.+--+-- If a directory is given, it is traversed to find all .hs and .lhs files+-- inside of it, ignoring hidden entries. doctest :: [String] -> IO ()-doctest args- | "--help" `elem` args = putStr usage- | "--version" `elem` args = printVersion- | otherwise = do- -- Look up the HASKELL_PACKAGE_SANDBOX environment variable and, if- -- present, add it to the list of package databases GHC searches.- -- Intended to make testing from inside sandboxes such as cabal-dev- -- simpler.- packageConf <- lookup "HASKELL_PACKAGE_SANDBOX" <$> getEnvironment- let addPackageConf = case packageConf of- Nothing -> id- Just p -> \rest -> ghcPackageDbFlag : p : rest- - i <- Interpreter.interpreterSupported- unless i $ do- hPutStrLn stderr "WARNING: GHC does not support --interactive, skipping tests"- exitSuccess+doctest = doctestWithRepl (repl defaultConfig) - let (f, args_) = stripOptGhc args- when f $ do- hPutStrLn stderr "WARNING: --optghc is deprecated, doctest now accepts arbitrary GHC options\ndirectly."- hFlush stderr- r <- doctest_ (addPackageConf args_) `E.catch` \e -> do- case fromException e of- Just (UsageError err) -> do- hPutStrLn stderr ("doctest: " ++ err)- hPutStrLn stderr "Try `doctest --help' for more information."- exitFailure- _ -> E.throwIO e- when (not $ isSuccess r) exitFailure+doctestWithRepl :: (String, [String]) -> [String] -> IO ()+#if __GLASGOW_HASKELL__ < 904+-- GHC versions prior to 9.4.1 don't support response files. For that reason+-- we want to expand early, so that GHCi gets expanded args.+doctestWithRepl repl = expandUnits >=> parseOptions >>> \ case+#else+doctestWithRepl repl = parseOptions >>> \ case+#endif+ Options.ProxyToGhc args -> exec Interpreter.ghc args+ Options.Output s -> putStr s+ Options.Result (Run warnings magicMode config) -> do+ mapM_ (hPutStrLn stderr) warnings+ hFlush stderr -isSuccess :: Summary -> Bool-isSuccess s = sErrors s == 0 && sFailures s == 0+ i <- Interpreter.interpreterSupported+ unless i $ do+ hPutStrLn stderr "WARNING: GHC does not support --interactive, skipping tests"+ exitSuccess --- |--- Strip --optghc from GHC options. This is for backward compatibility with--- previous versions of doctest.------ A boolean is returned with the stripped arguments. It is True if striping--- occurred.-stripOptGhc :: [String] -> (Bool, [String])-stripOptGhc = go+ opts <- case magicMode of+ False -> return (ghcOptions config)+ True -> do+ expandedArgs <- concat <$> mapM expandDirs (ghcOptions config)+ packageDBArgs <- getPackageDBArgs+ addDistArgs <- getAddDistArgs+ return (addDistArgs $ packageDBArgs ++ expandedArgs)+ doctestWith config{repl, ghcOptions = opts}++-- | Expand a reference to a directory to all .hs and .lhs files within it.+expandDirs :: String -> IO [String]+expandDirs fp0 = do+ isDir <- doesDirectoryExist fp0+ if isDir+ then findHaskellFiles fp0+ else return [fp0] where- go args = case args of- [] -> (False, [])- "--optghc" : opt : rest -> (True, opt : snd (go rest))- opt : rest -> maybe (fmap (opt :)) (\x (_, xs) -> (True, x :xs)) (stripPrefix "--optghc=" opt) (go rest)+ findHaskellFiles dir = do+ contents <- getDirectoryContents dir+ concat <$> mapM go (filter (not . hidden) contents)+ where+ go name = do+ isDir <- doesDirectoryExist fp+ if isDir+ then findHaskellFiles fp+ else if isHaskellFile fp+ then return [fp]+ else return []+ where+ fp = dir </> name -doctest_ :: [String] -> IO Summary-doctest_ args = do+ hidden ('.':_) = True+ hidden _ = False - -- get examples from Haddock comments- modules <- getDocTests args+ isHaskellFile fp = takeExtension fp `elem` [".hs", ".lhs"] - Interpreter.withInterpreter args $ \repl -> do- runModules repl modules+-- | Get the necessary arguments to add the @cabal_macros.h@ file and autogen+-- directory, if present.+getAddDistArgs :: IO ([String] -> [String])+getAddDistArgs = do+ env <- getEnvironment+ let dist = fromMaybe "dist" $ lookup "HASKELL_DIST_DIR" env+ autogen = dist ++ "/build/autogen/"+ cabalMacros = autogen ++ "cabal_macros.h"++ dirExists <- doesDirectoryExist autogen+ if dirExists+ then do+ fileExists <- doesFileExist cabalMacros+ return $ \rest ->+ concat ["-i", dist, "/build/autogen/"]+ : "-optP-include"+ : (if fileExists+ then (concat ["-optP", dist, "/build/autogen/cabal_macros.h"]:)+ else id) rest+ else return id++doctestWith :: Config -> IO ()+doctestWith = doctestWithResult >=> evaluateResult++type Result = Summary++evaluateResult :: Result -> IO ()+evaluateResult r = unless (isSuccess r) exitFailure++doctestWithResult :: Config -> IO Result+doctestWithResult config = do+ (extractDocTests (ghcOptions config) >>= runDocTests config) `E.catch` \e -> do+ case fromException e of+ Just (UsageError err) -> do+ hPutStrLn stderr ("doctest: " ++ err)+ hPutStrLn stderr "Try `doctest --help' for more information."+ exitFailure+ _ -> E.throwIO e++runDocTests :: Config -> [Module [Located DocTest]] -> IO Result+runDocTests Config{..} modules = do+ Interpreter.withInterpreter ((<> ghcOptions) <$> repl) $ \ interpreter -> withCP65001 $ do+ runModules+ (if fastMode then FastMode else NoFastMode)+ (if preserveIt then PreserveIt else NoPreserveIt)+ (if failFast then FailFast else NoFailFast)+ (if verbose then Verbose else NonVerbose)+ interpreter modules
src/Runner.hs view
@@ -1,32 +1,39 @@ {-# LANGUAGE CPP #-}+{-# LANGUAGE LambdaCase #-} module Runner ( runModules+, FastMode(..)+, PreserveIt(..)+, FailFast(..)+, Verbose(..) , Summary(..)+, isSuccess+, formatSummary #ifdef TEST , Report-, ReportState (..)+, ReportState(..)+, runReport+, Interactive(..) , report-, report_+, reportTransient #endif ) where -import Prelude hiding (putStr, putStrLn, error)--#if __GLASGOW_HASKELL__ < 710-import Data.Monoid-#endif+import Prelude ()+import Imports hiding (putStr, putStrLn, error) -import Control.Applicative-import Control.Monad hiding (forM_) import Text.Printf (printf)-import System.IO (hPutStrLn, hPutStr, stderr, hIsTerminalDevice)-import Data.Foldable (forM_)+import System.IO hiding (putStr, putStrLn) -import Control.Monad.Trans.State+import Control.Monad.Trans.Class+import Control.Monad.Trans.Maybe+import Control.Monad.Trans.State (StateT, evalStateT)+import qualified Control.Monad.Trans.State as State import Control.Monad.IO.Class+import Data.IORef -import Interpreter (Interpreter)+import Interpreter (Interpreter, PreserveIt(..), safeEvalWith) import qualified Interpreter import Parse import Location@@ -35,173 +42,236 @@ -- | Summary of a test run. data Summary = Summary {- sExamples :: Int-, sTried :: Int-, sErrors :: Int-, sFailures :: Int+ sExamples :: !Int+, sTried :: !Int+, sErrors :: !Int+, sFailures :: !Int } deriving Eq --- | Format a summary. instance Show Summary where- show (Summary examples tried errors failures) =- printf "Examples: %d Tried: %d Errors: %d Failures: %d" examples tried errors failures+ show = formatSummary +isSuccess :: Summary -> Bool+isSuccess s = sErrors s == 0 && sFailures s == 0++formatSummary :: Summary -> String+formatSummary (Summary examples tried errors failures) =+ printf "Examples: %d Tried: %d Errors: %d Failures: %d" examples tried errors failures+ -- | Sum up summaries. instance Monoid Summary where mempty = Summary 0 0 0 0- (Summary x1 x2 x3 x4) `mappend` (Summary y1 y2 y3 y4) = Summary (x1 + y1) (x2 + y2) (x3 + y3) (x4 + y4)+instance Semigroup Summary where+ Summary x1 x2 x3 x4 <> Summary y1 y2 y3 y4 = Summary (x1 + y1) (x2 + y2) (x3 + y3) (x4 + y4) +withLineBuffering :: Handle -> IO c -> IO c+withLineBuffering h action = bracket (hGetBuffering h) (hSetBuffering h) $ \ _ -> do+ hSetBuffering h LineBuffering+ action+ -- | Run all examples from a list of modules.-runModules :: Interpreter -> [Module [Located DocTest]] -> IO Summary-runModules repl modules = do- isInteractive <- hIsTerminalDevice stderr- ReportState _ _ s <- (`execStateT` ReportState 0 isInteractive mempty {sExamples = c}) $ do- forM_ modules $ runModule repl+runModules :: FastMode -> PreserveIt -> FailFast -> Verbose -> Interpreter -> [Module [Located DocTest]] -> IO Summary+runModules fastMode preserveIt failFast verbose repl modules = withLineBuffering stderr $ do - -- report final summary- gets (show . reportStateSummary) >>= report+ interactive <- hIsTerminalDevice stderr <&> \ case+ False -> NonInteractive+ True -> Interactive - return s+ summary <- newIORef mempty {sExamples = n}++ let+ reportFinalResult :: IO ()+ reportFinalResult = do+ final <- readIORef summary+ hPutStrLn stderr (formatSummary final)++ run :: IO ()+ run = runReport (ReportState interactive failFast verbose summary) $ do+ reportProgress+ forM_ modules $ runModule fastMode preserveIt repl+ verboseReport "# Final summary:"++ run `finally` reportFinalResult++ readIORef summary where- c = (sum . map count) modules+ n :: Int+ n = sum (map countExpressions modules) --- | Count number of expressions in given module.-count :: Module [Located DocTest] -> Int-count (Module _ setup tests) = sum (map length tests) + maybe 0 length setup+countExpressions :: Module [Located DocTest] -> Int+countExpressions (Module _ setup tests) = sum (map length tests) + maybe 0 length setup --- | A monad for generating test reports.-type Report = StateT ReportState IO+type Report = MaybeT (StateT ReportState IO) +data Interactive = NonInteractive | Interactive++data FastMode = NoFastMode | FastMode++data FailFast = NoFailFast | FailFast++data Verbose = NonVerbose | Verbose+ data ReportState = ReportState {- reportStateCount :: Int -- ^ characters on the current line-, reportStateInteractive :: Bool -- ^ should intermediate results be printed?-, reportStateSummary :: Summary -- ^ test summary+ reportStateInteractive :: Interactive+, reportStateFailFast :: FailFast+, reportStateVerbose :: Verbose+, reportStateSummary :: IORef Summary } +runReport :: ReportState -> Report () -> IO ()+runReport st = void . flip evalStateT st . runMaybeT++getSummary :: Report Summary+getSummary = gets reportStateSummary >>= liftIO . readIORef++gets :: (ReportState -> a) -> Report a+gets = lift . State.gets+ -- | Add output to the report. report :: String -> Report ()-report msg = do- overwrite msg-- -- add a newline, this makes the output permanent- liftIO $ hPutStrLn stderr ""- modify (\st -> st {reportStateCount = 0})+report = liftIO . hPutStrLn stderr -- | 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})---- | Add output to the report, overwrite any intermediate out.-overwrite :: String -> Report ()-overwrite msg = do- n <- gets reportStateCount- let str | 0 < n = "\r" ++ msg ++ replicate (n - length msg) ' '- | otherwise = msg- liftIO (hPutStr stderr str)+reportTransient :: String -> Report ()+reportTransient msg = gets reportStateInteractive >>= \ case+ NonInteractive -> pass+ Interactive -> liftIO $ do+ hPutStr stderr msg+ hFlush stderr+ hPutStr stderr $ '\r' : (replicate (length msg) ' ') ++ "\r" -- | Run all examples from given module.-runModule :: Interpreter -> Module [Located DocTest] -> Report ()-runModule repl (Module module_ setup examples) = do+runModule :: FastMode -> PreserveIt -> Interpreter -> Module [Located DocTest] -> Report ()+runModule fastMode preserveIt repl (Module module_ setup examples) = do - Summary _ _ e0 f0 <- gets reportStateSummary+ Summary _ _ e0 f0 <- getSummary forM_ setup $- runTestGroup repl reload+ runTestGroup preserveIt repl reload - Summary _ _ e1 f1 <- gets reportStateSummary+ Summary _ _ e1 f1 <- getSummary -- only run tests, if setup does not produce any errors/failures when (e0 == e1 && f0 == f1) $- forM_ examples $- runTestGroup repl setup_+ forM_ examples $ runTestGroup preserveIt repl setup_ where reload :: IO () reload = do- -- NOTE: It is important to do the :reload first! There was some odd bug- -- with a previous version of GHC (7.4.1?).- void $ Interpreter.eval repl ":reload"- void $ Interpreter.eval repl $ ":m *" ++ module_+ case fastMode of+ NoFastMode -> void $ Interpreter.safeEval repl ":reload"+ FastMode -> pass+ void $ Interpreter.safeEval repl $ ":m *" ++ module_ + case preserveIt of+ NoPreserveIt -> pass+ PreserveIt -> do+ -- Evaluate a dumb expression to populate the 'it' variable.+ --+ -- NOTE: This is one reason why we cannot just always use PreserveIt:+ -- 'it' isn't set in a fresh GHCi session.+ void $ Interpreter.safeEval repl $ "()"+ setup_ :: IO () setup_ = do reload forM_ setup $ \l -> forM_ l $ \(Located _ x) -> case x of Property _ -> return ()- Example e _ -> void $ Interpreter.eval repl e+ Example e _ -> void $ safeEvalWith preserveIt repl e -reportFailure :: Location -> Expression -> Report ()-reportFailure loc expression = do- report (printf "### Failure in %s: expression `%s'" (show loc) expression)+reportStart :: Location -> Expression -> String -> Report ()+reportStart loc expression testType = do+ verboseReport (printf "### Started execution at %s.\n### %s:\n%s" (show loc) testType expression)++reportFailure :: Location -> Expression -> [String] -> Report ()+reportFailure loc expression err = do+ report (printf "%s: failure in expression `%s'" (show loc) expression)+ mapM_ report err+ report "" updateSummary (Summary 0 1 0 1) reportError :: Location -> Expression -> String -> Report () reportError loc expression err = do- report (printf "### Error in %s: expression `%s'" (show loc) expression)+ report (printf "%s: error in expression `%s'" (show loc) expression) report err+ report "" updateSummary (Summary 0 1 1 0) reportSuccess :: Report ()-reportSuccess =+reportSuccess = do+ verboseReport "### Successful!\n" updateSummary (Summary 0 1 0 0) +verboseReport :: String -> Report ()+verboseReport msg = gets reportStateVerbose >>= \ case+ NonVerbose -> pass+ Verbose -> report msg+ updateSummary :: Summary -> Report () updateSummary summary = do- ReportState n f s <- get- put (ReportState n f $ s `mappend` summary)+ ref <- gets reportStateSummary+ liftIO $ modifyIORef' ref $ mappend summary+ reportProgress+ gets reportStateFailFast >>= \ case+ NoFailFast -> pass+ FailFast -> unless (isSuccess summary) abort +abort :: Report ()+abort = MaybeT $ return Nothing++reportProgress :: Report ()+reportProgress = gets reportStateVerbose >>= \ case+ NonVerbose -> do+ summary <- getSummary+ reportTransient (formatSummary summary)+ Verbose -> pass+ -- | Run given test group. -- -- The interpreter state is zeroed with @:reload@ first. This means that you -- can reuse the same 'Interpreter' for several test groups.-runTestGroup :: Interpreter -> IO () -> [Located DocTest] -> Report ()-runTestGroup repl setup tests = do-- -- report intermediate summary- gets (show . reportStateSummary) >>= report_-+runTestGroup :: PreserveIt -> Interpreter -> IO () -> [Located DocTest] -> Report ()+runTestGroup preserveIt repl setup tests = do liftIO setup- runExampleGroup repl examples+ runExampleGroup preserveIt repl examples forM_ properties $ \(loc, expression) -> do- r <- liftIO $ do- setup- runProperty repl expression+ r <- do+ liftIO setup+ reportStart loc expression "property"+ liftIO $ runProperty repl expression case r of Success -> reportSuccess Error err -> do reportError loc expression err Failure msg -> do- reportFailure loc expression- report msg+ reportFailure loc expression [msg] where properties = [(loc, p) | Located loc (Property p) <- tests] examples :: [Located Interaction] examples = [Located loc (e, r) | Located loc (Example e r) <- tests] +type Interaction = (Expression, ExpectedResult)+ -- | -- Execute all expressions from given example in given 'Interpreter' and verify -- the output.-runExampleGroup :: Interpreter -> [Located Interaction] -> Report ()-runExampleGroup repl = go+runExampleGroup :: PreserveIt -> Interpreter -> [Located Interaction] -> Report ()+runExampleGroup preserveIt repl = go where go ((Located loc (expression, expected)) : xs) = do- r <- fmap lines <$> liftIO (Interpreter.safeEval repl expression)+ reportStart loc expression "example"+ r <- fmap lines <$> liftIO (safeEvalWith preserveIt repl expression) case r of Left err -> do reportError loc expression err Right actual -> case mkResult expected actual of NotEqual err -> do- reportFailure loc expression- mapM_ report err+ reportFailure loc expression err Equal -> do reportSuccess go xs
src/Runner/Example.hs view
@@ -3,63 +3,151 @@ , mkResult ) where +import Imports+ import Data.Char-import Data.List+import Data.List (isPrefixOf) import Util import Parse +maxBy :: (Ord a) => (b -> a) -> b -> b -> b+maxBy f x y = case compare (f x) (f y) of+ LT -> y+ EQ -> x+ GT -> x+ data Result = Equal | NotEqual [String] deriving (Eq, Show) mkResult :: ExpectedResult -> [String] -> Result-mkResult expected actual- | expected `matches` actual = Equal- | otherwise = NotEqual (formatNotEqual expected actual)+mkResult expected_ actual_ =+ case expected `matches` actual of+ Full -> Equal+ Partial partial -> NotEqual (formatNotEqual expected actual partial) where- chunksMatch :: [LineChunk] -> String -> Bool- chunksMatch [] "" = True- chunksMatch [LineChunk xs] ys = stripEnd xs == stripEnd ys+ -- use show to escape special characters in output lines if any output line+ -- contains any unsafe character+ escapeOutput+ | any (not . isSafe) $ concat (expectedAsString ++ actual_) = init . tail . show . stripEnd+ | otherwise = id++ actual :: [String]+ actual = fmap escapeOutput actual_++ expected :: ExpectedResult+ expected = fmap (transformExcpectedLine escapeOutput) expected_++ expectedAsString :: [String]+ expectedAsString = map (\x -> case x of+ ExpectedLine str -> concatMap lineChunkToString str+ WildCardLine -> "..." ) expected_++ isSafe :: Char -> Bool+ isSafe c = c == ' ' || (isPrint c && (not . isSpace) c)++ chunksMatch :: [LineChunk] -> String -> Match ChunksDivergence+ chunksMatch [] "" = Full+ chunksMatch [LineChunk xs] ys =+ if stripEnd xs == stripEnd ys+ then Full+ else Partial $ matchingPrefix xs ys chunksMatch (LineChunk x : xs) ys =- x `isPrefixOf` ys && xs `chunksMatch` drop (length x) ys+ if x `isPrefixOf` ys+ then fmap (prependText x) $ (xs `chunksMatch` drop (length x) ys)+ else Partial $ matchingPrefix x ys chunksMatch zs@(WildCardChunk : xs) (_:ys) =- xs `chunksMatch` ys || zs `chunksMatch` ys- chunksMatch _ _ = False+ -- Prefer longer matches.+ fmap prependWildcard $ maxBy+ (fmap $ length . matchText)+ (chunksMatch xs ys)+ (chunksMatch zs ys)+ chunksMatch [WildCardChunk] [] = Full+ chunksMatch (WildCardChunk:_) [] = Partial (ChunksDivergence "" "")+ chunksMatch [] (_:_) = Partial (ChunksDivergence "" "") - matches :: ExpectedResult -> [String] -> Bool- matches [] [] = True- matches [] _ = False- matches _ [] = False- matches (ExpectedLine x : xs) (y:ys) =- x `chunksMatch` y && xs `matches` ys- matches zs@(WildCardLine : xs) (_:ys) =- xs `matches` ys || zs `matches` ys+ matchingPrefix xs ys =+ let common = fmap fst (takeWhile (\(x, y) -> x == y) (xs `zip` ys)) in+ ChunksDivergence common common + matches :: ExpectedResult -> [String] -> Match LinesDivergence+ matches (ExpectedLine x : xs) (y : ys) =+ case x `chunksMatch` y of+ Full -> fmap incLineNo $ xs `matches` ys+ Partial partial -> Partial (LinesDivergence 1 (expandedWildcards partial))+ matches zs@(WildCardLine : xs) us@(_ : ys) =+ -- Prefer longer matches, and later ones of equal length.+ let matchWithoutWC = xs `matches` us in+ let matchWithWC = fmap incLineNo (zs `matches` ys) in+ let key (LinesDivergence lineNo line) = (length line, lineNo) in+ maxBy (fmap key) matchWithoutWC matchWithWC+ matches [WildCardLine] [] = Full+ matches [] [] = Full+ matches [] _ = Partial (LinesDivergence 1 "")+ matches _ [] = Partial (LinesDivergence 1 "") -formatNotEqual :: ExpectedResult -> [String] -> [String]-formatNotEqual expected_ actual = formatLines "expected: " expected ++ formatLines " but got: " actual+-- Note: order of constructors matters, so that full matches sort as+-- greater than partial.+data Match a = Partial a | Full+ deriving (Eq, Ord, Show)++instance Functor Match where+ fmap f (Partial a) = Partial (f a)+ fmap _ Full = Full++data ChunksDivergence = ChunksDivergence { matchText :: String, expandedWildcards :: String }+ deriving (Show)++prependText :: String -> ChunksDivergence -> ChunksDivergence+prependText s (ChunksDivergence mt wct) = ChunksDivergence (s++mt) (s++wct)++prependWildcard :: ChunksDivergence -> ChunksDivergence+prependWildcard (ChunksDivergence mt wct) = ChunksDivergence mt ('.':wct)++data LinesDivergence = LinesDivergence { _mismatchLineNo :: Int, _partialLine :: String }+ deriving (Show)++incLineNo :: LinesDivergence -> LinesDivergence+incLineNo (LinesDivergence lineNo partialLineMatch) = LinesDivergence (lineNo + 1) partialLineMatch++formatNotEqual :: ExpectedResult -> [String] -> LinesDivergence -> [String]+formatNotEqual expected_ actual partial = formatLines "expected: " expected ++ formatLines " but got: " (lineMarker wildcard partial actual) where expected :: [String] expected = map (\x -> case x of ExpectedLine str -> concatMap lineChunkToString str WildCardLine -> "..." ) expected_ - -- use show to escape special characters in output lines if any output line- -- contains any unsafe character- escapeOutput- | any (not . isSafe) (concat $ expected ++ actual) = map show- | otherwise = id-- isSafe :: Char -> Bool- isSafe c = c == ' ' || (isPrint c && (not . isSpace) c)- formatLines :: String -> [String] -> [String]- formatLines message xs = case escapeOutput xs of+ formatLines message xs = case xs of y:ys -> (message ++ y) : map (padding ++) ys [] -> [message] where padding = replicate (length message) ' ' + wildcard :: Bool+ wildcard = any (\x -> case x of+ ExpectedLine xs -> any (\y -> case y of { WildCardChunk -> True; _ -> False }) xs+ WildCardLine -> True ) expected_+ lineChunkToString :: LineChunk -> String lineChunkToString WildCardChunk = "..." lineChunkToString (LineChunk str) = str++transformExcpectedLine :: (String -> String) -> ExpectedLine -> ExpectedLine+transformExcpectedLine f (ExpectedLine xs) =+ ExpectedLine $ fmap (\el -> case el of+ LineChunk s -> LineChunk $ f s+ WildCardChunk -> WildCardChunk+ ) xs+transformExcpectedLine _ WildCardLine = WildCardLine++lineMarker :: Bool -> LinesDivergence -> [String] -> [String]+lineMarker wildcard (LinesDivergence row expanded) actual =+ let (pre, post) = splitAt row actual in+ pre +++ [(if wildcard && length expanded > 30+ -- show expanded pattern if match is long, to help understanding what matched what+ then expanded+ else replicate (length expanded) ' ') ++ "^"] +++ post
− src/Sandbox.hs
@@ -1,81 +0,0 @@-{-# LANGUAGE BangPatterns #-}--module Sandbox (getSandboxArguments, getPackageDbDir) where--import Control.Applicative ((<$>))-import Control.Exception as E (catch, SomeException, throwIO)-import Data.Char (isSpace)-import Data.List (isPrefixOf, tails)-import System.Directory (getCurrentDirectory, doesFileExist)-import System.FilePath ((</>), takeDirectory, takeFileName)--configFile :: String-configFile = "cabal.sandbox.config"--pkgDbKey :: String-pkgDbKey = "package-db:"--pkgDbKeyLen :: Int-pkgDbKeyLen = length pkgDbKey--getSandboxArguments :: IO [String]-getSandboxArguments = (sandboxArguments <$> getPkgDb) `E.catch` handler- where- getPkgDb = getCurrentDirectory >>= getSandboxConfigFile >>= getPackageDbDir- handler :: SomeException -> IO [String]- handler _ = return []---- | Find a sandbox config file by tracing ancestor directories.--- Exception is thrown if not found-getSandboxConfigFile :: FilePath -> IO FilePath-getSandboxConfigFile dir = do- let cfile = dir </> configFile- exist <- doesFileExist cfile- if exist then- return cfile- else do- let dir' = takeDirectory dir- if dir == dir' then- throwIO $ userError "sandbox config file not found"- else- getSandboxConfigFile dir'---- | Extract a package db directory from the sandbox config file.--- Exception is thrown if the sandbox config file is broken.-getPackageDbDir :: FilePath -> IO FilePath-getPackageDbDir sconf = do- -- Be strict to ensure that an error can be caught.- !path <- extractValue . parse <$> readFile sconf- return path- where- parse = head . filter ("package-db:" `isPrefixOf`) . lines- extractValue = fst . break isSpace . dropWhile isSpace . drop pkgDbKeyLen---- | Adding necessary GHC options to the package db.--- Exception is thrown if the string argument is incorrect.------ >>> sandboxArguments "/foo/bar/i386-osx-ghc-7.6.3-packages.conf.d"--- ["-no-user-package-db","-package-db","/foo/bar/i386-osx-ghc-7.6.3-packages.conf.d"]--- >>> sandboxArguments "/foo/bar/i386-osx-ghc-7.4.1-packages.conf.d"--- ["-no-user-package-conf","-package-conf","/foo/bar/i386-osx-ghc-7.4.1-packages.conf.d"]-sandboxArguments :: FilePath -> [String]-sandboxArguments pkgDb = [noUserPkgDbOpt, pkgDbOpt, pkgDb]- where- ver = extractGhcVer pkgDb- (pkgDbOpt,noUserPkgDbOpt)- | ver < 706 = ("-package-conf","-no-user-package-conf")- | otherwise = ("-package-db", "-no-user-package-db")---- | Extracting GHC version from the path of package db.--- Exception is thrown if the string argument is incorrect.------ >>> extractGhcVer "/foo/bar/i386-osx-ghc-7.6.3-packages.conf.d"--- 706-extractGhcVer :: String -> Int-extractGhcVer dir = ver- where- file = takeFileName dir- findVer = drop 4 . head . filter ("ghc-" `isPrefixOf`) . tails- (verStr1,_:left) = break (== '.') $ findVer file- (verStr2,_) = break (== '.') left- ver = read verStr1 * 100 + read verStr2
src/Test/DocTest.hs view
@@ -1,5 +1,4 @@ module Test.DocTest ( doctest ) where--import Run+import Test.DocTest.Internal.Run
+ src/Test/DocTest/Internal/Cabal.hs view
@@ -0,0 +1,10 @@+module Test.DocTest.Internal.Cabal (+ doctest+) where++import Imports++import qualified Cabal++doctest :: [String] -> IO ()+doctest = Cabal.externalCommand
+ src/Test/DocTest/Internal/Extract.hs view
@@ -0,0 +1,4 @@+module Test.DocTest.Internal.Extract (+ module Extract+) where+import Extract
+ src/Test/DocTest/Internal/Location.hs view
@@ -0,0 +1,4 @@+module Test.DocTest.Internal.Location (+ module Location+) where+import Location
+ src/Test/DocTest/Internal/Parse.hs view
@@ -0,0 +1,4 @@+module Test.DocTest.Internal.Parse (+ module Parse+) where+import Parse
+ src/Test/DocTest/Internal/Run.hs view
@@ -0,0 +1,4 @@+module Test.DocTest.Internal.Run (+ module Run+) where+import Run
src/Util.hs view
@@ -1,4 +1,7 @@ module Util where++import Imports+ import Data.Char convertDosLineEndings :: String -> String
+ test/Cabal/OptionsSpec.hs view
@@ -0,0 +1,60 @@+{-# LANGUAGE CPP #-}+module Cabal.OptionsSpec (spec) where++import Imports++import Test.Hspec++import System.IO+import System.IO.Silently+import System.Exit+import System.Process+import Data.Set ((\\))+import qualified Data.Set as Set++import qualified Cabal.ReplOptionsSpec as Repl++import Cabal.Options++spec :: Spec+spec = do+ describe "replOnlyOptions" $ do+ it "is the set of options that are unique to 'cabal repl'" $ do+ build <- Set.fromList . lines <$> readProcess "cabal" ["build", "--list-options"] ""+ repl <- Set.fromList . lines <$> readProcess "cabal" ["repl", "--list-options"] ""+ Set.toList replOnlyOptions `shouldMatchList` Set.toList (repl \\ build)++ describe "rejectUnsupportedOptions" $ do+ it "produces error messages that are consistent with 'cabal repl'" $ do+ let+ shouldFail :: HasCallStack => String -> IO a -> Expectation+ shouldFail command action = do+ hCapture_ [stderr] (action `shouldThrow` (== ExitFailure 1))+ `shouldReturn` "Error: cabal: unrecognized '" <> command <> "' option `--installdir'\n"++#ifndef mingw32_HOST_OS+ shouldFail "repl" $ call "cabal" ["repl", "--installdir"]+#endif+ shouldFail "doctest" $ rejectUnsupportedOptions ["--installdir"]++ context "with --list-options" $ do+ it "lists supported command-line options" $ do+ repl <- Set.fromList . lines <$> readProcess "cabal" ["repl", "--list-options"] ""+ doctest <- Set.fromList . lines <$> capture_ (rejectUnsupportedOptions ["--list-options"] `shouldThrow` (== ExitSuccess))+ Set.toList (doctest \\ repl) `shouldMatchList` []+ Set.toList (repl \\ doctest) `shouldMatchList` Set.toList Repl.unsupported++ describe "discardReplOptions" $ do+ it "discards 'cabal repl'-only options" $ do+ discardReplOptions [+ "-w", "ghc-9.10"+ , "--build-depends=foo"+ , "--build-depends", "foo"+ , "-bfoo"+ , "-b", "foo"+ , "--disable-optimization"+ , "--enable-multi-repl"+ , "--repl-options", "foo"+ , "--repl-options=foo"+ , "--allow-newer"+ ] `shouldBe` ["--with-compiler=ghc-9.10", "--disable-optimization", "--allow-newer"]
+ test/Cabal/PathsSpec.hs view
@@ -0,0 +1,22 @@+module Cabal.PathsSpec (spec) where++import Imports++import Test.Hspec++import System.Directory++import Cabal ()+import Cabal.Paths++spec :: Spec+spec = do+ describe "paths" $ do+ it "returns the path to 'ghc'" $ do+ (paths "cabal" [] >>= doesFileExist . ghc) `shouldReturn` True++ it "returns the path to 'ghc-pkg'" $ do+ (paths "cabal" [] >>= doesFileExist . ghcPkg) `shouldReturn` True++ it "returns the path to Cabal's cache directory" $ do+ (paths "cabal" [] >>= doesDirectoryExist . cache) `shouldReturn` True
+ test/Cabal/ReplOptionsSpec.hs view
@@ -0,0 +1,99 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE ViewPatterns #-}+module Cabal.ReplOptionsSpec (spec, unsupported) where++import Imports++import Test.Hspec++import Data.List+import System.Process+import Data.Set (Set)+import qualified Data.Set as Set++import Cabal.ReplOptions++phony :: [String]+phony = [+ "with-PROG"+ , "PROG-option"+ , "PROG-options"+ ]++undocumented :: Set String+undocumented = Set.fromList [+ "--enable-optimisation"+ , "--disable-optimisation"+ , "--haddock-hyperlink-sources"+ , "--haddock-hyperlinked-source"+ ]++unsupported :: Set String+unsupported = undocumented <> Set.fromList (map ("--" <>) phony)++spec :: Spec+spec = do+ describe "options" $ do+ it "is the list of documented 'repl' options" $ do+ documentedOptions <- parseOptions <$> readProcess "cabal" ["help", "repl"] ""+ options `shouldBe` filter (optionName >>> (`notElem` phony)) documentedOptions++ it "is consistent with 'cabal repl --list-options'" $ do+ let+ optionNames :: Option -> [String]+ optionNames option = reverse $ "--" <> optionName option : case optionShortName option of+ Nothing -> []+ Just c -> [['-', c]]+ + repl <- filter (`Set.notMember` unsupported) . lines <$> readProcess "cabal" ["repl", "--list-options"] ""+ concatMap optionNames options `shouldBe` repl++parseOptions :: String -> [Option]+parseOptions = map parseOption . takeOptions+ where+ parseOption :: String -> Option+ parseOption input = case input of+ longAndHelp@('-':'-':_) -> parseLongOption Nothing longAndHelp+ '-':short:',':' ':longAndHelp -> parseLongOption (Just short) longAndHelp+ '-':short:'[':(breakOn ']' ->+ (_arg, ']':',':' ':longAndHelp)) -> parseLongOption (Just short) longAndHelp+ '-':short:' ':(breakOn ' ' ->+ (arg, ' ':'o':'r':' ':(stripPrefix ('-':short:arg) ->+ Just (',':' ':longAndHelp)))) -> parseLongOption (Just short) longAndHelp+ _ -> err+ where+ parseLongOption :: Maybe Char -> String -> Option+ parseLongOption short longAndHelp = case breakOnAny " [=" longAndHelp of+ ('-':'-':long, ' ':help) -> accept long NoArgument help+ ('-':'-':long, '[':'=': (breakOn ']' ->+ (arg, ']':help))) -> accept long (OptionalArgument arg) help+ ('-':'-':long, '=':(breakOn ' ' ->+ (arg, ' ':help))) -> accept long (Argument arg) help+ _ -> err+ where+ accept :: String -> Argument -> String -> Option+ accept long arg help = Option long short arg (strip help)++ err :: HasCallStack => Option+ err = error input++ breakOn c = break (== c)+ breakOnAny xs = break (`elem` xs)++ takeOptions :: String -> [String]+ takeOptions input = map strip . joinLines $ case break (== "Flags for repl:") (lines input) of+ (_, "Flags for repl:" : xs) -> case break (== "") xs of+ (ys, "" : _) -> ys+ _ -> undefined+ _ -> undefined++ joinLines :: [String] -> [String]+ joinLines = go+ where+ go = \ case+ x : y : ys | isOption y -> x : go (y : ys)+ x : y : ys -> go $ (x ++ ' ' : strip y) : ys+ x : xs -> x : xs+ [] -> []++ isOption = isPrefixOf " -"
+ test/ExtractSpec.hs view
@@ -0,0 +1,95 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE FlexibleContexts #-}++module ExtractSpec (main, spec) where++import Imports++import Test.Hspec+import Test.HUnit+++#if __GLASGOW_HASKELL__ < 900+import Panic (GhcException (..))+#else+import GHC.Utils.Panic (GhcException (..))+#endif++import Extract+import Location+import System.FilePath++shouldGive :: HasCallStack => (String, String) -> [Module String] -> Assertion+(d, m) `shouldGive` expected = do+ r <- map (fmap unLoc) `fmap` extract ["-i" ++ dir, dir </> m]+ r `shouldBe` expected+ where dir = "test/extract" </> d++main :: IO ()+main = hspec spec++spec :: Spec+spec = do++ describe "extract" $ do+ it "extracts documentation for a top-level declaration" $ do+ ("declaration", "Foo.hs") `shouldGive` [Module "Foo" Nothing [" Some documentation"]]++ it "extracts documentation from argument list" $ do+ ("argument-list", "Foo.hs") `shouldGive` [Module "Foo" Nothing [" doc for arg1", " doc for arg2"]]++ it "extracts documentation for a type class function" $ do+ ("type-class", "Foo.hs") `shouldGive` [Module "Foo" Nothing [" Convert given value to a string."]]++ it "extracts documentation from the argument list of a type class function" $ do+ ("type-class-args", "Foo.hs") `shouldGive` [Module "Foo" Nothing [" foo", " bar"]]++ it "extracts documentation from the module header" $ do+ ("module-header", "Foo.hs") `shouldGive` [Module "Foo" Nothing [" Some documentation"]]++ it "extracts documentation from imported modules" $ do+ ("imported-module", "Bar.hs") `shouldGive` [Module "Bar" Nothing [" documentation for bar"], Module "Baz" Nothing [" documentation for baz"]]++ it "extracts documentation from export list" $ do+ ("export-list", "Foo.hs") `shouldGive` [Module "Foo" Nothing [" documentation from export list"]]++ it "extracts documentation from named chunks" $ do+ ("named-chunks", "Foo.hs") `shouldGive` [Module "Foo" Nothing [" named chunk foo", "\n named chunk bar"]]++ it "returns docstrings in the same order they appear in the source" $ do+ ("comment-order", "Foo.hs") `shouldGive` [Module "Foo" Nothing [" module header", " export list 1", " export list 2", " foo", " named chunk", " bar"]]++ it "extracts $setup code" $ do+ ("setup", "Foo.hs") `shouldGive` [Module "Foo" (Just "\n some setup code") [" foo", " bar", " baz"]]++ it "fails on invalid flags" $ do+ extract ["--foobar", "test/Foo.hs"] `shouldThrow` (\e -> case e of UsageError "unrecognized option `--foobar'" -> True; _ -> False)++ describe "extract (regression tests)" $ do+ it "works with infix operators" $ do+ ("regression", "Fixity.hs") `shouldGive` [Module "Fixity" Nothing []]++ it "works with parallel list comprehensions" $ do+ ("regression", "ParallelListComp.hs") `shouldGive` [Module "ParallelListComp" Nothing []]++ it "works with list comprehensions in instance definitions" $ do+ ("regression", "ParallelListCompClass.hs") `shouldGive` [Module "ParallelListCompClass" Nothing []]++ it "works with foreign imports" $ do+ ("regression", "ForeignImport.hs") `shouldGive` [Module "ForeignImport" Nothing []]++ it "works for rewrite rules" $ do+ ("regression", "RewriteRules.hs") `shouldGive` [Module "RewriteRules" Nothing [" doc for foo"]]++ it "works for rewrite rules with type signatures" $ do+ ("regression", "RewriteRulesWithSigs.hs") `shouldGive` [Module "RewriteRulesWithSigs" Nothing [" doc for foo"]]++ it "strips CR from dos line endings" $ do+ ("dos-line-endings", "Foo.hs") `shouldGive` [Module "Foo" Nothing ["\n foo\n bar\n baz"]]++ it "works with a module that splices in an expression from an other module" $ do+ ("th", "Foo.hs") `shouldGive` [Module "Foo" Nothing [" some documentation"], Module "Bar" Nothing []]++ it "works for type families and GHC 7.6.1" $ do+ ("type-families", "Foo.hs") `shouldGive` [Module "Foo" Nothing []]
+ test/InfoSpec.hs view
@@ -0,0 +1,17 @@+module InfoSpec (spec) where++import Imports++import Test.Hspec++import System.Process++import Info (formatInfo)+import Interpreter (ghc)++spec :: Spec+spec = do+ describe "formatInfo" $ do+ it "formats --info output" $ do+ info <- readProcess ghc ["--info"] ""+ formatInfo (read info) `shouldBe` info
+ test/InterpreterSpec.hs view
@@ -0,0 +1,40 @@+module InterpreterSpec (spec) where++import Imports++import Test.Hspec++import Interpreter (Interpreter, interpreterSupported, haveInterpreterKey, ghcInfo, ghc, safeEval, filterExpression)+import qualified Interpreter++withInterpreter :: (Interpreter -> IO a) -> IO a+withInterpreter = Interpreter.withInterpreter (Interpreter.ghc, ["--interactive"])++spec :: Spec+spec = do+ describe "interpreterSupported" $ do+ it "indicates whether GHCi is supported on current platform" $ do+ (Interpreter.interpreterSupported >> return ()) `shouldReturn` ()++ describe "ghcInfo" $ do+ it ("includes " ++ show haveInterpreterKey) $ do+ info <- ghcInfo+ lookup haveInterpreterKey info `shouldSatisfy`+ (||) <$> (== Just "YES") <*> (== Just "NO")++ describe "safeEval" $ do+ it "evaluates an expression" $ withInterpreter $ \ ghci -> do+ Interpreter.safeEval ghci "23 + 42" `shouldReturn` Right "65\n"++ it "returns Left on unterminated multiline command" $ withInterpreter $ \ ghci -> do+ Interpreter.safeEval ghci ":{\n23 + 42" `shouldReturn` Left "unterminated multi-line command"++ describe "filterExpression" $ do+ it "removes :set -XTemplateHaskell" $ do+ filterExpression ":set -XTemplateHaskell" `shouldBe` Right ""++ it "filters -XTemplateHaskell" $ do+ filterExpression ":set -XTemplateHaskell -XCPP" `shouldBe` Right ":set -XCPP"++ it "leaves :set-statement that do not set -XTemplateHaskell alone " $ do+ filterExpression ":set -XFoo -XBar" `shouldBe` Right ":set -XFoo -XBar"
+ test/Language/Haskell/GhciWrapperSpec.hs view
@@ -0,0 +1,115 @@+{-# LANGUAGE CPP #-}+module Language.Haskell.GhciWrapperSpec (main, spec) where++import Imports++import Test.Hspec+import System.IO.Silently++import Data.List++import Language.Haskell.GhciWrapper (Interpreter, Config(..), defaultConfig, PreserveIt(..))+import qualified Language.Haskell.GhciWrapper as Interpreter++main :: IO ()+main = hspec spec++withInterpreterConfig :: Config -> [String] -> (Interpreter -> IO a) -> IO a+withInterpreterConfig config args = bracket (Interpreter.new config args) Interpreter.close++withInterpreterArgs :: [String] -> ((String -> IO String) -> IO a) -> IO a+withInterpreterArgs args action = withInterpreterConfig defaultConfig args $ action . Interpreter.eval++withInterpreter :: ((String -> IO String) -> IO a) -> IO a+withInterpreter = withInterpreterArgs []++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 "evalWith" $ do+ context "with PreserveIt" $ do+ it "preserves it" $ do+ withInterpreterConfig defaultConfig [] $ \ghci -> do+ Interpreter.evalWith PreserveIt 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` ""+#if __GLASGOW_HASKELL__ >= 912+ ghci "throwIO DivideByZero" `shouldReturn` "*** Exception: divide by zero\n\nHasCallStack backtrace:\n throwIO, called at <interactive>:25:1 in interactive:Ghci22\n\n"+#else+ ghci "throwIO DivideByZero" `shouldReturn` "*** Exception: divide by zero\n"+#endif++ 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+ ghci "foo" >>= (`shouldSatisfy` isInfixOf "Variable not in scope: foo")++ 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 ":seti -XOverloadedStrings -Wall -Werror" `shouldReturn` ""+ ghci "putStrLn \"foo\"" `shouldReturn` "foo\n"++ context "with NoImplicitPrelude" $ do+ it "works" $ withInterpreterArgs ["-XNoImplicitPrelude"] $ \ghci -> do+ ghci "putStrLn \"foo\"" >>= (`shouldContain` "Variable not in scope: putStrLn")+ ghci "23" `shouldReturn` "23\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/LocationSpec.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE CPP #-}++module LocationSpec (main, spec) where++import Imports++import Test.Hspec++import Location++#if __GLASGOW_HASKELL__ < 900+import SrcLoc+import FastString (fsLit)+#else+import GHC.Types.SrcLoc+import GHC.Data.FastString (fsLit)+#endif++main :: IO ()+main = hspec spec++spec :: Spec+spec = do++ describe "toLocation" $ do++ it "works for a regular SrcSpan" $ do+ toLocation (mkSrcSpan (mkSrcLoc (fsLit "Foo.hs") 2 5) (mkSrcLoc (fsLit "Foo.hs") 10 20))+ `shouldBe` Location "Foo.hs" 2++ it "works for a single-line SrcSpan" $ do+ toLocation (mkSrcSpan (mkSrcLoc (fsLit "Foo.hs") 2 5) (mkSrcLoc (fsLit "Foo.hs") 2 10))+ `shouldBe` Location "Foo.hs" 2++ it "works for a SrcSpan that corresponds to single point" $ do+ (toLocation . srcLocSpan) (mkSrcLoc (fsLit "Foo.hs") 10 20)+ `shouldBe` Location "Foo.hs" 10++ it "works for a bad SrcSpan" $ do+ toLocation noSrcSpan `shouldBe` UnhelpfulLocation "<no location info>"++ it "works for a SrcLoc with bad locations" $ do+ toLocation (mkSrcSpan noSrcLoc noSrcLoc)+ `shouldBe` UnhelpfulLocation "<no location info>"++ describe "enumerate" $ do+ it "replicates UnhelpfulLocation" $ do+ let loc = UnhelpfulLocation "foo"+ (take 10 $ enumerate loc) `shouldBe` replicate 10 loc++ it "enumerates Location" $ do+ let loc = Location "Foo.hs" 23+ (take 3 $ enumerate loc) `shouldBe` [Location "Foo.hs" 23, Location "Foo.hs" 24, Location "Foo.hs" 25]
+ test/MainSpec.hs view
@@ -0,0 +1,204 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ConstraintKinds #-}+module MainSpec (main, spec) where++import Imports++import Test.Hspec+import Test.HUnit (assertEqual, Assertion)++import System.Directory (getCurrentDirectory, setCurrentDirectory)+import System.FilePath+import Run hiding (doctest, doctestWith)+import System.IO.Silently+import System.IO++withCurrentDirectory :: FilePath -> IO a -> IO a+withCurrentDirectory workingDir action = do+ bracket getCurrentDirectory setCurrentDirectory $ \_ -> do+ setCurrentDirectory workingDir+ action++doctest :: HasCallStack => FilePath -> [String] -> Summary -> Assertion+doctest = doctestWith False False++doctestWithPreserveIt :: HasCallStack => FilePath -> [String] -> Summary -> Assertion+doctestWithPreserveIt = doctestWith True False++doctestWithFailFast :: HasCallStack => FilePath -> [String] -> Summary -> Assertion+doctestWithFailFast = doctestWith False True++doctestWith :: HasCallStack => Bool -> Bool -> FilePath -> [String] -> Summary -> Assertion+doctestWith preserveIt failFast workingDir ghcOptions expected = do+ actual <- withCurrentDirectory ("test/integration" </> workingDir) (hSilence [stderr] $ doctestWithResult defaultConfig {ghcOptions, preserveIt, failFast})+ assertEqual label (formatSummary expected) (formatSummary actual)+ where+ label = workingDir ++ " " ++ show ghcOptions++cases :: Int -> Summary+cases n = Summary n n 0 0++main :: IO ()+main = hspec spec++spec :: Spec+spec = do+ describe "doctest" $ do+ it "testSimple" $ do+ doctest "." ["testSimple/Fib.hs"]+ (cases 1)++ it "it-variable" $ do+ doctestWithPreserveIt "." ["it/Foo.hs"]+ (cases 5)++ it "it-variable in $setup" $ do+ doctestWithPreserveIt "." ["it/Setup.hs"]+ (cases 5)++ it "failing" $ do+ doctest "." ["failing/Foo.hs"]+ (cases 1) {sFailures = 1}++ it "skips subsequent examples from the same group if an example fails" $ do+ doctest "." ["failing-multiple/Foo.hs"]+ (cases 4) {sTried = 2, sFailures = 1}++ context "without --fail-fast" $ do+ it "continuous even if some tests fail" $ do+ doctest "fail-fast" ["Foo.hs"]+ (cases 4) {sTried = 4, sFailures = 1}++ context "with --fail-fast" $ do+ it "stops after the first failure" $ do+ doctestWithFailFast "fail-fast" ["Foo.hs"]+ (cases 4) {sTried = 2, sFailures = 1}++ it "stops after failures in $setup" $ do+ doctestWithFailFast "fail-fast" ["SetupFoo.hs"]+ (cases 6) {sTried = 1, sFailures = 1}++ it "testImport" $ do+ doctest "testImport" ["ModuleA.hs"]+ (cases 3)+ doctest ".." ["-iintegration/testImport", "integration/testImport/ModuleA.hs"]+ (cases 3)++ it "testCommentLocation" $ do+ doctest "." ["testCommentLocation/Foo.hs"]+ (cases 11)++ it "testPutStr" $ do+ doctest "testPutStr" ["Fib.hs"]+ (cases 3)++ it "fails on multi-line expressions, introduced with :{" $ do+ doctest "testFailOnMultiline" ["Fib.hs"]+ (cases 2) {sErrors = 2}++ it "testBlankline" $ do+ doctest "testBlankline" ["Fib.hs"]+ (cases 1)++ it "examples from the same Haddock comment share the same scope" $ do+ doctest "testCombinedExample" ["Fib.hs"]+ (cases 4)++ it "testDocumentationForArguments" $ do+ doctest "testDocumentationForArguments" ["Fib.hs"]+ (cases 1)++ it "template-haskell" $ do+ doctest "template-haskell" ["Foo.hs"]+ (cases 2)++ it "handles source files with CRLF line endings" $ do+ doctest "dos-line-endings" ["Fib.hs"]+ (cases 1)++ it "runs $setup before each test group" $ do+ doctest "setup" ["Foo.hs"]+ (cases 2)++ it "skips subsequent tests from a module, if $setup fails" $ do+ doctest "setup-skip-on-failure" ["Foo.hs"]+ (cases 3) {sTried = 1, sFailures = 1}++ -- Andreas, 2021-02-27, see issue #294.+ -- This test case contains a hard-wired path that does not work+ -- with v2-cabal.+ -- I tested it under v2-cabal with a very non-portable path.+ -- Deactivating the test case until a systematic solution is found...+ --+ -- it "works with additional object files" $ do+ -- -- -- Path for v1-cabal:+ -- -- doctest "with-cbits" ["Bar.hs", "../../../dist/build/spec/spec-tmp/test/integration/with-cbits/foo.o"]+ -- -- -- Path for v2-cabal with ghc-9.0.1+ -- -- doctest "with-cbits" ["Bar.hs", "../../../dist-newstyle/build/x86_64-osx/ghc-9.0.1/doctest-0.19/t/spec/build/spec/spec-tmp/Test/integration/with-cbits/foo.o"]+ -- (cases 1)++ it "ignores trailing whitespace when matching test output" $ do+ doctest "trailing-whitespace" ["Foo.hs"]+ (cases 1)++ describe "doctest as a runner for QuickCheck properties" $ do+ it "runs a boolean property" $ do+ doctest "property-bool" ["Foo.hs"]+ (cases 1)++ it "runs an explicitly quantified property" $ do+ doctest "property-quantified" ["Foo.hs"]+ (cases 1)++ it "runs an implicitly quantified property" $ do+ doctest "property-implicitly-quantified" ["Foo.hs"]+ (cases 1)++ it "reports a failing property" $ do+ doctest "property-failing" ["Foo.hs"]+ (cases 1) {sFailures = 1}++ it "runs a boolean property with an explicit type signature" $ do+ doctest "property-bool-with-type-signature" ["Foo.hs"]+ (cases 1)++ it "runs $setup before each property" $ do+ doctest "property-setup" ["Foo.hs"]+ (cases 3)++ describe "doctest (regression tests)" $ do+ it "bugfixWorkingDirectory" $ do+ doctest "bugfixWorkingDirectory" ["Fib.hs"]+ (cases 1)+ doctest "bugfixWorkingDirectory" ["examples/Fib.hs"]+ (cases 2)++ it "bugfixOutputToStdErr" $ do+ doctest "bugfixOutputToStdErr" ["Fib.hs"]+ (cases 2)++ it "bugfixImportHierarchical" $ do+ doctest "bugfixImportHierarchical" ["ModuleA.hs", "ModuleB.hs"]+ (cases 3)++ it "bugfixMultipleModules" $ do+ doctest "bugfixMultipleModules" ["ModuleA.hs"]+ (cases 5)++ it "testCPP" $ do+ doctest "testCPP" ["-cpp", "Foo.hs"]+ (cases 1) {sFailures = 1}+ doctest "testCPP" ["-cpp", "-DFOO", "Foo.hs"]+ (cases 1)++ it "template-haskell-bugfix" $ do+ doctest "template-haskell-bugfix" ["Main.hs"]+ (cases 2)++ it "doesn't clash with user bindings of stdout/stderr" $ do+ doctest "local-stderr-binding" ["A.hs"]+ (cases 1)++ it "doesn't get confused by doctests using System.IO imports" $ do+ doctest "system-io-imported" ["A.hs"]+ (cases 1)
+ test/OptionsSpec.hs view
@@ -0,0 +1,108 @@+module OptionsSpec (spec) where++import Imports++import Data.List++import Test.Hspec+import Test.QuickCheck hiding (verbose)++import Options++newtype NonInteractive = NonInteractive String+ deriving (Eq, Show)++instance Arbitrary NonInteractive where+ arbitrary = NonInteractive <$> elements (nonInteractiveGhcOptions \\ ["--info"])++spec :: Spec+spec = do+ describe "parseOptions" $ do+ let+ run :: [String] -> Run+ run ghcOptions = defaultRun {+ runWarnings = ["WARNING: --optghc is deprecated, doctest now accepts arbitrary GHC options\ndirectly."]+ , runMagicMode = True+ , runConfig = defaultConfig { ghcOptions }+ }++ it "strips --optghc" $+ parseOptions ["--optghc", "foobar"] `shouldBe` Result (run ["foobar"])++ it "strips --optghc=" $+ parseOptions ["--optghc=foobar"] `shouldBe` Result (run ["foobar"])++ context "with ghc options that are not valid with --interactive" $ do+ it "returns ProxyToGhc" $ do+ property $ \ (NonInteractive x) xs -> do+ let options = x : xs+ parseOptions options `shouldBe` ProxyToGhc options++ context "with --interactive" $ do+ let options = ["--interactive", "--foo", "--bar"]++ it "disables magic mode" $ do+ runMagicMode <$> parseOptions options `shouldBe` Result False++ it "filters out --interactive" $ do+ ghcOptions . runConfig <$> parseOptions options `shouldBe` Result ["--foo", "--bar"]++ it "accepts --fast" $ do+ fastMode . runConfig <$> parseOptions ("--fast" : options) `shouldBe` Result True++ describe "--no-magic" $ do+ context "without --no-magic" $ do+ it "enables magic mode" $ do+ runMagicMode <$> parseOptions [] `shouldBe` Result True++ context "with --no-magic" $ do+ it "disables magic mode" $ do+ runMagicMode <$> parseOptions ["--no-magic"] `shouldBe` Result False++ describe "--fast" $ do+ context "without --fast" $ do+ it "disables fast mode" $ do+ fastMode . runConfig <$> parseOptions [] `shouldBe` Result False++ context "with --fast" $ do+ it "enables fast mode" $ do+ fastMode . runConfig <$> parseOptions ["--fast"] `shouldBe` Result True++ describe "--preserve-it" $ do+ context "without --preserve-it" $ do+ it "does not preserve the `it` variable" $ do+ preserveIt . runConfig <$> parseOptions [] `shouldBe` Result False++ context "with --preserve-it" $ do+ it "preserves the `it` variable" $ do+ preserveIt . runConfig <$> parseOptions ["--preserve-it"] `shouldBe` Result True++ describe "--fail-fast" $ do+ context "without --fail-fast" $ do+ it "disables fail-fast mode" $ do+ failFast . runConfig <$> parseOptions [] `shouldBe` Result False++ context "with --fail-fast" $ do+ it "enables fail-fast mode" $ do+ failFast . runConfig <$> parseOptions ["--fail-fast"] `shouldBe` Result True++ context "with --help" $ do+ it "outputs usage information" $ do+ parseOptions ["--help"] `shouldBe` Output usage++ context "with --version" $ do+ it "outputs version information" $ do+ parseOptions ["--version"] `shouldBe` Output versionInfo++ context "with --info" $ do+ it "outputs machine readable version information" $ do+ parseOptions ["--info"] `shouldBe` Output info++ describe "--verbose" $ do+ context "without --verbose" $ do+ it "is not verbose by default" $ do+ verbose . runConfig <$> parseOptions [] `shouldBe` Result False++ context "with --verbose" $ do+ it "parses verbose option" $ do+ verbose . runConfig <$> parseOptions ["--verbose"] `shouldBe` Result True
+ test/PackageDBsSpec.hs view
@@ -0,0 +1,40 @@+module PackageDBsSpec (main, spec) where++import Imports++import qualified Control.Exception as E+import Data.List (intercalate)+import PackageDBs+import System.Environment+import System.FilePath (searchPathSeparator)+import Test.Hspec++import Test.Mockery.Directory++main :: IO ()+main = hspec spec++withEnv :: String -> String -> IO a -> IO a+withEnv k v action = E.bracket save restore $ \_ -> do+ setEnv k v >> action+ where+ save = lookup k <$> getEnvironment+ restore = maybe (unsetEnv k) (setEnv k)++clearEnv :: IO a -> IO a+clearEnv =+ withEnv "GHC_PACKAGE_PATH" ""++combineDirs :: [FilePath] -> String+combineDirs = intercalate [searchPathSeparator]++spec :: Spec+spec = around_ clearEnv $ do+ describe "getPackageDBsFromEnv" $ do+ around_ (inTempDirectory) $ do+ it "uses global and user when no env used" $ do+ getPackageDBsFromEnv `shouldReturn` PackageDBs True True []++ it "respects GHC_PACKAGE_PATH" $+ withEnv "GHC_PACKAGE_PATH" (combineDirs ["foo", "bar", ""]) $ do+ getPackageDBsFromEnv `shouldReturn` PackageDBs False True ["foo", "bar"]
+ test/ParseSpec.hs view
@@ -0,0 +1,205 @@+{-# LANGUAGE OverloadedStrings #-}+module ParseSpec (main, spec) where++import Imports++import Test.Hspec+import Data.String+import Data.String.Builder (Builder, build)+import Control.Monad.Trans.Writer++import Parse+import Location++main :: IO ()+main = hspec spec++group :: Writer [DocTest] () -> Writer [[DocTest]] ()+group g = tell [execWriter g]++ghci :: Expression -> Builder -> Writer [DocTest] ()+ghci expressions expected = tell [Example expressions $ (map fromString . lines . build) expected]++prop_ :: Expression -> Writer [DocTest] ()+prop_ e = tell [Property e]++module_ :: String -> Writer [[DocTest]] () -> Writer [Module [DocTest]] ()+module_ name gs = tell [Module name Nothing $ execWriter gs]++shouldGive :: IO [Module [Located DocTest]] -> Writer [Module [DocTest]] () -> Expectation+shouldGive action expected = map (fmap $ map unLoc) `fmap` action `shouldReturn` execWriter expected++spec :: Spec+spec = do+ describe "extractDocTests" $ do+ it "extracts properties from a module" $ do+ extractDocTests ["test/parse/property/Fib.hs"] `shouldGive` do+ module_ "Fib" $ do+ group $ do+ prop_ "foo"+ prop_ "bar"+ prop_ "baz"++ it "extracts examples from a module" $ do+ extractDocTests ["test/parse/simple/Fib.hs"] `shouldGive` do+ module_ "Fib" $ do+ group $ do+ ghci "putStrLn \"foo\""+ "foo"+ ghci "putStr \"bar\""+ "bar"+ ghci "putStrLn \"baz\""+ "baz"++ it "extracts examples from documentation for non-exported names" $ do+ extractDocTests ["test/parse/non-exported/Fib.hs"] `shouldGive` do+ module_ "Fib" $ do+ group $ do+ ghci "putStrLn \"foo\""+ "foo"+ ghci "putStr \"bar\""+ "bar"+ ghci "putStrLn \"baz\""+ "baz"++ it "extracts multiple examples from a module" $ do+ extractDocTests ["test/parse/multiple-examples/Foo.hs"] `shouldGive` do+ module_ "Foo" $ do+ group $ do+ ghci "foo"+ "23"+ group $ do+ ghci "bar"+ "42"++ it "returns an empty list, if documentation contains no examples" $ do+ extractDocTests ["test/parse/no-examples/Fib.hs"] >>= (`shouldBe` [])++ it "sets setup code to Nothing, if it does not contain any tests" $ do+ extractDocTests ["test/parse/setup-empty/Foo.hs"] `shouldGive` do+ module_ "Foo" $ do+ group $ do+ ghci "foo"+ "23"++ it "keeps modules that only contain setup code" $ do+ extractDocTests ["test/parse/setup-only/Foo.hs"] `shouldGive` do+ tell [Module "Foo" (Just [Example "foo" ["23"]]) []]++ describe "parseInteractions (an internal function)" $ do++ let parse_ = map unLoc . parseInteractions . noLocation . build++ it "parses an interaction" $ do+ parse_ $ do+ ">>> foo"+ "23"+ `shouldBe` [("foo", ["23"])]++ it "drops whitespace as appropriate" $ do+ parse_ $ do+ " >>> foo "+ " 23"+ `shouldBe` [("foo", ["23"])]++ it "parses an interaction without a result" $ do+ parse_ $ do+ ">>> foo"+ `shouldBe` [("foo", [])]++ it "works with a complex example" $ do+ parse_ $ do+ "test"+ "foobar"+ ""+ ">>> foo"+ "23"+ ""+ ">>> baz"+ ""+ ">>> bar"+ "23"+ ""+ "baz"+ `shouldBe` [("foo", ["23"]), ("baz", []), ("bar", ["23"])]++ it "attaches location information to parsed interactions" $ do+ let loc = Located . Location "Foo.hs"+ r <- return . parseInteractions . loc 23 . build $ do+ "1"+ "2"+ ""+ ">>> 4"+ "5"+ ""+ ">>> 7"+ ""+ ">>> 9"+ "10"+ ""+ "11"+ r `shouldBe` [loc 26 $ ("4", ["5"]), loc 29 $ ("7", []), loc 31 $ ("9", ["10"])]++ it "basic multiline" $ do+ parse_ $ do+ ">>> :{ first"+ " next"+ "some"+ ":}"+ "output"+ `shouldBe` [(":{ first\n next\nsome\n:}", ["output"])]++ it "multiline align output" $ do+ parse_ $ do+ ">>> :{ first"+ " :}"+ " output"+ `shouldBe` [(":{ first\n:}", ["output"])]++ it "multiline align output with >>>" $ do+ parse_ $ do+ " >>> :{ first"+ " >>> :}"+ " output"+ `shouldBe` [(":{ first\n:}", ["output"])]++ it "parses wild cards lines" $ do+ parse_ $ do+ " >>> action"+ " foo"+ " ..."+ " bar"+ `shouldBe` [("action", ["foo", WildCardLine, "bar"])]++ it "parses wild card chunks" $ do+ parse_ $ do+ " >>> action"+ " foo ... bar"+ `shouldBe` [("action", [ExpectedLine ["foo ", WildCardChunk, " bar"]])]++ describe " parseProperties (an internal function)" $ do+ let parse_ = map unLoc . parseProperties . noLocation . build++ it "parses a property" $ do+ parse_ $ do+ "prop> foo"+ `shouldBe` ["foo"]++ describe "mkLineChunks (an internal function)" $ do++ it "replaces ellipsis with WildCardChunks" $ do+ mkLineChunks "foo ... bar ... baz" `shouldBe`+ ["foo ", WildCardChunk, " bar ", WildCardChunk, " baz"]++ it "doesn't replace fewer than 3 consecutive dots" $ do+ mkLineChunks "foo .. bar .. baz" `shouldBe`+ ["foo .. bar .. baz"]++ it "handles leading and trailing dots" $ do+ mkLineChunks ".. foo bar .." `shouldBe` [".. foo bar .."]++ it "handles leading and trailing ellipsis" $ do+ mkLineChunks "... foo bar ..." `shouldBe` [ WildCardChunk+ , " foo bar "+ , WildCardChunk+ ]
+ test/PropertySpec.hs view
@@ -0,0 +1,126 @@+{-# LANGUAGE OverloadedStrings #-}+module PropertySpec (spec) where++import Imports++import Test.Hspec+import Data.String.Builder++import Property+import Interpreter (Interpreter)+import qualified Interpreter++withInterpreter :: (Interpreter -> IO a) -> IO a+withInterpreter = Interpreter.withInterpreter (Interpreter.ghc, ["--interactive"])++isFailure :: PropertyResult -> Bool+isFailure (Failure _) = True+isFailure _ = False++spec :: Spec+spec = do+ describe "runProperty" $ do+ it "reports a failing property" $ withInterpreter $ \repl -> do+ runProperty repl "False" `shouldReturn` Failure "*** Failed! Falsified (after 1 test):"++ it "runs a Bool property" $ withInterpreter $ \repl -> do+ runProperty repl "True" `shouldReturn` Success++ it "runs a Bool property with an explicit type signature" $ withInterpreter $ \repl -> do+ runProperty repl "True :: Bool" `shouldReturn` Success++ it "runs an implicitly quantified property" $ withInterpreter $ \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+ runProperty repl "foldr (+) 0 is == sum (is :: [Int])" `shouldReturn` Success++ it "runs an explicitly quantified property" $ withInterpreter $ \repl -> do+ runProperty repl "\\xs -> (reverse . reverse) xs == (xs :: [Int])" `shouldReturn` Success++ it "allows to mix implicit and explicit quantification" $ withInterpreter $ \repl -> do+ runProperty repl "\\x -> x + y == y + x" `shouldReturn` Success++ it "reports the value for which a property fails" $ withInterpreter $ \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+ 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+ runProperty repl "reverse xs == xs" >>= (`shouldSatisfy` isFailure)++ describe "freeVariables" $ do+ it "finds a free variables in a term" $ withInterpreter $ \repl -> do+ freeVariables repl "x" `shouldReturn` ["x"]++ it "ignores duplicates" $ withInterpreter $ \repl -> do+ freeVariables repl "x == x" `shouldReturn` ["x"]++ it "works for terms with multiple names" $ withInterpreter $ \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+ freeVariables repl "x' == y''" `shouldReturn` ["x'", "y''"]++ it "works for names that are similar to other names that are in scope" $ withInterpreter $ \repl -> do+ freeVariables repl "length_" `shouldReturn` ["length_"]++ describe "parseNotInScope" $ do+ context "when error message was produced by GHC 7.4.1" $ do+ it "extracts a variable name of variable that is not in scope from an error message" $ do+ parseNotInScope . build $ do+ "<interactive>:4:1: Not in scope: `x'"+ `shouldBe` ["x"]++ it "ignores duplicates" $ do+ parseNotInScope . build $ do+ "<interactive>:4:1: Not in scope: `x'"+ ""+ "<interactive>:4:6: Not in scope: `x'"+ `shouldBe` ["x"]++ it "works for variable names that contain a prime" $ do+ parseNotInScope . build $ do+ "<interactive>:2:1: Not in scope: x'"+ ""+ "<interactive>:2:7: Not in scope: y'"+ `shouldBe` ["x'", "y'"]++ it "works for error messages with suggestions" $ do+ parseNotInScope . build $ do+ "<interactive>:1:1:"+ " Not in scope: `is'"+ " Perhaps you meant `id' (imported from Prelude)"+ `shouldBe` ["is"]++ context "when error message was produced by GHC 8.0.1" $ do+ it "extracts a variable name of variable that is not in scope from an error message" $ do+ parseNotInScope . build $ do+ "<interactive>:1:1: error: Variable not in scope: x"+ `shouldBe` ["x"]++ it "ignores duplicates" $ do+ parseNotInScope . build $ do+ "<interactive>:1:1: error: Variable not in scope: x :: ()"+ ""+ "<interactive>:1:6: error: Variable not in scope: x :: ()"+ `shouldBe` ["x"]++ it "works for variable names that contain a prime" $ do+ parseNotInScope . build $ do+ "<interactive>:1:1: error: Variable not in scope: x' :: ()"+ ""+ "<interactive>:1:7: error: Variable not in scope: y'' :: ()"+ `shouldBe` ["x'", "y''"]++ it "works for error messages with suggestions" $ do+ parseNotInScope . build $ do+ "<interactive>:1:1: error:"+ " • Variable not in scope: length_"+ " • Perhaps you meant ‘length’ (imported from Prelude)"+ `shouldBe` ["length_"]
+ test/RunSpec.hs view
@@ -0,0 +1,187 @@+{-# LANGUAGE CPP #-}+module RunSpec (main, spec) where++import Imports++import Test.Hspec+import System.Exit++import qualified Control.Exception as E+import System.FilePath+import System.Directory (getCurrentDirectory, setCurrentDirectory)+import System.IO.Temp (withSystemTempDirectory)+import Data.List (isPrefixOf, sort)++import System.IO.Silently+import System.IO (stderr)+import qualified Options++import Run++withCurrentDirectory :: FilePath -> IO a -> IO a+withCurrentDirectory workingDir action = do+ E.bracket getCurrentDirectory setCurrentDirectory $ \_ -> do+ setCurrentDirectory workingDir+ action++main :: IO ()+main = hspec spec+++removeLoadedPackageEnvironment :: String -> String+#if __GLASGOW_HASKELL__ < 810+removeLoadedPackageEnvironment = unlines . filter (not . isPrefixOf "Loaded package environment from ") . lines+#else+removeLoadedPackageEnvironment = id+#endif++verboseFibOutput :: String+verboseFibOutput = unlines [+ "### Started execution at test/integration/testSimple/Fib.hs:5."+ , "### example:"+ , "fib 10"+ , "### Successful!"+ , ""+ , "# Final summary:"+ , "Examples: 1 Tried: 1 Errors: 0 Failures: 0"+ ]++spec :: Spec+spec = do+ describe "doctest" $ do+ it "exits with ExitFailure if at least one test case fails" $ do+ hSilence [stderr] (doctest ["test/integration/failing/Foo.hs"]) `shouldThrow` (== ExitFailure 1)++ it "prints help on --help" $ do+ (r, ()) <- capture (doctest ["--help"])+ r `shouldBe` Options.usage++ it "prints version on --version" $ do+ (r, ()) <- capture (doctest ["--version"])+ lines r `shouldSatisfy` any (isPrefixOf "doctest version ")++ it "accepts arbitrary GHC options" $ do+ hSilence [stderr] $ doctest ["-cpp", "-DFOO", "test/integration/test-options/Foo.hs"]++ it "accepts GHC options with --optghc" $ do+ hSilence [stderr] $ doctest ["--optghc=-cpp", "--optghc=-DFOO", "test/integration/test-options/Foo.hs"]++ it "prints a deprecation message for --optghc" $ do+ (r, _) <- hCapture [stderr] $ doctest ["--optghc=-cpp", "--optghc=-DFOO", "test/integration/test-options/Foo.hs"]+ lines r `shouldSatisfy` isPrefixOf [+ "WARNING: --optghc is deprecated, doctest now accepts arbitrary GHC options"+ , "directly."+ ]++ it "prints error message on invalid option" $ do+ (r, e) <- hCapture [stderr] . E.try $ doctest ["--foo", "test/integration/test-options/Foo.hs"]+ e `shouldBe` Left (ExitFailure 1)+ removeLoadedPackageEnvironment r `shouldBe` unlines [+ "doctest: unrecognized option `--foo'"+ , "Try `doctest --help' for more information."+ ]++ it "interprets GHC response files" $ do+ withSystemTempDirectory "hspec" $ \ dir -> do+ let file = dir </> "response-file"+ writeFile file $ unlines [+ "test/integration/testSimple/Fib.hs"+ ]+ (r, ()) <- hCapture [stderr] $ doctest ["--verbose", '@':file]+ removeLoadedPackageEnvironment r `shouldBe` verboseFibOutput++ it "prints verbose description of a specification" $ do+ (r, ()) <- hCapture [stderr] $ doctest ["--verbose", "test/integration/testSimple/Fib.hs"]+ removeLoadedPackageEnvironment r `shouldBe` verboseFibOutput++ it "prints verbose description of a property" $ do+ (r, ()) <- hCapture [stderr] $ doctest ["--verbose", "test/integration/property-bool/Foo.hs"]+ removeLoadedPackageEnvironment r `shouldBe` unlines [+ "### Started execution at test/integration/property-bool/Foo.hs:4."+ , "### property:"+ , "True"+ , "### Successful!"+ , ""+ , "# Final summary:"+ , "Examples: 1 Tried: 1 Errors: 0 Failures: 0"+ ]++ it "prints verbose error" $ do+ (r, e) <- hCapture [stderr] . E.try $ doctest ["--verbose", "test/integration/failing/Foo.hs"]+ e `shouldBe` Left (ExitFailure 1)+ removeLoadedPackageEnvironment r `shouldBe` unlines [+ "### Started execution at test/integration/failing/Foo.hs:5."+ , "### example:"+ , "23"+ , "test/integration/failing/Foo.hs:5: failure in expression `23'"+ , "expected: 42"+ , " but got: 23"+ , " ^"+ , ""+ , "# Final summary:"+ , "Examples: 1 Tried: 1 Errors: 0 Failures: 1"+ ]++#if __GLASGOW_HASKELL__ >= 802+ it "can deal with potentially problematic GHC options" $ do+ hSilence [stderr] $ doctest ["-fdiagnostics-color=always", "test/integration/color/Foo.hs"]+#endif++ describe "doctestWithResult" $ do+ context "on parse error" $ do+ let+ action :: IO Result+ action = withCurrentDirectory "test/integration/parse-error" $ do+ doctestWithResult defaultConfig {+ ghcOptions = [+ "Foo.hs"++ -- This is necessary due to:+ --+ -- https://gitlab.haskell.org/ghc/ghc/-/commit/88f38b03025386f0f1e8f5861eed67d80495168a+ --+ -- It will be fixed by:+ --+ -- https://gitlab.haskell.org/ghc/ghc/-/merge_requests/15995+ --+ , "-fdiagnostics-color=never"+#if __GLASGOW_HASKELL__ >= 910+ , "-fprint-error-index-links=never"+#endif+ ]+ }++ it "aborts with (ExitFailure 1)" $ do+ hSilence [stderr] action `shouldThrow` (== ExitFailure 1)++ it "prints a useful error message" $ do+ (r, _) <- hCapture [stderr] (E.try action :: IO (Either ExitCode Summary))+ removeLoadedPackageEnvironment r `shouldBe` unlines (+#if __GLASGOW_HASKELL__ < 910+ "" :+#endif+#if __GLASGOW_HASKELL__ >= 906+ [ "Foo.hs:6:1: error: [GHC-58481]"+#else+ [ "Foo.hs:6:1: error:"+#endif+ , " parse error (possibly incorrect indentation or mismatched brackets)"+#if __GLASGOW_HASKELL__ >= 910+ , ""+#endif+ ])++ describe "expandDirs" $ do+ it "expands a directory" $ do+ res <- expandDirs "example"+ sort res `shouldBe`+ [ "example" </> "src" </> "Example.hs"+ , "example" </> "test" </> "doctests.hs"+ ]+ it "ignores files" $ do+ res <- expandDirs "doctest.cabal"+ res `shouldBe` ["doctest.cabal"]+ it "ignores random things" $ do+ let x = "foo bar baz bin"+ res <- expandDirs x+ res `shouldBe` [x]
+ test/Runner/ExampleSpec.hs view
@@ -0,0 +1,162 @@+{-# LANGUAGE OverloadedStrings #-}+module Runner.ExampleSpec (main, spec) where++import Imports++import Data.String+import Test.Hspec+import Test.Hspec.Core.QuickCheck (modifyMaxSize)+import Test.QuickCheck++import Parse+import Runner.Example++main :: IO ()+main = hspec spec++data Line = PlainLine String | WildCardLines [String]+ deriving (Show, Eq)++instance Arbitrary Line where+ arbitrary = frequency [ (2, PlainLine <$> arbitrary)+ , (1, WildCardLines . getNonEmpty <$> arbitrary)+ ]++lineToExpected :: [Line] -> ExpectedResult+lineToExpected = map $ \x -> case x of+ PlainLine str -> fromString str+ WildCardLines _ -> WildCardLine++lineToActual :: [Line] -> [String]+lineToActual = concatMap $ \x -> case x of+ PlainLine str -> [str]+ WildCardLines xs -> xs++spec :: Spec+spec = do+ describe "mkResult" $ do+ it "returns Equal when output matches" $ do+ property $ \xs -> do+ mkResult (map fromString xs) xs `shouldBe` Equal++ it "ignores trailing whitespace" $ do+ mkResult ["foo\t"] ["foo "] `shouldBe` Equal++ context "with WildCardLine" $ do+ it "matches zero lines" $ do+ mkResult ["foo", WildCardLine, "bar"] ["foo", "bar"]+ `shouldBe` Equal++ it "matches first zero line" $ do+ mkResult [WildCardLine, "foo", "bar"] ["foo", "bar"]+ `shouldBe` Equal++ it "matches final zero line" $ do+ mkResult ["foo", "bar", WildCardLine] ["foo", "bar"]+ `shouldBe` Equal++ it "matches an arbitrary number of lines" $ do+ mkResult ["foo", WildCardLine, "bar"] ["foo", "baz", "bazoom", "bar"]+ `shouldBe` Equal++ -- See https://github.com/sol/doctest/issues/259+ modifyMaxSize (const 8) $+ it "matches an arbitrary number of lines (quickcheck)" $ do+ property $ \xs -> mkResult (lineToExpected xs) (lineToActual xs)+ `shouldBe` Equal++ context "with WildCardChunk" $ do+ it "matches an arbitrary line chunk" $ do+ mkResult [ExpectedLine ["foo", WildCardChunk, "bar"]] ["foo baz bar"]+ `shouldBe` Equal++ it "matches an arbitrary line chunk at end" $ do+ mkResult [ExpectedLine ["foo", WildCardChunk]] ["foo baz bar"]+ `shouldBe` Equal++ it "does not match at end" $ do+ mkResult [ExpectedLine [WildCardChunk, "baz"]] ["foo baz bar"]+ `shouldBe` NotEqual [+ "expected: ...baz"+ , " but got: foo baz bar"+ , " ^"+ ]++ it "does not match at start" $ do+ mkResult [ExpectedLine ["fuu", WildCardChunk]] ["foo baz bar"]+ `shouldBe` NotEqual [+ "expected: fuu..."+ , " but got: foo baz bar"+ , " ^"+ ]++ context "when output does not match" $ do+ it "constructs failure message" $ do+ mkResult ["foo"] ["bar"] `shouldBe` NotEqual [+ "expected: foo"+ , " but got: bar"+ , " ^"+ ]++ it "constructs failure message for multi-line output" $ do+ mkResult ["foo", "bar"] ["foo", "baz"] `shouldBe` NotEqual [+ "expected: foo"+ , " bar"+ , " but got: foo"+ , " baz"+ , " ^"+ ]++ context "when any output line contains \"unsafe\" characters" $ do+ it "uses show to format output lines" $ do+ mkResult ["foo\160bar"] ["foo bar"] `shouldBe` NotEqual [+ "expected: foo\\160bar"+ , " but got: foo bar"+ , " ^"+ ]++ it "insert caret after last matching character on different lengths" $ do+ mkResult ["foo"] ["fo"] `shouldBe` NotEqual [+ "expected: foo"+ , " but got: fo"+ , " ^"+ ]++ it "insert caret after mismatching line for multi-line output" $ do+ mkResult ["foo", "bar", "bat"] ["foo", "baz", "bax"] `shouldBe` NotEqual [+ "expected: foo"+ , " bar"+ , " bat"+ , " but got: foo"+ , " baz"+ , " ^"+ , " bax"+ ]++ it "insert caret after mismatching line with the longest match for multi-line wildcard pattern" $ do+ mkResult ["foo", WildCardLine, "bar", "bat"] ["foo", "xxx", "yyy", "baz", "bxx"] `shouldBe` NotEqual [+ "expected: foo"+ , " ..."+ , " bar"+ , " bat"+ , " but got: foo"+ , " xxx"+ , " yyy"+ , " baz"+ , " ^"+ , " bxx"+ ]++ it "insert caret after longest match for wildcard" $ do+ mkResult [ExpectedLine ["foo ", WildCardChunk, " bar bat"]] ["foo xxx yyy baz bxx"] `shouldBe` NotEqual [+ "expected: foo ... bar bat"+ , " but got: foo xxx yyy baz bxx"+ , " ^"+ ]++ it "show expanded pattern for long matches" $ do+ mkResult [ExpectedLine ["foo ", WildCardChunk, " bar bat"]] ["foo 123456789 123456789 xxx yyy baz bxx"] `shouldBe` NotEqual [+ "expected: foo ... bar bat"+ , " but got: foo 123456789 123456789 xxx yyy baz bxx"+ , " foo ........................... ba^"+ ]
+ test/RunnerSpec.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE CPP, OverloadedStrings #-}+module RunnerSpec (spec) where++import Imports++import Test.Hspec++import Data.IORef+import System.IO+import System.IO.Silently (hCapture_)+import Runner++capture :: Interactive -> Report () -> IO String+capture interactive action = do+ ref <- newIORef mempty+ hCapture_ [stderr] (runReport (ReportState interactive NoFailFast NonVerbose ref) action)++spec :: Spec+spec = do+ describe "report" $ do+ context "when mode is interactive" $ do+ it "writes to stderr" $ do+ capture Interactive $ do+ report "foobar"+ `shouldReturn` "foobar\n"++ context "when mode is non-interactive" $ do+ it "writes to stderr" $ do+ capture NonInteractive $ do+ report "foobar"+ `shouldReturn` "foobar\n"++ describe "report_" $ do+ context "when mode is interactive" $ do+ it "writes transient output to stderr" $ do+ capture Interactive $ do+ reportTransient "foobar"+ `shouldReturn` "foobar\r \r"++ context "when mode is non-interactive" $ do+ it "is ignored" $ do+ capture NonInteractive $ do+ reportTransient "foobar"+ `shouldReturn` ""
+ test/UtilSpec.hs view
@@ -0,0 +1,23 @@+module UtilSpec (main, spec) where++import Imports++import Test.Hspec++import Util++main :: IO ()+main = hspec spec++spec :: Spec+spec = do+ describe "convertDosLineEndings" $ do+ it "converts CRLF to LF" $ do+ convertDosLineEndings "foo\r\nbar\r\nbaz" `shouldBe` "foo\nbar\nbaz"++ it "strips a trailing CR" $ do+ convertDosLineEndings "foo\r" `shouldBe` "foo"++ describe "takeWhileEnd" $ do+ it "returns the longest suffix of elements that satisfy a given predicate" $ do+ takeWhileEnd (/= ' ') "foo bar" `shouldBe` "bar"
− test/doctests.hs
@@ -1,13 +0,0 @@-module Main where--import Test.DocTest--main :: IO ()-main = doctest [- "-packageghc"- , "-isrc"- , "-idist/build/autogen/"- , "-optP-include"- , "-optPdist/build/autogen/cabal_macros.h"- , "src/Run.hs"- ]
+ test/extract/argument-list/Foo.hs view
@@ -0,0 +1,6 @@+module Foo where++foo :: Int -- ^ doc for arg1+ -> Int -- ^ doc for arg2+ -> Int+foo = undefined
+ test/extract/comment-order/Foo.hs view
@@ -0,0 +1,28 @@+-- | module header+module Foo (++-- * some heading+-- | export list 1+ foo++-- * some other heading+-- | export list 2+, bar++-- * one more heading+-- $foo+, baz+) where++-- | foo+foo :: Int+foo = 23++-- $foo named chunk++-- | bar+bar :: Int+bar = 23++baz :: Int+baz = 23
+ test/extract/declaration/Foo.hs view
@@ -0,0 +1,5 @@+module Foo where++-- | Some documentation+foo :: Int+foo = 23
+ test/extract/dos-line-endings/Foo.hs view
@@ -0,0 +1,8 @@+module Foo where + +-- | +-- foo +-- bar +-- baz +foo :: Int +foo = 23
+ test/extract/export-list/Foo.hs view
@@ -0,0 +1,12 @@+module Foo (+-- * some heading+-- | documentation from export list+ foo+, bar+) where++foo :: Int+foo = 23++bar :: Int+bar = 23
+ test/extract/imported-module/Bar.hs view
@@ -0,0 +1,7 @@+module Bar where++import Baz++-- | documentation for bar+bar :: Int+bar = 23
+ test/extract/imported-module/Baz.hs view
@@ -0,0 +1,5 @@+module Baz where++-- | documentation for baz+baz :: Int+baz = 23
+ test/extract/module-header/Foo.hs view
@@ -0,0 +1,5 @@+-- | Some documentation+module Foo where++foo :: Int+foo = 23
+ test/extract/named-chunks/Foo.hs view
@@ -0,0 +1,15 @@+module Foo (+ foo+, bar+) where++-- $foo named chunk foo++-- $bar+-- named chunk bar++foo :: Int+foo = 23++bar :: Int+bar = 23
+ test/extract/regression/Fixity.hs view
@@ -0,0 +1,4 @@+module Fixity where++foo :: Int+foo = 23 + 42
+ test/extract/regression/ForeignImport.hs view
@@ -0,0 +1,10 @@+{-# LANGUAGE ForeignFunctionInterface #-}+module ForeignImport where+import Foreign.C++import Prelude hiding (sin)+ +-- pure function+foreign import ccall "sin" c_sin :: CDouble -> CDouble+sin :: Double -> Double+sin d = realToFrac (c_sin (realToFrac d))
+ test/extract/regression/ParallelListComp.hs view
@@ -0,0 +1,5 @@+{-# LANGUAGE ParallelListComp #-}+module ParallelListComp where++foo :: [Int]+foo = [x+y | x <- [1,2,3] | y <- [4,5,6]]
+ test/extract/regression/ParallelListCompClass.hs view
@@ -0,0 +1,8 @@+{-# LANGUAGE ParallelListComp #-}+module ParallelListCompClass where++class Foo a where+ foo :: a -> [Int]++instance Foo Int where+ foo _ = [x+y | x <- [1,2,3] | y <- [4,5,6]]
+ test/extract/regression/RewriteRules.hs view
@@ -0,0 +1,7 @@+module RewriteRules (foo) where++{-# RULES "map/append" forall f xs ys. map f (xs ++ ys) = map f xs ++ map f ys #-}++-- | doc for foo+foo :: Int+foo = 23
+ test/extract/regression/RewriteRulesWithSigs.hs view
@@ -0,0 +1,7 @@+module RewriteRulesWithSigs (foo) where++{-# RULES "map/append" forall f (xs :: [Int]) ys. map f (xs ++ ys) = map f xs ++ map f ys #-}++-- | doc for foo+foo :: Int+foo = 23
+ test/extract/setup/Foo.hs view
@@ -0,0 +1,16 @@+module Foo where++-- $setup+-- some setup code++-- | foo+foo :: Int+foo = 42++-- | bar+bar :: Int+bar = 42++-- | baz+baz :: Int+baz = 42
+ test/extract/th/Bar.hs view
@@ -0,0 +1,8 @@+{-# LANGUAGE TemplateHaskell #-}++module Bar where++import Language.Haskell.TH.Lib (ExpQ)++bar :: ExpQ+bar = [| 23 |]
+ test/extract/th/Foo.hs view
@@ -0,0 +1,8 @@+{-# LANGUAGE TemplateHaskell #-}+module Foo where++import Bar++-- | some documentation+foo :: Int+foo = $(bar)
+ test/extract/type-class-args/Foo.hs view
@@ -0,0 +1,8 @@+module Foo where+++class Foo a where++ bar :: a -- ^ foo+ -> Int -- ^ bar+ -> String
+ test/extract/type-class/Foo.hs view
@@ -0,0 +1,7 @@+module Foo where+++class ToString a where++ -- | Convert given value to a string.+ toString :: a -> String
+ test/extract/type-families/Foo.hs view
@@ -0,0 +1,6 @@+{-# LANGUAGE TypeFamilies #-}+module Foo where++type family Foo a++type instance Foo Int = Int
+ test/integration/bugfixImportHierarchical/ModuleA.hs view
@@ -0,0 +1,6 @@+-- |+-- >>> fib 10+-- 55+module ModuleA where++import Foo.ModuleB
+ test/integration/bugfixImportHierarchical/ModuleB.hs view
@@ -0,0 +1,12 @@+module Foo.ModuleB (fib) where+++-- |+-- >>> fib 10+-- 55+-- >>> fib 5+-- 5+fib :: Integer -> Integer+fib 0 = 0+fib 1 = 1+fib n = fib (n - 1) + fib (n - 2)
+ test/integration/bugfixMultipleModules/ModuleA.hs view
@@ -0,0 +1,6 @@+-- |+-- >>> fib 10+-- 55+module ModuleA where++import ModuleB
+ test/integration/bugfixMultipleModules/ModuleB.hs view
@@ -0,0 +1,20 @@+module ModuleB (fib) where+++-- |+-- >>> fib 10+-- 55+-- >>> fib 5+-- 5+fib :: Integer -> Integer+fib = foo++-- |+-- >>> foo 10+-- 55+-- >>> foo 5+-- 5+foo :: Integer -> Integer+foo 0 = 0+foo 1 = 1+foo n = foo (n - 1) + foo (n - 2)
+ test/integration/bugfixOutputToStdErr/Fib.hs view
@@ -0,0 +1,9 @@+module Fib where++-- | Calculate Fibonacci number of given 'Num'.+--+-- >>> import System.IO+-- >>> hPutStrLn stderr "foobar"+-- foobar+fib :: (Num t, Num t1) => t -> t1+fib _ = undefined
+ test/integration/bugfixWorkingDirectory/Fib.hs view
@@ -0,0 +1,10 @@+module Fib where++-- | Calculate Fibonacci number of given 'Num'.+--+-- >>> bar+-- 10+fib :: (Num t, Num t1) => t -> t1+fib _ = undefined++bar = 10
+ test/integration/bugfixWorkingDirectory/description view
@@ -0,0 +1,10 @@+Put the following files in the current working directory:++ ./Fib.hs+ ./examples/Fib.hs++Now run:++ doctest examples/Fib.hs++Erroneously `./Fib.hs` will be tested instead of `examples/Fib.hs`.
+ test/integration/bugfixWorkingDirectory/examples/Fib.hs view
@@ -0,0 +1,22 @@+module Fib where+++-- | Calculate Fibonacci number of given 'Num'.+--+-- Examples:+--+-- >>> fib 10+-- 55+fib :: Integer -> Integer+fib 0 = 0+fib 1 = 1+fib n = fib (n - 1) + fib (n - 2)++-- |+--+-- Examples:+--+-- >>> fib 10+-- 55+foo :: Int -> Int+foo = undefined
+ test/integration/color/Foo.hs view
@@ -0,0 +1,8 @@+module Foo where++import Data.Maybe++-- | Convert a map into list array.+-- prop> tabulate m !! fromEnum d == fromMaybe 0 (lookup d m)+tabulate :: [(Bool, Double)] -> [Double]+tabulate m = [fromMaybe 0 $ lookup False m, fromMaybe 0 $ lookup True m]
+ test/integration/custom-package-conf/Bar.hs view
@@ -0,0 +1,10 @@+module Bar where++import Foo++-- |+-- >>> import Foo+-- >>> foo+-- 23+bar :: Int+bar = 42
+ test/integration/custom-package-conf/foo/Foo.hs view
@@ -0,0 +1,3 @@+module Foo where+foo :: Int+foo = 23
+ test/integration/custom-package-conf/foo/doctest-foo.cabal view
@@ -0,0 +1,8 @@+name: doctest-foo+version: 0.0.0+build-type: Simple+cabal-version: >= 1.8++library+ exposed-modules: Foo+ build-depends: base
+ test/integration/dos-line-endings/Fib.hs view
@@ -0,0 +1,10 @@+module Fib where + +-- | Calculate Fibonacci numbers. +-- +-- >>> fib 10 +-- 55 +fib :: Integer -> Integer +fib 0 = 0 +fib 1 = 1 +fib n = fib (n - 1) + fib (n - 2)
+ test/integration/fail-fast/Bar.hs view
@@ -0,0 +1,8 @@+module Bar where++-- | bar+-- a passing test+-- >>> bar+-- 42+bar :: Int+bar = 42
+ test/integration/fail-fast/Foo.hs view
@@ -0,0 +1,24 @@+module Foo where++import Bar++-- | A passing example+--+-- >>> 23+-- 23+test1 :: a+test1 = undefined++-- | A failing example+--+-- >>> 23+-- 42+test2 :: a+test2 = undefined++-- | Another passing example+--+-- >>> 23+-- 23+test3 :: a+test3 = undefined
+ test/integration/fail-fast/SetupBar.hs view
@@ -0,0 +1,12 @@+module SetupBar where++-- $setup+-- >>> 23+-- 23++-- | bar+-- a passing test+-- >>> bar+-- 42+bar :: Int+bar = 42
+ test/integration/fail-fast/SetupFoo.hs view
@@ -0,0 +1,29 @@+module SetupFoo where++import SetupBar+++-- $setup+-- >>> 24+-- 23++-- | A passing example+--+-- >>> 23+-- 23+test1 :: a+test1 = undefined++-- | A failing example+--+-- >>> 23+-- 42+test2 :: a+test2 = undefined++-- | Another passing example+--+-- >>> 23+-- 23+test3 :: a+test3 = undefined
+ test/integration/failing-multiple/Foo.hs view
@@ -0,0 +1,16 @@+module Foo where++-- | A failing example+--+-- >>> 23+-- 23+--+-- >>> 23+-- 42+--+-- >>> 23+-- 23+-- >>> 23+-- 23+test :: a+test = undefined
+ test/integration/failing/Foo.hs view
@@ -0,0 +1,8 @@+module Foo where++-- | A failing example+--+-- >>> 23+-- 42+test :: a+test = undefined
+ test/integration/it/Foo.hs view
@@ -0,0 +1,20 @@+module Foo where++-- |+--+-- >>> :t 'a'+-- 'a' :: Char+--+-- >>> "foo"+-- "foo"+--+-- >>> length it+-- 3+--+-- >>> it * it+-- 9+--+-- >>> :t it+-- it :: Int+--+foo = undefined
+ test/integration/it/Setup.hs view
@@ -0,0 +1,23 @@+module Setup where++-- $setup+-- >>> :t 'a'+-- 'a' :: Char+--+-- >>> 42 :: Int+-- 42+--+-- >>> it+-- 42++-- |+--+-- >>> it * it+-- 1764+foo = undefined++-- |+--+-- >>> it * it+-- 1764+bar = undefined
+ test/integration/local-stderr-binding/A.hs view
@@ -0,0 +1,11 @@+module A where++stderr :: Bool+stderr = True++stdout :: String+stdout = "hello"++-- |+-- >>> 3 + 3+-- 6
+ test/integration/multiline/Multiline.hs view
@@ -0,0 +1,61 @@+module Multiline where+++{- |++>>> :{+ let+ x = 1+ y = z+ in x + y+ :}+ 3++-}+z = 2++{- |++Aligns with the closing++>>> :{+ let+ x = 1+ y = z+ in x + y+ :}+ 3+-}+z2 = 2+++{- | Also works let that's for do:++>>> :{+let+ x = 1+ y = z+:}++>>> y+2++-}+z3 = 2++++{- | Handles repeated @>>>@ too, which is bad since haddock-2.13.2 currently +will strip the leading whitespace leading to something that will not copy-paste+(unless it uses explicit { ; } and the users manually strip the @>>>@)++>>> :{+>>> let+>>> x = 1+>>> y = z+>>> in x + y+>>> :}+3++-}+z4 = 4
+ test/integration/parse-error/Foo.hs view
@@ -0,0 +1,5 @@+module Foo where++-- | Some documentation+foo :: Int+foo =
+ test/integration/property-bool-with-type-signature/Foo.hs view
@@ -0,0 +1,5 @@+module Foo where++-- |+-- prop> True :: Bool+foo = undefined
+ test/integration/property-bool/Foo.hs view
@@ -0,0 +1,5 @@+module Foo where++-- |+-- prop> True+foo = undefined
+ test/integration/property-failing/Foo.hs view
@@ -0,0 +1,5 @@+module Foo where++-- |+-- prop> abs x == x+foo = undefined
+ test/integration/property-implicitly-quantified/Foo.hs view
@@ -0,0 +1,5 @@+module Foo where++-- |+-- prop> abs x == abs (abs x)+foo = undefined
+ test/integration/property-quantified/Foo.hs view
@@ -0,0 +1,5 @@+module Foo where++-- |+-- prop> \x -> abs x == abs (abs x)+foo = undefined
+ test/integration/property-setup/Foo.hs view
@@ -0,0 +1,9 @@+module Foo where++-- $setup+-- >>> import Test.QuickCheck+-- >>> let arbitraryEven = (* 2) `fmap` arbitrary++-- |+-- prop> forAll arbitraryEven even+foo = undefined
+ test/integration/setup-skip-on-failure/Foo.hs view
@@ -0,0 +1,17 @@+module Foo where++-- $setup+-- >>> x+-- 23++-- |+-- >>> foo+-- 42+foo :: Int+foo = 42++-- |+-- >>> y+-- 42+bar :: Int+bar = 42
+ test/integration/setup/Foo.hs view
@@ -0,0 +1,10 @@+module Foo where++-- $setup+-- >>> let x = 23 :: Int++-- |+-- >>> x + foo+-- 65+foo :: Int+foo = 42
+ test/integration/system-io-imported/A.hs view
@@ -0,0 +1,10 @@+module A where++import System.IO++-- ghci-wrapper needs to poke around with System.IO itself, and unloads the module once it's done. Test to make sure legitimate uses of System.IO don't get lost in the wash.++-- |+-- >>> ReadMode+-- ReadMode+
+ test/integration/template-haskell-bugfix/Main.hs view
@@ -0,0 +1,10 @@+{-# LANGUAGE TemplateHaskell #-}+module Main where++-- Import our template "pr"+import Printf ( pr )++-- The splice operator $ takes the Haskell source code+-- generated at compile time by "pr" and splices it into+-- the argument of "putStrLn".+main = putStrLn ( $(pr "Hello") )
+ test/integration/template-haskell-bugfix/Printf.hs view
@@ -0,0 +1,25 @@+{-# LANGUAGE TemplateHaskell #-}+--+-- derived from: http://www.haskell.org/ghc/docs/latest/html/users_guide/template-haskell.html#th-example+--+module Printf (pr) where++import Language.Haskell.TH++data Format = D | S | L String++parse :: String -> [Format]+parse s = [ L s ]++gen :: [Format] -> Q Exp+gen [D] = [| \n -> show n |]+gen [S] = [| \s -> s |]+gen [L s] = stringE s++-- |+--+-- >>> :set -XTemplateHaskell+-- >>> putStrLn ( $(pr "Hello") )+-- Hello+pr :: String -> Q Exp+pr s = gen (parse s)
+ test/integration/template-haskell/Foo.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE TemplateHaskell #-}+module Foo where++import Language.Haskell.TH+import Text.Printf++-- | Report an error.+--+-- >>> :set -XTemplateHaskell+-- >>> $(logError "Something bad happened!")+-- ERROR <interactive>: Something bad happened!+logError :: String -> Q Exp+logError msg = do+ loc <- location+ let s = (printf "ERROR %s: %s" (loc_filename loc) msg) :: String+ [| putStrLn s |]
+ test/integration/test-options/Foo.hs view
@@ -0,0 +1,14 @@+module Foo where+++-- |+-- Examples:+--+-- >>> foo+-- 23+foo :: Int+#ifdef FOO+foo = 23+#else+foo = 42+#endif
+ test/integration/testBlankline/Fib.hs view
@@ -0,0 +1,10 @@+module Fib where++-- | Calculate Fibonacci number of given 'Num'.+--+-- >>> putStrLn "foo\n\nbar"+-- foo+-- <BLANKLINE>+-- bar+fib :: (Num t, Num t1) => t -> t1+fib _ = undefined
+ test/integration/testCPP/Foo.hs view
@@ -0,0 +1,14 @@+module Foo where+++-- |+-- Examples:+--+-- >>> foo+-- 23+foo :: Int+#ifdef FOO+foo = 23+#else+foo = 42+#endif
+ test/integration/testCombinedExample/Fib.hs view
@@ -0,0 +1,20 @@+module Fib where++-- | Calculate Fibonacci number of given 'Num'.+--+-- First let's set `n` to ten:+--+-- >>> let n = 10+--+-- And now calculate the 10th Fibonacci number:+--+-- >>> fib n+-- 55+--+-- >>> let x = 10+-- >>> x+-- 10+fib :: Integer -> Integer+fib 0 = 0+fib 1 = 1+fib n = fib (n - 1) + fib (n - 2)
+ test/integration/testCommentLocation/Foo.hs view
@@ -0,0 +1,79 @@+-- |+-- Examples in various locations...+--+-- Some random text. Some random text. Some random text. Some random text.+-- Some random text. Some random text. Some random text. Some random text.+-- Some random text.+--+-- >>> let x = 10+--+-- Some random text. Some random text. Some random text. Some random text.+-- Some random text. Some random text. Some random text. Some random text.+-- Some random text.+--+--+-- >>> baz+-- "foobar"++module Foo (+ -- | Some documentation not attached to a particular Haskell entity+ --+ -- >>> test 10+ -- *** Exception: Prelude.undefined+ -- ...+ test,++ -- |+ -- >>> fib 10+ -- 55+ fib,++ -- |+ -- >>> bar+ -- "bar"+ bar+ ) where+++-- | My test+--+-- >>> test 20+-- *** Exception: Prelude.undefined+-- ...+test :: Integer -> Integer+test = undefined++-- | Note that examples for 'fib' include the two examples below+-- and the one example with ^ syntax after 'fix'+--+-- >>> foo+-- "foo"++{- |+ Example:++ >>> fib 10+ 55+-}++-- | Calculate Fibonacci number of given `n`.+fib :: Integer -- ^ given `n`+ --+ -- >>> fib 10+ -- 55++ -> Integer -- ^ Fibonacci of given `n`+ --+ -- >>> baz+ -- "foobar"+fib 0 = 0+fib 1 = 1+fib n = fib (n - 1) + fib (n - 2)+-- ^ Example:+--+-- >>> fib 5+-- 5++foo = "foo"+bar = "bar"+baz = foo ++ bar
+ test/integration/testDocumentationForArguments/Fib.hs view
@@ -0,0 +1,7 @@+module Fib where++fib :: Int -- ^ + -- >>> 23+ -- 23+ -> Int+fib _ = undefined
+ test/integration/testFailOnMultiline/Fib.hs view
@@ -0,0 +1,13 @@+module Fib where++-- | The following interaction cause `doctest' to fail with an error:+--+-- >>> :{+foo :: Int+foo = 23++-- | The following interaction cause `doctest' to fail with an error:+--+-- >>> :{+bar :: Int+bar = 23
+ test/integration/testImport/ModuleA.hs view
@@ -0,0 +1,6 @@+-- |+-- >>> fib 10+-- 55+module ModuleA where++import ModuleB
+ test/integration/testImport/ModuleB.hs view
@@ -0,0 +1,12 @@+module ModuleB (fib) where+++-- |+-- >>> fib 10+-- 55+-- >>> fib 5+-- 5+fib :: Integer -> Integer+fib 0 = 0+fib 1 = 1+fib n = fib (n - 1) + fib (n - 2)
+ test/integration/testPutStr/Fib.hs view
@@ -0,0 +1,13 @@+module Fib where++-- | Calculate Fibonacci number of given 'Num'.+--+-- >>> putStrLn "foo"+-- foo+-- >>> putStr "bar"+-- bar+--+-- >>> putStrLn "baz"+-- baz+fib :: (Num t, Num t1) => t -> t1+fib _ = undefined
+ test/integration/testSimple/Fib.hs view
@@ -0,0 +1,10 @@+module Fib where++-- | Calculate Fibonacci numbers.+--+-- >>> fib 10+-- 55+fib :: Integer -> Integer+fib 0 = 0+fib 1 = 1+fib n = fib (n - 1) + fib (n - 2)
+ test/integration/trailing-whitespace/Foo.hs view
@@ -0,0 +1,8 @@+module Foo where++-- | A failing example+--+-- >>> putStrLn "foo "+-- foo+test :: a+test = undefined
+ test/integration/with-cbits/Bar.hs view
@@ -0,0 +1,9 @@+{-# LANGUAGE ForeignFunctionInterface #-}+module Bar where++import Foreign.C++-- |+-- >>> foo+-- 23+foreign import ccall foo :: CInt
+ test/parse/multiple-examples/Foo.hs view
@@ -0,0 +1,11 @@+module Foo where++-- |+-- >>> foo+-- 23+foo = 23++-- |+-- >>> bar+-- 42+bar = 42
+ test/parse/no-examples/Fib.hs view
@@ -0,0 +1,10 @@+module Fib where++-- | Calculate Fibonacci numbers.+-- @+-- some code+-- @+--+-- foobar 23+fib :: Int -> Int -> Int+fib _ = undefined
+ test/parse/non-exported/Fib.hs view
@@ -0,0 +1,16 @@+module Fib (foo) where++foo :: Int+foo = 23++-- | Calculate Fibonacci number of given 'Num'.+--+-- >>> putStrLn "foo"+-- foo+-- >>> putStr "bar"+-- bar+--+-- >>> putStrLn "baz"+-- baz+fib :: (Num t, Num t1) => t -> t1+fib _ = undefined
+ test/parse/property/Fib.hs view
@@ -0,0 +1,15 @@+module Fib where++-- | Calculate Fibonacci numbers.+--+-- prop> foo+--+-- some text+--+-- prop> bar+--+-- some more text+--+-- prop> baz+fib :: Int -> Int -> Int+fib _ = undefined
+ test/parse/setup-empty/Foo.hs view
@@ -0,0 +1,10 @@+module Foo where++-- $setup+-- some setup code++-- |+-- >>> foo+-- 23+foo :: Int+foo = 23
+ test/parse/setup-only/Foo.hs view
@@ -0,0 +1,9 @@+module Foo where++-- $setup+-- >>> foo+-- 23++-- | some documentation+foo :: Int+foo = 23
+ test/parse/simple/Fib.hs view
@@ -0,0 +1,13 @@+module Fib where++-- | Calculate Fibonacci numbers.+--+-- >>> putStrLn "foo"+-- foo+-- >>> putStr "bar"+-- bar+--+-- >>> putStrLn "baz"+-- baz+fib :: Int -> Int -> Int+fib _ = undefined