diff --git a/CHANGES b/CHANGES
new file mode 100644
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,107 @@
+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
diff --git a/README.markdown b/README.markdown
new file mode 100644
--- /dev/null
+++ b/README.markdown
@@ -0,0 +1,423 @@
+# 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 it's `C:\Documents And Settings\user\Application Data\cabal\bin`.
+
+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 an
+[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/
+
+### 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 the x and y are 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
+
+### 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 [![Build Status](https://secure.travis-ci.org/sol/doctest.png)](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
+ * Anders Persson
+ * Ankit Ahuja
+ * Edward Kmett
+ * Hiroki Hattori
+ * Joachim Breitner
+ * João Cristóvão
+ * Julian Arni
+ * Kazu Yamamoto
+ * Levent Erkok
+ * Luke Murphy
+ * Matvey Aksenov
+ * Michael Orlitzky
+ * Michael Snoyman
+ * Nick Smallbone
+ * Sakari Jokinen
+ * Simon Hengel
+ * Sönke Hahn
diff --git a/doctest.cabal b/doctest.cabal
--- a/doctest.cabal
+++ b/doctest.cabal
@@ -1,121 +1,178 @@
-name:             doctest
-version:          0.13.0
-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-2017 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
+-- This file has been generated from package.yaml by hpack version 0.25.0.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: 963cf1ec79ce30b2c464e5e64a64f5b9f0cba107029eb8fdc5333229775060c9
 
+name:           doctest
+version:        0.14.0
+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-2017 Simon Hengel
+author:         Simon Hengel <sol@typeful.net>
+maintainer:     Simon Hengel <sol@typeful.net>
+build-type:     Simple
+cabal-version:  >= 1.10
+
+extra-source-files:
+    CHANGES
+    example/example.cabal
+    example/src/Example.hs
+    example/test/doctests.hs
+    README.markdown
+
 source-repository head
   type: git
   location: https://github.com/sol/doctest
 
 library
+  ghc-options: -Wall
+  hs-source-dirs:
+      src
+      ghci-wrapper/src
   exposed-modules:
       Test.DocTest
-  ghc-options:
-      -Wall
-  hs-source-dirs:
-      src, ghci-wrapper/src
   other-modules:
       Extract
-    , GhcUtil
-    , Interpreter
-    , Location
-    , Options
-    , PackageDBs
-    , Parse
-    , Paths_doctest
-    , Property
-    , Runner
-    , Runner.Example
-    , Run
-    , Util
-    , Sandbox
-    , Language.Haskell.GhciWrapper
+      GhcUtil
+      Interpreter
+      Location
+      Options
+      PackageDBs
+      Parse
+      Property
+      Run
+      Runner
+      Runner.Example
+      Sandbox
+      Util
+      Language.Haskell.GhciWrapper
+      Paths_doctest
   build-depends:
-      base          == 4.*
-    , base-compat   >= 0.7.0
-    , ghc           >= 7.0 && < 8.4
-    , syb           >= 0.3
-    , code-page     >= 0.1
+      base ==4.*
+    , base-compat >=0.7.0
+    , code-page >=0.1
     , deepseq
     , directory
     , filepath
+    , ghc >=7.0 && <8.6
+    , ghc-paths >=0.1.0.9
     , process
-    , ghc-paths     >= 0.1.0.9
+    , syb >=0.3
     , transformers
+  default-language: Haskell2010
 
 executable doctest
-  main-is:
-      Main.hs
-  ghc-options:
-      -Wall -threaded
+  main-is: Main.hs
+  other-modules:
+      Paths_doctest
+  ghc-options: -Wall -threaded
   hs-source-dirs:
       driver
   build-depends:
-      base          == 4.*
+      base ==4.*
+    , base-compat >=0.7.0
+    , code-page >=0.1
+    , deepseq
+    , directory
     , doctest
+    , filepath
+    , ghc >=7.0 && <8.6
+    , ghc-paths >=0.1.0.9
+    , process
+    , syb >=0.3
+    , transformers
+  default-language: Haskell2010
 
-test-suite spec
-  main-is:
-      Spec.hs
-  type:
-      exitcode-stdio-1.0
-  ghc-options:
-      -Wall -threaded
-  cpp-options:
-      -DTEST
+test-suite doctests
+  main-is: doctests.hs
+  type: exitcode-stdio-1.0
+  ghc-options: -Wall -threaded
   hs-source-dirs:
-      test, src, ghci-wrapper/src
-  c-sources:
-      test/integration/with-cbits/foo.c
+      test
   build-depends:
-      base
-    , ghc
-    , syb
-    , code-page
+      base ==4.*
+    , base-compat >=0.7.0
+    , code-page >=0.1
     , deepseq
     , directory
+    , doctest
     , filepath
+    , ghc >=7.0 && <8.6
+    , ghc-paths >=0.1.0.9
     , process
-    , ghc-paths
+    , syb >=0.3
     , transformers
-
-    , base-compat
-    , HUnit
-    , hspec         >= 1.5.1
-    , QuickCheck    >= 2.8.2
-    , stringbuilder >= 0.4
-    , silently      >= 1.2.4
-    , setenv
-    , with-location
-    , mockery
+  default-language: Haskell2010
 
-test-suite doctests
-  main-is:
-      doctests.hs
-  type:
-      exitcode-stdio-1.0
-  ghc-options:
-      -Wall -threaded
+test-suite spec
+  main-is: Spec.hs
+  other-modules:
+      ExtractSpec
+      InterpreterSpec
+      LocationSpec
+      MainSpec
+      OptionsSpec
+      Orphans
+      PackageDBsSpec
+      ParseSpec
+      PropertySpec
+      Runner.ExampleSpec
+      RunnerSpec
+      RunSpec
+      SandboxSpec
+      UtilSpec
+      Extract
+      GhcUtil
+      Interpreter
+      Location
+      Options
+      PackageDBs
+      Parse
+      Property
+      Run
+      Runner
+      Runner.Example
+      Sandbox
+      Test.DocTest
+      Util
+      Language.Haskell.GhciWrapper
+      Paths_doctest
+  type: exitcode-stdio-1.0
+  ghc-options: -Wall -threaded
+  cpp-options: -DTEST
   hs-source-dirs:
       test
+      src
+      ghci-wrapper/src
+  c-sources:
+      test/integration/with-cbits/foo.c
   build-depends:
-      base
-    , doctest
+      HUnit
+    , QuickCheck >=2.11.3
+    , base ==4.*
+    , base-compat >=0.7.0
+    , code-page >=0.1
+    , deepseq
+    , directory
+    , filepath
+    , ghc >=7.0 && <8.6
+    , ghc-paths >=0.1.0.9
+    , hspec >=1.5.1
+    , mockery
+    , process
+    , setenv
+    , silently >=1.2.4
+    , stringbuilder >=0.4
+    , syb >=0.3
+    , transformers
+    , with-location
+  default-language: Haskell2010
diff --git a/src/Extract.hs b/src/Extract.hs
--- a/src/Extract.hs
+++ b/src/Extract.hs
@@ -76,6 +76,16 @@
 instance NFData a => NFData (Module a) where
   rnf (Module name setup content) = name `deepseq` setup `deepseq` content `deepseq` ()
 
+#if __GLASGOW_HASKELL__ < 803
+type GhcPs = RdrName
+
+needsTemplateHaskellOrQQ :: ModuleGraph -> Bool
+needsTemplateHaskellOrQQ = needsTemplateHaskell
+
+mapMG :: (ModSummary -> ModSummary) -> ModuleGraph -> ModuleGraph
+mapMG = map
+#endif
+
 -- | Parse a list of modules.
 parse :: [String] -> IO [TypecheckedModule]
 parse args = withGhc args $ \modules_ -> withTempOutputDir $ do
@@ -86,7 +96,7 @@
   mapM (`guessTarget` Nothing) modules >>= setTargets
   mods <- depanal [] False
 
-  mods' <- if needsTemplateHaskell mods then enableCompilation mods else return mods
+  mods' <- if needsTemplateHaskellOrQQ mods then enableCompilation mods else return mods
 
   let sortedMods = flattenSCCs (topSortModuleGraph False mods' Nothing)
   reverse <$> mapM (parseModule >=> typecheckModule >=> loadModule) sortedMods
@@ -103,7 +113,7 @@
       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
+      let modGraph' = mapMG upd modGraph
       return modGraph'
 
     -- copied from Haddock/GhcUtils.hs
@@ -236,7 +246,7 @@
 #endif
   )
   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
diff --git a/src/Interpreter.hs b/src/Interpreter.hs
--- a/src/Interpreter.hs
+++ b/src/Interpreter.hs
@@ -52,7 +52,14 @@
   -> (Interpreter -> IO a)  -- ^ Action to run
   -> IO a                   -- ^ Result of action
 withInterpreter flags action = do
-  let args = ["--interactive"] ++ flags
+  let
+    args = [
+        "--interactive"
+#if __GLASGOW_HASKELL__ >= 802
+      , "-fdiagnostics-color=never"
+      , "-fno-diagnostics-show-caret"
+#endif
+      ] ++ flags
   bracket (new defaultConfig{configGhci = ghc} args) close action
 
 -- | Evaluate an expression; return a Left value on exceptions.
diff --git a/src/Runner.hs b/src/Runner.hs
--- a/src/Runner.hs
+++ b/src/Runner.hs
@@ -14,7 +14,7 @@
 import           Prelude hiding (putStr, putStrLn, error)
 
 #if __GLASGOW_HASKELL__ < 710
-import           Data.Monoid
+import           Data.Monoid hiding ((<>))
 import           Control.Applicative
 #endif
 
@@ -46,10 +46,17 @@
   show (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)
+#if MIN_VERSION_base(4,11,0)
+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)
 
 -- | Run all examples from a list of modules.
 runModules :: Bool -> Bool -> Interpreter -> [Module [Located DocTest]] -> IO Summary
