doctest 0.20.0 → 0.20.1
raw patch · 16 files changed
+629/−678 lines, 16 filesdep ~ghcPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: ghc
API changes (from Hackage documentation)
Files
- CHANGES.markdown +3/−0
- LICENSE +1/−1
- README.markdown +0/−479
- README.md +533/−0
- doctest.cabal +11/−9
- src/Extract.hs +48/−66
- src/GhcUtil.hs +3/−37
- src/Interpreter.hs +0/−3
- src/Location.hs +1/−11
- src/PackageDBs.hs +13/−33
- src/Parse.hs +0/−4
- src/Property.hs +0/−2
- src/Runner.hs +3/−8
- test/PropertySpec.hs +1/−8
- test/RunSpec.hs +12/−13
- test/RunnerSpec.hs +0/−4
CHANGES.markdown view
@@ -1,3 +1,6 @@+Changes in 0.20.1+ - GHC 9.4 compatibility. (#382)+ Changes in 0.20.0 - Allow doctest to be invoked via `cabal repl --with-ghc=doctest` - Include `ghc --info` output in `--info`
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2009-2018 Simon Hengel <sol@typeful.net>+Copyright (c) 2009-2022 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.markdown
@@ -1,479 +0,0 @@-# Doctest: Test interactive Haskell examples--`doctest` is a small program, that checks [examples in Haddock comments](http://www.haskell.org/haddock/doc/html/ch03s08.html#id566093). It is similar-to the [popular Python module with the same name](http://docs.python.org/library/doctest.html).---## Installation--`doctest` is available from-[Hackage](http://hackage.haskell.org/cgi-bin/hackage-scripts/package/doctest).-Install it, by typing:-- cabal install doctest--Make sure that Cabal's `bindir` is on your `PATH`.--On Linux:-- export PATH="$HOME/.cabal/bin:$PATH"--On Mac OS X:-- export PATH="$HOME/Library/Haskell/bin:$PATH"--On Windows:-- set PATH="%AppData%\cabal\bin\;%PATH%"--For more information, see the [section on paths in the Cabal User Guide](http://www.haskell.org/cabal/users-guide/installing-packages.html#paths-in-the-simple-build-system).--## Usage--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-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](http://en.wikipedia.org/wiki/Read-eval-print_loop) (e.g. ghci)-prints to `stdout` and `stderr` when evaluating that expression.)--With `doctest` you may check whether the implementation satisfies the given examples, by typing:-- doctest Fib.hs--You may produce Haddock documentation for that module with:-- haddock -h Fib.hs -o doc/--`doctest` will fail on comments that `haddock` also doesn't like.-Sometimes (e.g., [#251](https://github.com/sol/doctest/issues/251)), this means that `doctest` will fail on input that GHC accepts.--`doctest` likes UTF-8. If you are running it with, e.g., `LC_ALL=C`,-you may need to invoke `doctest` with `LC_ALL=C.UTF-8`.--### 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 not tried, because `let n = x + y` fails (`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://ghc.haskell.org/trac/ghc/ticket/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 (since version 2.13.0) 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](http://hackage.haskell.org/package/QuickCheck) is a dependency-of the test-suite or executable running `doctest`.--```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]: http://www.haskell.org/haddock/doc/html/ch03s05.html--### 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.-(Doctest will not look at your cabal file.)-2. 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--- |--- >>> :set -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--- $--- >>> :set -XTupleSections-```--[language-pragma]: http://www.haskell.org/ghc/docs/latest/html/users_guide/pragmas.html#language-pragma--### Limitations--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.---### Cabal integration--Doctest provides both, an executable and a library. The library exposes a-function `doctest` of type:--```haskell-doctest :: [String] -> IO ()-```--Doctest's own `main` is simply:--```haskell-main = getArgs >>= doctest-```--Consequently, it is possible to create a custom executable for a project, by-passing all command-line arguments that are required for that project to-`doctest`. A simple example looks like this:--```haskell--- file doctests.hs-import Test.DocTest-main = doctest ["-isrc", "src/Main.hs"]-```--And a corresponding Cabal test suite section like this:-- test-suite doctests- type: exitcode-stdio-1.0- ghc-options: -threaded- main-is: doctests.hs- build-depends: base, doctest >= 0.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)---## Doctest extensions-- * [doctest-discover](https://github.com/karun012/doctest-discover)--## Development [](http://travis-ci.org/sol/doctest)--Join in at `#hspec` on freenode.--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 configure --enable-tests && cabal build && cabal exec cabal test---## Contributors-- * Adam Vogt- * Alan Zimmerman- * Alexander Bernauer- * Alexandre Esteves- * Anders Persson- * Andreas Abel- * Ankit Ahuja- * Artyom Kazak- * Edward Kmett- * Gabor Greif- * Hiroki Hattori- * Ignat Insarov- * Jens Petersen- * Joachim Breitner- * John Chee- * João Cristóvão- * Julian Arni- * Kazu Yamamoto- * Leon Schoorl- * Levent Erkok- * Luke Murphy- * Matvey Aksenov- * Michael Orlitzky- * Michael Snoyman- * Mitchell Rosen- * Nick Smallbone- * Nikos Baxevanis- * Oleg Grenrus- * quasicomputational- * Ryan Scott- * Sakari Jokinen- * Simon Hengel- * Sönke Hahn- * Takano Akio- * Tamar Christina- * Veronika Romashkina--For up-to-date list, query-- git shortlog -s
+ README.md view
@@ -0,0 +1,533 @@+# Doctest: Test interactive Haskell examples++`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).++* [Getting started](#getting-started)+ * [Installation](#installation)+ * [A basic example](#a-basic-example)+ * [Running doctest for a Cabal package](#running-doctest-for-a-cabal-package)+* [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](http://hackage.haskell.org/cgi-bin/hackage-scripts/package/doctest).+Install it with:++ cabal update && cabal install doctest++Make sure that Cabal's `bindir` is on your `PATH`.++On Linux:++ export PATH="$HOME/.cabal/bin:$PATH"++On Mac OS X:++ export PATH="$HOME/Library/Haskell/bin:$PATH"++On Windows:++ set PATH="%AppData%\cabal\bin\;%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](http://en.wikipedia.org/wiki/Read-eval-print_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-ghc=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`:++```+$ cabal repl --with-ghc=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`.++- `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-ghc=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-ghc=doctest`).++So a more robust way to call `doctest` is as follows:++```+cabal install doctest --overwrite-policy=always && cabal build && cabal repl --build-depends=QuickCheck --build-depends=template-haskell --with-ghc=doctest+```++(This is what you want to use on CI.)+++# 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://ghc.haskell.org/trac/ghc/ticket/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](http://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]: http://www.haskell.org/haddock/doc/html/ch03s05.html++## 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+-- |+-- >>> :set -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+-- $+-- >>> :set -XTupleSections+```++[language-pragma]: https://downloads.haskell.org/~ghc/latest/docs/html/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 --enable-tests && cabal exec -- cabal test --test-show-details=direct+++# Contributors++ * Adam Vogt+ * Alan Zimmerman+ * Alexander Bernauer+ * Alexandre Esteves+ * Anders Persson+ * Andreas Abel+ * Ankit Ahuja+ * Artyom Kazak+ * Edward Kmett+ * Gabor Greif+ * Hiroki Hattori+ * Ignat Insarov+ * Jens Petersen+ * Joachim Breitner+ * John Chee+ * João Cristóvão+ * Julian Arni+ * Kazu Yamamoto+ * Leon Schoorl+ * Levent Erkok+ * Luke Murphy+ * Matvey Aksenov+ * Michael Orlitzky+ * Michael Snoyman+ * Mitchell Rosen+ * Nick Smallbone+ * Nikos Baxevanis+ * Oleg Grenrus+ * quasicomputational+ * Ryan Scott+ * Sakari Jokinen+ * Simon Hengel+ * Sönke Hahn+ * Takano Akio+ * Tamar Christina+ * Veronika Romashkina++For up-to-date list, query++ git shortlog -s
doctest.cabal view
@@ -1,14 +1,16 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.34.4.+-- This file has been generated from package.yaml by hpack version 0.35.0. -- -- see: https://github.com/sol/hpack name: doctest-version: 0.20.0+version: 0.20.1 synopsis: Test interactive Haskell examples-description: The doctest program checks examples in source code comments. It is modeled- after doctest for Python (<https://docs.python.org/3/library/doctest.html>).+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@@ -16,7 +18,7 @@ homepage: https://github.com/sol/doctest#readme license: MIT license-file: LICENSE-copyright: (c) 2009-2018 Simon Hengel+copyright: (c) 2009-2022 Simon Hengel author: Simon Hengel <sol@typeful.net> maintainer: quasicomputational@gmail.com, Andreas Abel build-type: Simple@@ -99,7 +101,7 @@ test/integration/with-cbits/Bar.hs test/integration/with-cbits/foo.c CHANGES.markdown- README.markdown+ README.md source-repository head type: git@@ -136,7 +138,7 @@ , directory , exceptions , filepath- , ghc >=7.0 && <9.3+ , ghc >=8.0 && <9.5 , ghc-paths >=0.1.0.9 , process , syb >=0.3@@ -159,7 +161,7 @@ , doctest , exceptions , filepath- , ghc >=7.0 && <9.3+ , ghc >=8.0 && <9.5 , ghc-paths >=0.1.0.9 , process , syb >=0.3@@ -220,7 +222,7 @@ , directory , exceptions , filepath- , ghc >=7.0 && <9.3+ , ghc >=8.0 && <9.5 , ghc-paths >=0.1.0.9 , hspec >=2.3.0 , hspec-core >=2.3.0
src/Extract.hs view
@@ -3,23 +3,14 @@ import Prelude hiding (mod, concat) import Control.Monad-#if __GLASGOW_HASKELL__ < 710-import Control.Applicative-#endif import Control.Exception 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)-#elif __GLASGOW_HASKELL__ < 900+#if __GLASGOW_HASKELL__ < 900 import GHC hiding (Module, Located) import DynFlags import MonadUtils (liftIO)@@ -41,11 +32,6 @@ import System.Directory import System.FilePath -#if __GLASGOW_HASKELL__ < 710-import NameSet (NameSet)-import Coercion (Coercion)-#endif- #if __GLASGOW_HASKELL__ < 805 import FastString (unpackFS) #endif@@ -137,15 +123,11 @@ 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@@ -181,11 +163,7 @@ _ <- setSessionDynFlags (GHC.ms_hspp_opts modsum) hsc_env <- getSession -# if __GLASGOW_HASKELL__ >= 903- hsc_env' <- liftIO (initializePlugins hsc_env Nothing)- setSession hsc_env'- return $ modsum-# elif __GLASGOW_HASKELL__ >= 901+# if __GLASGOW_HASKELL__ >= 902 hsc_env' <- liftIO (initializePlugins hsc_env) setSession hsc_env' return $ modsum@@ -225,6 +203,11 @@ (setup, docs) = partition isSetup (docStringsFromModule m) name = (moduleNameString . GHC.moduleName . ms_mod . pm_mod_summary) m +#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 unpackHDS)) docs@@ -232,66 +215,58 @@ 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__ >= 904+ header = [(Nothing, hsDocString <$> x) | Just x <- [hsmodHaddockModHeader source]]+#else header = [(Nothing, x) | Just x <- [hsmodHaddockModHeader source]]+#endif+ exports :: [(Maybe String, LHsDocString)]+#if __GLASGOW_HASKELL__ >= 904+ exports = [ (Nothing, L (locA loc) (hsDocString (unLoc doc)))+#else exports = [ (Nothing, L (locA loc) doc)-#if __GLASGOW_HASKELL__ < 710- | L loc (IEDoc doc) <- concat (hsmodExports source)-#elif __GLASGOW_HASKELL__ < 805+#endif+#if __GLASGOW_HASKELL__ < 805 | L loc (IEDoc doc) <- maybe [] unLoc (hsmodExports source) #else | L loc (IEDoc _ doc) <- maybe [] unLoc (hsmodExports source) #endif ]+ decls :: [(Maybe String, LHsDocString)] decls = extractDocStrings (hsmodDecls source) -type Selector a = a -> ([(Maybe String, LHsDocString)], Bool)--#if __GLASGOW_HASKELL__ < 710--- | Ignore a subtree.-ignore :: Selector a-ignore = const ([], True)-#endif---- | 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)-- -- 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))-- -- undefined before type checking- `extQ` (ignore :: Selector Coercion)+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))) -#if __GLASGOW_HASKELL__ >= 706- -- 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)))-#endif+ extractDocDocString :: LHsDoc GhcPs -> LHsDocString+ extractDocDocString = fmap hsDocString -#endif- )+ extractDocName :: DocDecl GhcPs -> Maybe (SrcSpan, String)+ extractDocName docDecl = case docDecl of+ DocCommentNamed name y ->+ Just (getLoc y, name)+ _ ->+ Nothing+#else+ everythingBut (++) (([], False) `mkQ` fromLHsDecl+ `extQ` fromLDocDecl+ `extQ` fromLHsDocString+ ) d where fromLHsDecl :: Selector (LHsDecl GhcPs) fromLHsDecl (L loc decl) = case decl of@@ -323,6 +298,13 @@ fromDocDecl loc x = case x of DocCommentNamed name doc -> (Just name, L loc doc) _ -> (Nothing, L loc $ docDeclDoc x)++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__ < 805 -- | Convert a docstring to a plain string.
src/GhcUtil.hs view
@@ -2,18 +2,12 @@ module GhcUtil (withGhc) where 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-#endif #if __GLASGOW_HASKELL__ < 900 import Panic (throwGhcException)@@ -29,50 +23,26 @@ 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)-#elif __GLASGOW_HASKELL__ < 801+#if __GLASGOW_HASKELL__ < 801 import StaticFlags (discardStaticFlags) #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)-#else-bracketStaticFlags action = action-#endif- -- 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+withGhc flags action = do flags_ <- handleStaticFlags flags runGhc (Just libdir) $ do handleDynamicFlags flags_ >>= handleSrcErrors . action handleStaticFlags :: [String] -> IO [Located String]-#if __GLASGOW_HASKELL__ < 707-handleStaticFlags flags = fst `fmap` parseStaticFlags (map noLoc flags)-#elif __GLASGOW_HASKELL__ < 801+#if __GLASGOW_HASKELL__ < 801 handleStaticFlags flags = return $ map noLoc $ discardStaticFlags flags #else handleStaticFlags flags = return $ map noLoc $ flags@@ -98,11 +68,7 @@ _ -> 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__ >= 901 backend = NoBackend #else
src/Interpreter.hs view
@@ -16,9 +16,6 @@ import System.Process import System.Directory (getPermissions, executable)-#if __GLASGOW_HASKELL__ < 710-import Control.Applicative-#endif import Control.Monad import Control.Exception hiding (handle) import Data.Char
src/Location.hs view
@@ -13,10 +13,6 @@ import GHC.Data.FastString (unpackFS) #endif -#if __GLASGOW_HASKELL__ < 702-import Outputable (showPpr)-#endif- -- | A thing with a location attached. data Located a = Located Location a deriving (Eq, Show, Functor)@@ -61,13 +57,7 @@ -- | 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-#elif __GLASGOW_HASKELL__ < 900+#if __GLASGOW_HASKELL__ < 900 toLocation loc = case loc of UnhelpfulSpan str -> UnhelpfulLocation (unpackFS str) RealSrcSpan sp -> Location (unpackFS . srcSpanFile $ sp) (srcSpanStartLine sp)
src/PackageDBs.hs view
@@ -1,14 +1,12 @@ {-# LANGUAGE CPP #-}-{-# LANGUAGE PatternGuards #-} -- | Manage GHC package databases-module PackageDBs- ( PackageDBs (..)- , ArgStyle (..)- , dbArgs- , buildArgStyle- , getPackageDBsFromEnv- , getPackageDBArgs- ) where+module PackageDBs (+ getPackageDBArgs+#ifdef TEST+, PackageDBs (..)+, getPackageDBsFromEnv+#endif+) where import System.Environment (getEnvironment) import System.FilePath (splitSearchPath, searchPathSeparator)@@ -16,42 +14,24 @@ -- | Full stack of GHC package databases data PackageDBs = PackageDBs { includeUser :: Bool- -- | Unsupported on GHC < 7.6 , includeGlobal :: Bool , extraDBs :: [FilePath] } deriving (Show, Eq) --- | Package database handling switched between GHC 7.4 and 7.6-data ArgStyle = Pre76 | Post76- deriving (Show, Eq)- -- | Determine command line arguments to be passed to GHC to set databases correctly ----- >>> dbArgs Post76 (PackageDBs False True [])+-- >>> dbArgs (PackageDBs False True []) -- ["-no-user-package-db"] ----- >>> dbArgs Pre76 (PackageDBs True True ["somedb"])--- ["-package-conf","somedb"]-dbArgs :: ArgStyle -> PackageDBs -> [String]-dbArgs Post76 (PackageDBs user global extras) =+-- >>> 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-dbArgs Pre76 (PackageDBs _ False _) =- error "Global package database must be included with GHC < 7.6"-dbArgs Pre76 (PackageDBs user True extras) =- (if user then id else ("-no-user-package-conf":)) $- concatMap (\extra -> ["-package-conf", extra]) extras --- | The argument style to be used with the current GHC version-buildArgStyle :: ArgStyle-#if __GLASGOW_HASKELL__ >= 706-buildArgStyle = Post76-#else-buildArgStyle = Pre76-#endif- -- | Determine the PackageDBs based on the environment. getPackageDBsFromEnv :: IO PackageDBs getPackageDBsFromEnv = do@@ -79,4 +59,4 @@ getPackageDBArgs :: IO [String] getPackageDBArgs = do dbs <- getPackageDBsFromEnv- return $ dbArgs buildArgStyle dbs+ return $ dbArgs dbs
src/Parse.hs view
@@ -1,6 +1,5 @@ {-# LANGUAGE PatternGuards #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE CPP #-} module Parse ( Module (..) , DocTest (..)@@ -21,9 +20,6 @@ import Data.List (isPrefixOf, stripPrefix) import Data.Maybe import Data.String-#if __GLASGOW_HASKELL__ < 710-import Control.Applicative-#endif import Extract import Location
src/Property.hs view
@@ -67,7 +67,5 @@ -- | 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/Runner.hs view
@@ -13,11 +13,6 @@ import Prelude hiding (putStr, putStrLn, error) -#if __GLASGOW_HASKELL__ < 710-import Data.Monoid hiding ((<>))-import Control.Applicative-#endif- import Control.Monad hiding (forM_) import Text.Printf (printf) import System.IO (hPutStrLn, hPutStr, stderr, hIsTerminalDevice)@@ -50,11 +45,11 @@ -- | Sum up summaries. instance Monoid Summary where mempty = Summary 0 0 0 0-#if MIN_VERSION_base(4,11,0)+#if __GLASGOW_HASKELL__ < 804+ mappend+#else instance Semigroup Summary where (<>)-#else- mappend #endif (Summary x1 x2 x3 x4) (Summary y1 y2 y3 y4) = Summary (x1 + y1) (x2 + y2) (x3 + y3) (x4 + y4)
test/PropertySpec.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE CPP, OverloadedStrings #-}+{-# LANGUAGE OverloadedStrings #-} module PropertySpec (main, spec) where import Test.Hspec@@ -30,17 +30,10 @@ runProperty repl "(reverse . reverse) xs == (xs :: [Int])" `shouldReturn` Success it "runs an implicitly quantified property even with GHC 7.4" $-#if __GLASGOW_HASKELL__ == 702- pendingWith "This triggers a bug in GHC 7.2.*."- -- try e.g.- -- >>> 23- -- >>> :t is-#else -- 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-#endif it "runs an explicitly quantified property" $ withInterpreter [] $ \repl -> do runProperty repl "\\xs -> (reverse . reverse) xs == (xs :: [Int])" `shouldReturn` Success
test/RunSpec.hs view
@@ -8,8 +8,10 @@ import System.Exit import qualified Control.Exception as E+import System.FilePath import System.Directory (getCurrentDirectory, setCurrentDirectory) import Data.List.Compat (isPrefixOf, sort)+import Data.Char import System.IO.Silently import System.IO (stderr)@@ -126,24 +128,14 @@ it "prints a useful error message" $ do (r, _) <- hCapture [stderr] (E.try action :: IO (Either ExitCode Summary))-#if __GLASGOW_HASKELL__ < 706- r `shouldBe` "\nFoo.hs:6:1: parse error (possibly incorrect indentation)\n"-#else--#if __GLASGOW_HASKELL__ < 800- r `shouldBe` "\nFoo.hs:6:1:\n parse error (possibly incorrect indentation or mismatched brackets)\n"-#else- removeLoadedPackageEnvironment r `shouldBe` "\nFoo.hs:6:1: error:\n parse error (possibly incorrect indentation or mismatched brackets)\n"-#endif--#endif+ stripAnsiColors (removeLoadedPackageEnvironment r) `shouldBe` "\nFoo.hs:6:1: error:\n parse error (possibly incorrect indentation or mismatched brackets)\n" describe "expandDirs" $ do it "expands a directory" $ do res <- expandDirs "example" sort res `shouldBe`- [ "example/src/Example.hs"- , "example/test/doctests.hs"+ [ "example" </> "src" </> "Example.hs"+ , "example" </> "test" </> "doctests.hs" ] it "ignores files" $ do res <- expandDirs "doctest.cabal"@@ -152,3 +144,10 @@ let x = "foo bar baz bin" res <- expandDirs x res `shouldBe` [x]++stripAnsiColors :: String -> String+stripAnsiColors xs = case xs of+ '\ESC' : '[' : ';' : ys | 'm' : zs <- dropWhile isNumber ys -> stripAnsiColors zs+ '\ESC' : '[' : ys | 'm' : zs <- dropWhile isNumber ys -> stripAnsiColors zs+ y : ys -> y : stripAnsiColors ys+ [] -> []
test/RunnerSpec.hs view
@@ -3,10 +3,6 @@ import Test.Hspec -#if __GLASGOW_HASKELL__ < 710-import Data.Monoid-#endif- import System.IO import System.IO.Silently (hCapture) import Control.Monad.Trans.State