diff --git a/test/ExtractSpec.hs b/test/ExtractSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/ExtractSpec.hs
@@ -0,0 +1,88 @@
+{-# LANGUAGE FlexibleContexts #-}
+module ExtractSpec (main, spec) where
+
+import           Test.Hspec
+import           Test.HUnit
+import           Data.WithLocation
+
+import           Panic (GhcException (..))
+
+import           Extract
+import           Location
+import           System.FilePath
+
+import           Orphans ()
+
+shouldGive :: WithLocation ((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 []]
diff --git a/test/InterpreterSpec.hs b/test/InterpreterSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/InterpreterSpec.hs
@@ -0,0 +1,30 @@
+module InterpreterSpec (main, spec) where
+
+import           Prelude ()
+import           Prelude.Compat
+
+import           Test.Hspec
+
+import           Interpreter (interpreterSupported, haveInterpreterKey, ghcInfo, withInterpreter, safeEval)
+
+main :: IO ()
+main = hspec spec
+
+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 multiline command"
diff --git a/test/LocationSpec.hs b/test/LocationSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/LocationSpec.hs
@@ -0,0 +1,43 @@
+module LocationSpec (main, spec) where
+
+import           Test.Hspec
+
+import           Location
+import           SrcLoc
+import           FastString (fsLit)
+
+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]
diff --git a/test/MainSpec.hs b/test/MainSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/MainSpec.hs
@@ -0,0 +1,169 @@
+{-# LANGUAGE FlexibleContexts #-}
+module MainSpec (main, spec) where
+
+import           Test.Hspec
+import           Test.HUnit (assertEqual, Assertion)
+import           Data.WithLocation
+
+import           Control.Exception
+import           System.Directory (getCurrentDirectory, setCurrentDirectory)
+import           System.FilePath
+import           Options
+import           Runner (Summary(..))
+import           Run hiding (doctest)
+import           System.IO.Silently
+import           System.IO
+
+withCurrentDirectory :: FilePath -> IO a -> IO a
+withCurrentDirectory workingDir action = do
+  bracket getCurrentDirectory setCurrentDirectory $ \_ -> do
+    setCurrentDirectory workingDir
+    action
+
+-- | Construct a doctest specific 'Assertion'.
+doctest :: WithLocation (FilePath -> [String] -> Summary -> Assertion)
+doctest = doctestWithPreserveIt defaultPreserveIt
+
+doctestWithPreserveIt :: WithLocation (Bool -> FilePath -> [String] -> Summary -> Assertion)
+doctestWithPreserveIt preserveIt workingDir args expected = do
+  actual <- withCurrentDirectory ("test/integration" </> workingDir) (hSilence [stderr] $ doctestWithOptions defaultFastMode preserveIt args)
+  assertEqual label expected actual
+  where
+    label = workingDir ++ " " ++ show args
+
+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 True "." ["it/Foo.hs"]
+        (cases 5)
+
+    it "it-variable in $setup" $ do
+      doctestWithPreserveIt True "." ["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" $
+      doctest "." ["failing-multiple/Foo.hs"]
+        (cases 4) {sTried = 2, 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}
+
+    it "works with additional object files" $ do
+      doctest "with-cbits" ["Bar.hs", "../../../dist/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)
diff --git a/test/OptionsSpec.hs b/test/OptionsSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/OptionsSpec.hs
@@ -0,0 +1,60 @@
+module OptionsSpec (spec) where
+
+import           Prelude ()
+import           Prelude.Compat
+
+import           Test.Hspec
+import           Test.QuickCheck
+
+import           Options
+
+spec :: Spec
+spec = do
+  describe "parseOptions" $ do
+    let warning = ["WARNING: --optghc is deprecated, doctest now accepts arbitrary GHC options\ndirectly."]
+    it "strips --optghc" $
+      property $ \xs ys ->
+        parseOptions (xs ++ ["--optghc", "foobar"] ++ ys) `shouldBe` Result (Run warning (xs ++ ["foobar"] ++ ys) defaultMagic defaultFastMode defaultPreserveIt)
+
+    it "strips --optghc=" $
+      property $ \xs ys ->
+        parseOptions (xs ++ ["--optghc=foobar"] ++ ys) `shouldBe` Result (Run warning (xs ++ ["foobar"] ++ ys) defaultMagic defaultFastMode defaultPreserveIt)
+
+    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
+          runFastMode <$> parseOptions [] `shouldBe` Result False
+
+      context "with --fast" $ do
+        it "enabled fast mode" $ do
+          runFastMode <$> parseOptions ["--fast"] `shouldBe` Result True
+
+    describe "--preserve-it" $ do
+      context "without --preserve-it" $ do
+        it "does not preserve the `it` variable" $ do
+          runPreserveIt <$> parseOptions [] `shouldBe` Result False
+
+      context "with --preserve-it" $ do
+        it "preserves the `it` variable" $ do
+          runPreserveIt <$> parseOptions ["--preserve-it"] `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
diff --git a/test/Orphans.hs b/test/Orphans.hs
new file mode 100644
--- /dev/null
+++ b/test/Orphans.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE StandaloneDeriving, FlexibleInstances #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+module Orphans where
+
+import           Parse
+import           Location
+
+-- The generic form
+--
+-- > deriving instance Show a => Show (Module a)
+--
+-- fails with GHC 7.0.1 due to an overlapping instance (leaked by the GHC API),
+-- this is why we derive the things we need individually.
+deriving instance Show (Module String)
+deriving instance Show (Module [DocTest])
+deriving instance Show (Module [Located DocTest])
diff --git a/test/PackageDBsSpec.hs b/test/PackageDBsSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/PackageDBsSpec.hs
@@ -0,0 +1,73 @@
+module PackageDBsSpec (main, spec) where
+
+import           Prelude ()
+import           Prelude.Compat
+
+import qualified Control.Exception         as E
+import           Data.List                 (intercalate)
+import           PackageDBs
+import           System.Directory          (getCurrentDirectory, setCurrentDirectory)
+import           System.Environment.Compat
+import           System.FilePath           (searchPathSeparator)
+import           Test.Hspec
+
+import           Test.Mockery.Directory
+
+main :: IO ()
+main = hspec spec
+
+withCurrentDirectory :: FilePath -> IO a -> IO a
+withCurrentDirectory workingDir action = do
+  E.bracket getCurrentDirectory setCurrentDirectory $ \_ -> do
+    setCurrentDirectory workingDir
+    action
+
+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" ""
+  . withEnv "HASKELL_PACKAGE_SANDBOX" ""
+  . withEnv "HASKELL_PACKAGE_SANDBOXES" ""
+
+combineDirs :: [FilePath] -> String
+combineDirs = intercalate [searchPathSeparator]
+
+spec :: Spec
+spec = around_ clearEnv $ do
+  describe "getPackageDBsFromEnv" $ do
+    context "without a cabal sandbox present" $ do
+      around_ (inTempDirectory) $ do
+        it "uses global and user when no env or sandboxing 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"]
+
+        it "HASKELL_PACKAGE_SANDBOXES trumps GHC_PACKAGE_PATH" $
+          withEnv "GHC_PACKAGE_PATH" (combineDirs ["foo1", "bar1", ""]) $ do
+          withEnv "HASKELL_PACKAGE_SANDBOXES" (combineDirs ["foo2", "bar2", ""]) $ do
+            getPackageDBsFromEnv `shouldReturn` PackageDBs False True ["foo2", "bar2"]
+
+        it "HASKELL_PACKAGE_SANDBOX trumps GHC_PACKAGE_PATH" $
+          withEnv "GHC_PACKAGE_PATH" (combineDirs ["foo1", "bar1", ""]) $ do
+          withEnv "HASKELL_PACKAGE_SANDBOX" (combineDirs ["foo2"]) $ do
+
+            getPackageDBsFromEnv `shouldReturn` PackageDBs True True ["foo2"]
+
+    context "with a cabal sandbox present" $ do
+      around_ (withCurrentDirectory "test/sandbox") $ do
+        it "respects cabal sandboxes" $ do
+            getPackageDBsFromEnv `shouldReturn`
+              PackageDBs False True ["/home/me/doctest-haskell/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d"]
+
+        it "GHC_PACKAGE_PATH takes precedence" $
+          withEnv "GHC_PACKAGE_PATH" (combineDirs ["foo", "bar"]) $ do
+            getPackageDBsFromEnv `shouldReturn`
+              PackageDBs False False ["foo", "bar"]
diff --git a/test/ParseSpec.hs b/test/ParseSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/ParseSpec.hs
@@ -0,0 +1,205 @@
+{-# LANGUAGE OverloadedStrings #-}
+module ParseSpec (main, spec) where
+
+import           Test.Hspec
+import           Data.String
+import           Data.String.Builder (Builder, build)
+import           Control.Monad.Trans.Writer
+
+import           Parse
+import           Location
+
+import           Orphans ()
+
+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 "getDocTests" $ do
+    it "extracts properties from a module" $ do
+      getDocTests ["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
+      getDocTests ["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
+      getDocTests ["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
+      getDocTests ["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
+      getDocTests ["test/parse/no-examples/Fib.hs"] >>= (`shouldBe` [])
+
+    it "sets setup code to Nothing, if it does not contain any tests" $ do
+      getDocTests ["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
+      getDocTests ["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
+                                                ]
diff --git a/test/PropertySpec.hs b/test/PropertySpec.hs
new file mode 100644
--- /dev/null
+++ b/test/PropertySpec.hs
@@ -0,0 +1,130 @@
+{-# LANGUAGE CPP, OverloadedStrings #-}
+module PropertySpec (main, spec) where
+
+import           Test.Hspec
+import           Data.String.Builder
+
+import           Property
+import           Interpreter (withInterpreter)
+
+main :: IO ()
+main = hspec spec
+
+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! Falsifiable (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" $
+#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
+
+    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! Falsifiable (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_"]
diff --git a/test/RunSpec.hs b/test/RunSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/RunSpec.hs
@@ -0,0 +1,134 @@
+{-# LANGUAGE CPP #-}
+module RunSpec (main, spec) where
+
+import           Prelude ()
+import           Prelude.Compat
+
+import           Test.Hspec
+import           System.Exit
+
+import qualified Control.Exception as E
+#if __GLASGOW_HASKELL__ < 707
+import           System.Cmd
+#else
+import           System.Process
+#endif
+import           System.Directory (getCurrentDirectory, setCurrentDirectory, removeDirectoryRecursive)
+import           Data.List.Compat
+
+import           System.Environment.Compat
+
+import           System.IO.Silently
+import           System.IO (stderr)
+import qualified Options
+
+import           Run
+
+doctestWithDefaultOptions :: [String] -> IO Summary
+doctestWithDefaultOptions = doctestWithOptions Options.defaultFastMode Options.defaultPreserveIt
+
+withCurrentDirectory :: FilePath -> IO a -> IO a
+withCurrentDirectory workingDir action = do
+  E.bracket getCurrentDirectory setCurrentDirectory $ \_ -> do
+    setCurrentDirectory workingDir
+    action
+
+rmDir :: FilePath -> IO ()
+rmDir dir = removeDirectoryRecursive dir `E.catch` (const $ return () :: E.IOException -> IO ())
+
+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)
+
+main :: IO ()
+main = hspec spec
+
+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)
+      r `shouldBe` unlines [
+          "doctest: unrecognized option `--foo'"
+        , "Try `doctest --help' for more information."
+        ]
+
+    it "respects HASKELL_PACKAGE_SANDBOX" $ do
+      withCurrentDirectory "test/integration/custom-package-conf/foo" $ do
+        ExitSuccess <- rawSystem "ghc-pkg" ["init", "../packages"]
+        ExitSuccess <- rawSystem "cabal" ["configure", "--disable-optimization", "--disable-library-profiling", "--package-db=../packages"]
+        ExitSuccess <- rawSystem "cabal" ["build"]
+        ExitSuccess <- rawSystem "cabal" ["register", "--inplace"]
+        return ()
+
+      withEnv "HASKELL_PACKAGE_SANDBOX" "test/integration/custom-package-conf/packages" $ do
+        hCapture_ [stderr] (doctest ["test/integration/custom-package-conf/Bar.hs"])
+          `shouldReturn` "Examples: 2  Tried: 2  Errors: 0  Failures: 0\n"
+
+      `E.finally` do
+        rmDir "test/integration/custom-package-conf/packages/"
+        rmDir "test/integration/custom-package-conf/foo/dist/"
+
+  describe "doctestWithOptions" $ do
+    context "on parse error" $ do
+      let action = withCurrentDirectory "test/integration/parse-error" (doctestWithDefaultOptions ["Foo.hs"])
+
+      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))
+#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
+        r `shouldBe` "\nFoo.hs:6:1: error:\n    parse error (possibly incorrect indentation or mismatched brackets)\n"
+#endif
+
+#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]
diff --git a/test/Runner/ExampleSpec.hs b/test/Runner/ExampleSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Runner/ExampleSpec.hs
@@ -0,0 +1,83 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Runner.ExampleSpec (main, spec) where
+
+import           Prelude ()
+import           Prelude.Compat
+
+import           Data.String
+import           Test.Hspec
+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 an arbitrary number of lines" $ do
+        mkResult ["foo", WildCardLine, "bar"] ["foo", "baz", "bazoom", "bar"]
+            `shouldBe` Equal
+
+      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
+
+    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\""
+            ]
diff --git a/test/RunnerSpec.hs b/test/RunnerSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/RunnerSpec.hs
@@ -0,0 +1,91 @@
+{-# LANGUAGE CPP, OverloadedStrings #-}
+module RunnerSpec (main, spec) where
+
+import           Test.Hspec
+
+#if __GLASGOW_HASKELL__ < 710
+import           Data.Monoid
+#endif
+
+import           System.IO
+import           System.IO.Silently (hCapture)
+import           Control.Monad.Trans.State
+import           Runner
+
+main :: IO ()
+main = hspec spec
+
+capture :: Report a -> IO String
+capture = fmap fst . hCapture [stderr] . (`execStateT` ReportState 0 True mempty)
+
+-- like capture, but with interactivity set to False
+capture_ :: Report a -> IO String
+capture_ = fmap fst . hCapture [stderr] . (`execStateT` ReportState 0 False mempty)
+
+spec :: Spec
+spec = do
+
+  describe "report" $ do
+
+    context "when mode is interactive" $ do
+
+      it "writes to stderr" $ do
+        capture $ do
+          report "foobar"
+        `shouldReturn` "foobar\n"
+
+      it "overwrites any intermediate output" $ do
+        capture $ do
+          report_ "foo"
+          report  "bar"
+        `shouldReturn` "foo\rbar\n"
+
+      it "blank out intermediate output if necessary" $ do
+        capture $ do
+          report_ "foobar"
+          report  "baz"
+        `shouldReturn` "foobar\rbaz   \n"
+
+    context "when mode is non-interactive" $ do
+      it "writes to stderr" $ do
+        capture_ $ do
+          report "foobar"
+        `shouldReturn` "foobar\n"
+
+  describe "report_" $ do
+
+    context "when mode is interactive" $ do
+      it "writes intermediate output to stderr" $ do
+        capture $ do
+          report_ "foobar"
+        `shouldReturn` "foobar"
+
+      it "overwrites any intermediate output" $ do
+        capture $ do
+          report_ "foo"
+          report_ "bar"
+        `shouldReturn` "foo\rbar"
+
+      it "blank out intermediate output if necessary" $ do
+        capture $ do
+          report_ "foobar"
+          report_  "baz"
+        `shouldReturn` "foobar\rbaz   "
+
+    context "when mode is non-interactive" $ do
+      it "is ignored" $ do
+        capture_ $ do
+          report_ "foobar"
+        `shouldReturn` ""
+
+      it "does not influence a subsequent call to `report`" $ do
+        capture_ $ do
+          report_ "foo"
+          report  "bar"
+        `shouldReturn` "bar\n"
+
+      it "does not require `report` to blank out any intermediate output" $ do
+        capture_ $ do
+          report_ "foobar"
+          report  "baz"
+        `shouldReturn` "baz\n"
diff --git a/test/SandboxSpec.hs b/test/SandboxSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/SandboxSpec.hs
@@ -0,0 +1,18 @@
+module SandboxSpec where
+
+import Test.Hspec
+
+import Sandbox
+
+main :: IO ()
+main = hspec spec
+
+spec :: Spec
+spec = do
+  describe "getPackageDbDir" $ do
+    it "parses a config file and extracts package db" $ do
+      pkgDb <- getPackageDbDir "test/sandbox/cabal.sandbox.config"
+      pkgDb `shouldBe` "/home/me/doctest-haskell/.cabal-sandbox/i386-osx-ghc-7.6.3-packages.conf.d"
+
+    it "throws an error if a config file is broken" $ do
+      getPackageDbDir "test/sandbox/bad.config" `shouldThrow` anyException
diff --git a/test/UtilSpec.hs b/test/UtilSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/UtilSpec.hs
@@ -0,0 +1,21 @@
+module UtilSpec (main, spec) where
+
+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"
