diff --git a/CHANGES.txt b/CHANGES.txt
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,13 @@
 Changelog for ghcid
 
+0.6.5
+    #82, properly deal with warning messages including spans
+    #78, support boot files better
+    Better support for dealing with GHC 8.0 call stack messages
+    #79, support multiple --test flags
+    #71, improve multi-project stack support
+    #74, make sure Cygwin terminals flush output properly
+    If the test exits then exit ghcid
 0.6.4
     #69, fix up for stack project with file arguments
 0.6.3
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -35,4 +35,7 @@
 
 * _This isn't as good as full IDE._ I've gone for simplicity over features. It's a point in the design space, but not necessarily the best point in the design space for you. For "real" IDEs see [the Haskell wiki](http://www.haskell.org/haskellwiki/IDEs).
 * _If I delete a file and put it back it gets stuck._ Yes, that's a [bug in GHCi](https://ghc.haskell.org/trac/ghc/ticket/9648). If you see GHCi getting confused just kill `ghcid` and start it again.
-* _I want to run arbitrary commands when arbitrary files change._ This project reloads `ghci` when files loaded by `ghci` change. If you want a more general mechanism something like [Steel Overseer](https://github.com/steeloverseer/steeloverseer) will probably work better.
+* _I want to run my tests when files change._ You can pass any `ghci` expression with the `--test` flag, e.g. `--test=:main`, which will be run whenever the code is warning free (or pass `--warnings` for when the code is merely error free).
+* _I want to run arbitrary commands when arbitrary files change._ This project reloads `ghci` when files loaded by `ghci` change. If you want a more general mechanism something like [Steel Overseer](https://github.com/schell/steeloverseer) will probably work better.
+* _I want syntax highlighting in the error messages._ One option is to use Neovim or Emacs and run the terminal in a buffer whose file type is set to Haskell.
+* _I'm not seeing pattern matching warnings._ Ghcid automatically appends `-fno-code` to the command line, which makes the reload cycle about twice as fast. Unfortunately GHC 8.0 suffers from [bug 10600](https://ghc.haskell.org/trac/ghc/ticket/10600) which means `-fno-code` also disables pattern matching warnings. Until that GHC bug is fixed either accept no pattern match warnings or use `-c` to specify a command line to start `ghci` that doesn't include `-fno-code`.
diff --git a/ghcid.cabal b/ghcid.cabal
--- a/ghcid.cabal
+++ b/ghcid.cabal
@@ -1,7 +1,7 @@
 cabal-version:      >= 1.10
 build-type:         Simple
 name:               ghcid
-version:            0.6.4
+version:            0.6.5
 license:            BSD3
 license-file:       LICENSE
 category:           Development
@@ -70,12 +70,12 @@
     else
         build-depends: unix
     other-modules:
-        Language.Haskell.Ghcid.Types,
-        Language.Haskell.Ghcid.Parser,
-        Language.Haskell.Ghcid.Terminal,
-        Language.Haskell.Ghcid.Util,
-        Language.Haskell.Ghcid,
-        Session,
+        Language.Haskell.Ghcid.Types
+        Language.Haskell.Ghcid.Parser
+        Language.Haskell.Ghcid.Terminal
+        Language.Haskell.Ghcid.Util
+        Language.Haskell.Ghcid
+        Session
         Wait
 
 test-suite ghcid_test
@@ -103,7 +103,7 @@
     else
         build-depends: unix
     other-modules:
-        Test.Parser,
-        Test.HighLevel,
-        Test.Util,
-        Test.Polling
+        Test.Parser
+        Test.API
+        Test.Util
+        Test.Ghcid
diff --git a/src/Ghcid.hs b/src/Ghcid.hs
--- a/src/Ghcid.hs
+++ b/src/Ghcid.hs
@@ -2,7 +2,7 @@
 {-# OPTIONS_GHC -fno-cse #-}
 
 -- | The application entry point
-module Ghcid(main, runGhcid) where
+module Ghcid(main, mainWithTerminal) where
 
 import Control.Exception
 import System.IO.Error
@@ -19,21 +19,22 @@
 import System.Exit
 import System.FilePath
 import System.IO
-import System.IO.Unsafe
 
 import Paths_ghcid
-import Language.Haskell.Ghcid
 import Language.Haskell.Ghcid.Terminal
 import Language.Haskell.Ghcid.Util
 import Language.Haskell.Ghcid.Types
 import Wait
 
+import Data.Functor
+import Prelude
 
+
 -- | Command line options
 data Options = Options
     {command :: String
     ,arguments :: [String]
-    ,test :: Maybe String
+    ,test :: [String]
     ,warnings :: Bool
     ,nostatus :: Bool
     ,height :: Maybe Int
@@ -51,7 +52,7 @@
 options = cmdArgsMode $ Options
     {command = "" &= typ "COMMAND" &= help "Command to run (defaults to ghci or cabal repl)"
     ,arguments = [] &= args &= typ "MODULE"
-    ,test = Nothing &= name "T" &= typ "EXPR" &= help "Command to run after successful loading"
+    ,test = [] &= name "T" &= typ "EXPR" &= help "Command to run after successful loading"
     ,warnings = False &= name "W" &= help "Allow tests to run even with warnings"
     ,nostatus = False &= name "S" &= explicit &= name "no-status" &= help "Suppress status messages"
     ,height = Nothing &= help "Number of lines to use (defaults to console height)"
@@ -96,13 +97,14 @@
         files <- getDirectoryContents "."
 
         -- use unsafePerformIO to get nicer pattern matching for logic (read-only operations)
-        let isStack dir = unsafePerformIO $ flip catchIOError (const $ return False) $
+        let isStack dir = flip catchIOError (const $ return False) $
                 doesFileExist (dir </> "stack.yaml") &&^ doesDirectoryExist (dir </> ".stack-work")
+        stack <- isStack "." ||^ isStack ".." -- stack file might be parent, see #62
 
         let cabal = filter ((==) ".cabal" . takeExtension) files
-        let opts = ["-fno-code" | isNothing test]
+        let opts = ["-fno-code" | null test]
         return $ case () of
-            _ | cabal /= [], isStack "." || isStack ".." -> -- stack file might be parent, see #62
+            _ | stack ->
                 let flags = if null arguments then
                                 "stack ghci --test" :
                                 ["--no-load" | ".ghci" `elem` files] ++
@@ -121,45 +123,58 @@
                  | otherwise = x
 
 
-main :: IO ()
-main = withWindowIcon $ withSession $ \session -> do
+-- | Like 'main', but run with a fake terminal for testing
+mainWithTerminal :: IO (Int,Int) -> ([(Style,String)] -> IO ()) -> IO ()
+mainWithTerminal termSize termOutput = withWindowIcon $ withSession $ \session -> do
+    -- On certain Cygwin terminals stdout defaults to BlockBuffering
+    hSetBuffering stdout LineBuffering
+    hSetBuffering stderr NoBuffering
     opts <- cmdArgsRun options
     withCurrentDirectory (directory opts) $ do
-        opts@Options{..} <- autoOptions opts
-        when topmost terminalTopmost
-        height <- return $ case (width, height) of
+        opts <- autoOptions opts
+        opts <- return $ opts{restart = nubOrd $ restart opts, reload = nubOrd $ reload opts}
+        when (topmost opts) terminalTopmost
+
+        termSize <- return $ case (width opts, height opts) of
             (Just w, Just h) -> return (w,h)
-            _ -> do
-                term <- fmap (fmap $ Term.width &&& Term.height) Term.size
-                whenLoud $ do
-                    outStrLn $ "%CONSOLE: width = " ++ maybe "?" (show . fst) term ++ ", height = " ++ maybe "?" (show . snd) term
-                let f user def sel = fromMaybe (maybe def sel term) user
-                -- if we write to the end of the window then it wraps automatically
+            (w, h) -> do
+                term <- termSize
+                -- if we write to the final column of the window then it wraps automatically
                 -- so putStrLn width 'x' uses up two lines
-                return (f width 80 (pred . fst), f height 8 snd)
+                return (fromMaybe (pred $ fst term) w, fromMaybe (snd term) h)
+
         withWaiterNotify $ \waiter ->
             handle (\(UnexpectedExit cmd _) -> putStrLn $ "Command \"" ++ cmd ++ "\" exited unexpectedly") $
-                runGhcid session waiter (nubOrd restart) (nubOrd reload) command outputfile test warnings nostatus height (not notitle) $ \xs -> do
-                    outWith $ forM_ (groupOn fst xs) $ \x@((s,_):_) -> do
-                        when (s == Bold) $ setSGR [SetConsoleIntensity BoldIntensity]
-                        putStr $ concatMap ((:) '\n' . snd) x
-                        when (s == Bold) $ setSGR []
-                    hFlush stdout -- must flush, since we don't finish with a newline
+                runGhcid session waiter termSize termOutput opts
 
 
+
+main :: IO ()
+main = mainWithTerminal termSize termOutput
+    where
+        termSize = maybe (80, 8) (Term.width &&& Term.height) <$> Term.size
+
+        termOutput xs = do
+            outWith $ forM_ (groupOn fst xs) $ \x@((s,_):_) -> do
+                when (s == Bold) $ setSGR [SetConsoleIntensity BoldIntensity]
+                putStr $ concatMap ((:) '\n' . snd) x
+                when (s == Bold) $ setSGR []
+            hFlush stdout -- must flush, since we don't finish with a newline
+
+
 data Style = Plain | Bold deriving Eq
 
 
-runGhcid :: Session -> Waiter -> [FilePath] -> [FilePath] -> String -> [FilePath] -> Maybe String -> Bool -> Bool -> IO (Int,Int) -> Bool -> ([(Style,String)] -> IO ()) -> IO ()
-runGhcid session waiter restart reload command outputfiles test warnings nostatus size titles output = do
+runGhcid :: Session -> Waiter -> IO (Int,Int) -> ([(Style,String)] -> IO ()) -> Options -> IO ()
+runGhcid session waiter termSize termOutput opts@Options{..} = do
     let outputFill :: Maybe (Int, [Load]) -> [String] -> IO ()
         outputFill load msg = do
-            (width, height) <- size
+            (width, height) <- termSize
             let n = height - length msg
             load <- return $ take (if isJust load then n else 0) $ prettyOutput (maybe 0 fst load)
                 [ m{loadMessage = concatMap (chunksOfWord width (width `div` 5)) $ loadMessage m}
                 | m@Message{} <- maybe [] snd load]
-            output $ load ++ map (Plain,) msg ++ replicate (height - (length load + length msg)) (Plain,"")
+            termOutput $ load ++ map (Plain,) msg ++ replicate (height - (length load + length msg)) (Plain,"")
 
     restartTimes <- mapM getModTime restart
     curdir <- getCurrentDirectory
@@ -173,20 +188,22 @@
 
             let (countErrors, countWarnings) = both sum $ unzip
                     [if loadSeverity == Error then (1,0) else (0,1) | m@Message{..} <- messages, loadMessage /= []]
-            test <- return $ if countErrors == 0 && (warnings || countWarnings == 0) then test else Nothing
+            test <- return $
+                if null test || countErrors /= 0 || (countWarnings /= 0 && not warnings) then Nothing
+                else Just $ intercalate "\n" test
 
-            when titles $ setWindowIcon $
+            unless notitle $ setWindowIcon $
                 if countErrors > 0 then IconError else if countWarnings > 0 then IconWarning else IconOK
 
-            let updateTitle extra = when titles $ setTitle $
+            let updateTitle extra = unless notitle $ setTitle $
                     let f n msg = if n == 0 then "" else show n ++ " " ++ msg ++ ['s' | n > 1]
                     in (if countErrors == 0 && countWarnings == 0 then allGoodMessage else f countErrors "error" ++
                         (if countErrors > 0 && countWarnings > 0 then ", " else "") ++ f countWarnings "warning") ++
                        " " ++ extra ++ [' ' | extra /= ""] ++ "- " ++ takeFileName curdir
 
-            updateTitle $ if isJust test then "(running test) " else ""
+            updateTitle $ if isJust test then "(running test)" else ""
             outputFill (Just (loadedCount, messages)) ["Running test..." | isJust test]
-            forM_ outputfiles $ \file ->
+            forM_ outputfile $ \file ->
                 writeFile file $ unlines $ map snd $ prettyOutput loadedCount $ filter isMessage messages
             when (null loaded) $ do
                 putStrLn $ "No files loaded, nothing to wait for. Fix the last error and restart."
@@ -195,6 +212,7 @@
                 whenLoud $ outStrLn $ "%TESTING: " ++ t
                 sessionExecAsync session t $ \stderr -> do
                     whenLoud $ outStrLn "%TESTING: Completed"
+                    hFlush stdout -- may not have been a terminating newline from test output
                     if "*** Exception: " `isPrefixOf` stderr then do
                         updateTitle "(test failed)"
                         setWindowIcon IconError
@@ -208,7 +226,7 @@
                 nextWait <- waitFiles waiter
                 fire nextWait =<< sessionReload session
             else do
-                runGhcid session waiter restart reload command outputfiles test warnings nostatus size titles output
+                runGhcid session waiter termSize termOutput opts
 
     nextWait <- waitFiles waiter
     (messages, loaded) <- sessionStart session command
diff --git a/src/Language/Haskell/Ghcid.hs b/src/Language/Haskell/Ghcid.hs
--- a/src/Language/Haskell/Ghcid.hs
+++ b/src/Language/Haskell/Ghcid.hs
@@ -71,7 +71,8 @@
     let syncReplay = do
             i <- readVar syncCount
             let msg = "#~GHCID-FINISH-" ++ show i ++ "~#"
-            writeInp $ "Prelude.putStrLn " ++ show msg ++ "\nPrelude.error " ++ show msg
+            writeInp $ "INTERNAL_GHCID.putStrLn " ++ show msg ++ "\n" ++
+                       "INTERNAL_GHCID.hPutStrLn INTERNAL_GHCID.stderr " ++ show msg
             return $ isInfixOf msg
     let syncFresh = do
             modifyVar_ syncCount $ return . succ
@@ -150,9 +151,10 @@
                 -- the thing before me may have done its own Haskell compiling
                 writeIORef stdout []
                 writeIORef stderr []
-                writeIORef sync =<< syncFresh
+                writeInp "import qualified System.IO as INTERNAL_GHCID"
                 writeInp $ ":set prompt " ++ ghcid_prefix
                 writeInp ":set -fno-break-on-exception -fno-break-on-error" -- see #43
+                writeIORef sync =<< syncFresh
             echo0 strm s
             return Nothing
     r <- parseLoad . reverse <$> ((++) <$> readIORef stderr <*> readIORef stdout)
@@ -206,9 +208,8 @@
 quit ghci =  do
     interrupt ghci
     handle (\UnexpectedExit{} -> return ()) $ void $ exec ghci ":quit"
-    -- Add ignore because sometimes I get a message about an invalid handle,
-    -- and I've got no idea why - but exceptions here probably mean the thing already died.
-    -- Windows only, and Appveyor only.
+    -- Be aware that waitForProcess has a race condition, see https://github.com/haskell/process/issues/46.
+    -- Therefore just ignore the exception anyway, its probably already terminated.
     ignore $
         void $ waitForProcess $ process ghci
 
@@ -217,9 +218,8 @@
 --   within 5 seconds it just terminates the process.
 stopGhci :: Ghci -> IO ()
 stopGhci ghci = do
-    forkIO $ quit ghci
     forkIO $ do
         -- if nicely doesn't work, kill ghci as the process level
         sleep 5
         terminateProcess $ process ghci
-    void $ waitForProcess $ process ghci
+    quit ghci
diff --git a/src/Language/Haskell/Ghcid/Parser.hs b/src/Language/Haskell/Ghcid/Parser.hs
--- a/src/Language/Haskell/Ghcid/Parser.hs
+++ b/src/Language/Haskell/Ghcid/Parser.hs
@@ -35,12 +35,14 @@
         f (x:xs)
             | not $ " " `isPrefixOf` x
             , Just (file,rest) <- breakFileColon x
-            , takeExtension file `elem` [".hs",".lhs"]
-            , (pos,rest2) <- span (\c -> c == ':' || isDigit c) rest
-            , [p1,p2] <- map read $ words $ map (\c -> if c == ':' then ' ' else c) pos
+            , takeExtension file `elem` [".hs",".lhs",".hs-boot",".lhs-boot"]
+             -- take position, including span if present
+            , (pos,rest) <- span (\c -> c == ':' || c == '-' || isSpan c || isDigit c) rest
+            -- separate line and column, ignoring span (we want the start point only)
+            , [p1,p2] <- map read $ wordsBy (\c -> c == ':' || isSpan c) $ takeWhile (\c->c /= '-') pos 
             , (msg,las) <- span (isPrefixOf " ") xs
-            , rest3 <- trimStart rest2
-            , sev <- if "warning:" `isPrefixOf` lower rest3 then Warning else Error
+            , rest <- trimStart $ unwords $ rest : xs
+            , sev <- if "warning:" `isPrefixOf` lower rest then Warning else Error
             = Message sev file (p1,p2) (x:msg) : f las
         f (x:xs)
             | Just file <- stripPrefix "<no location info>: can't find file: " x
@@ -54,7 +56,7 @@
               [Message Error m (0,0) [] | m <- nubOrd ms] ++ f rest
         f (_:xs) = f xs
         f [] = []
-
+        isSpan c = c== ',' || c == '(' || c == ')'
 
 -- A filename, followed by a colon - be careful to handle Windows drive letters, see #61
 breakFileColon :: String -> Maybe (FilePath, String)
diff --git a/src/Language/Haskell/Ghcid/Util.hs b/src/Language/Haskell/Ghcid/Util.hs
--- a/src/Language/Haskell/Ghcid/Util.hs
+++ b/src/Language/Haskell/Ghcid/Util.hs
@@ -5,17 +5,21 @@
     chunksOfWord,
     outWith, outStrLn, outStr,
     allGoodMessage,
-    getModTime
+    getModTime, getModTimeResolution
     ) where
 
 import Control.Concurrent.Extra
+import System.Time.Extra
 import System.IO.Unsafe
+import System.IO.Extra
+import System.FilePath
 import Data.List.Extra
 import Data.Char
 import Data.Time.Clock
 import System.IO.Error
 import System.Directory
 import Control.Exception
+import Control.Monad.Extra
 import Control.Applicative
 import Prelude
 
@@ -59,3 +63,28 @@
     (\e -> if isDoesNotExistError e then Just () else Nothing)
     (\_ -> return Nothing)
     (Just <$> getModificationTime file)
+
+
+
+-- | Get the smallest difference that can be reported by two modification times
+getModTimeResolution :: IO Seconds
+getModTimeResolution = return getModTimeResolutionCache
+
+{-# NOINLINE getModTimeResolutionCache #-}
+-- Cache the result so only computed once per run
+getModTimeResolutionCache :: Seconds
+getModTimeResolutionCache = unsafePerformIO $ withTempDir $ \dir -> do
+    let file = dir </> "calibrate.txt"
+
+    -- with 10 measurements can get a bit slow, see Shake issue tracker #451
+    -- if it rounds to a second then 1st will be a fraction, but 2nd will be full second
+    mtime <- fmap maximum $ forM [1..3] $ \i -> fmap fst $ duration $ do
+        writeFile file $ show i
+        t1 <- getModificationTime file
+        flip loopM 0 $ \j -> do
+            writeFile file $ show (i,j)
+            t2 <- getModificationTime file
+            return $ if t1 == t2 then Left $ j+1 else Right ()
+    putStrLn $ "Longest file modification time lag was " ++ show (ceiling (mtime * 1000)) ++ "ms"
+    -- add a little bit of safety, but if it's really quick, don't make it that much slower
+    return $ mtime + min 0.1 mtime
diff --git a/src/Session.hs b/src/Session.hs
--- a/src/Session.hs
+++ b/src/Session.hs
@@ -31,8 +31,9 @@
 
 
 -- | Ensure an action runs off the main thread, so can't get hit with Ctrl-C exceptions.
+--   Disabled because it plays havoc with tests and cleaning up quickly enough.
 ctrlC :: IO a -> IO a
-ctrlC = join . onceFork
+ctrlC = id -- join . onceFork
 
 
 -- | The function 'withSession' expects to be run on the main thread,
@@ -44,7 +45,7 @@
     command <- newIORef Nothing
     warnings <- newIORef []
     running <- newVar False
-    ctrlC (f $ Session{..}) `finally` do
+    ctrlC (f Session{..}) `finally` do
         modifyVar_ running $ const $ return False
         whenJustM (readIORef ghci) $ \v -> do
             writeIORef ghci Nothing
@@ -118,7 +119,8 @@
     Just ghci <- readIORef ghci
     stderr <- newIORef ""
     modifyVar_ running $ const $ return True
-    void $ forkIO $ do
+    caller <- myThreadId
+    void $ flip forkFinally (either (throwTo caller) (const $ return ())) $ do
         execStream ghci cmd $ \strm msg ->
             when (msg /= "*** Exception: ExitSuccess") $ do
                 when (strm == Stderr) $ writeIORef stderr msg
diff --git a/src/Test.hs b/src/Test.hs
--- a/src/Test.hs
+++ b/src/Test.hs
@@ -2,21 +2,24 @@
 module Test(main) where
 
 import Test.Tasty
-import Test.Parser
-import Test.HighLevel
-import Test.Util
-import Test.Polling
 import System.IO
+import System.Console.CmdArgs
 
+import Test.Util
+import Test.Parser
+import Test.API
+import Test.Ghcid
+
 main :: IO ()
 main = do
     hSetBuffering stdout NoBuffering
+    setVerbosity Loud
     defaultMain tests
 
 tests :: TestTree
 tests = testGroup "Tests"
     [utilsTests
     ,parserTests
-    ,pollingTest
-    ,highLevelTests
+    ,apiTests
+    ,ghcidTest
     ]
diff --git a/src/Test/API.hs b/src/Test/API.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/API.hs
@@ -0,0 +1,36 @@
+-- | Test the high level library API
+module Test.API(apiTests) where
+
+import Test.Tasty
+import Test.Tasty.HUnit
+import System.FilePath
+import System.IO.Extra
+import System.Time.Extra
+import Language.Haskell.Ghcid
+import Language.Haskell.Ghcid.Util
+
+
+apiTests :: TestTree
+apiTests = testGroup "API test"
+    [testCase "No files" $ withTempDir $ \dir -> do
+        (ghci,load) <- startGhci "ghci" (Just dir) $ const putStrLn
+        load @?= []
+        showModules ghci >>= (@?= [])
+        exec ghci "import Data.List"
+        exec ghci "nub \"test\"" >>= (@?= ["\"tes\""])
+        stopGhci ghci
+
+    ,testCase "Load file" $ withTempDir $ \dir -> do
+        writeFile (dir </> "File.hs") "module A where\na = 123"
+        (ghci, load) <- startGhci "ghci File.hs" (Just dir) $ const putStrLn
+        load @?= [Loading "A" "File.hs"]
+        exec ghci "a + 1" >>= (@?= ["124"])
+        reload ghci >>= (@?= [])
+
+        sleep =<< getModTimeResolution
+        writeFile (dir </> "File.hs") "module A where\na = 456"
+        exec ghci "a + 1" >>= (@?= ["124"])
+        reload ghci >>= (@?= [Loading "A" "File.hs"])
+        exec ghci "a + 1" >>= (@?= ["457"])
+        stopGhci ghci
+    ]
diff --git a/src/Test/Ghcid.hs b/src/Test/Ghcid.hs
new file mode 100644
--- /dev/null
+++ b/src/Test/Ghcid.hs
@@ -0,0 +1,208 @@
+
+-- | Test behavior of the executable, polling files for changes
+module Test.Ghcid(ghcidTest) where
+
+import Control.Concurrent.Extra
+import Control.Exception.Extra
+import Control.Monad.Extra
+import Data.Char
+import Data.List.Extra
+import System.Directory.Extra
+import System.IO.Extra
+import System.Time.Extra
+import Data.Version.Extra
+import System.Environment
+import System.Process.Extra
+import System.FilePath
+import System.Exit
+
+import Test.Tasty
+import Test.Tasty.HUnit
+
+import Ghcid
+import Language.Haskell.Ghcid.Util
+import Data.Functor
+import Prelude
+
+
+ghcidTest :: TestTree
+ghcidTest = testGroup "Ghcid test"
+    [basicTest
+    ,dotGhciTest
+    ,cabalTest
+    ,stackTest
+    ]
+
+
+freshDir :: IO a -> IO a
+freshDir act = withTempDir $ \tdir -> withCurrentDirectory tdir act
+
+copyDir :: FilePath -> IO a -> IO ()
+copyDir dir act = do
+    b <- doesDirectoryExist dir
+    if not b then putStrLn $ "Couldn't run test because test source is missing, " ++ dir else void $ do
+        withTempDir $ \tdir -> do
+            xs <- withCurrentDirectory dir $ listFilesRecursive "."
+            forM_ xs $ \x -> do
+                createDirectoryIfMissing True $ takeDirectory $ tdir </> x
+                copyFile (dir </> x) (tdir </> x)
+            withCurrentDirectory tdir act
+
+
+whenStack :: IO a -> IO ()
+whenStack act = do
+    v <- findExecutable "stack"
+    case v of
+        Nothing -> putStrLn "Couldn't run test because stack is missing"
+        Just _ -> void act
+
+
+withGhcid :: [String] -> (([String] -> IO ()) -> IO a) -> IO a
+withGhcid args script = do
+    chan <- newChan
+    let require want = do
+            t <- timeout 30 $ readChan chan
+            case t of
+                Nothing -> fail $ "Require failed to produce results in time, expected: " ++ show want
+                Just got -> assertApproxInfix want got
+            sleep =<< getModTimeResolution
+
+    let output = writeChan chan . filter (/= "") . map snd
+    done <- newBarrier
+    res <- bracket
+        (flip forkFinally (const $ signalBarrier done ()) $
+            withArgs (["--notitle","--no-status"]++args) $
+                mainWithTerminal (return (100, 50)) output)
+        killThread $ \_ -> script require
+    waitBarrier done
+    return res
+
+
+-- | Since different versions of GHCi give different messages, we only try to find what
+--   we require anywhere in the obtained messages, ignoring weird characters.
+assertApproxInfix :: [String] -> [String] -> IO ()
+assertApproxInfix want got = do
+    -- Spacing and quotes tend to be different on different GHCi versions
+    let simple = lower . filter (\x -> isLetter x || isDigit x || x == ':')
+        got2 = simple $ unwords got
+    all (`isInfixOf` got2) (map simple want) @?
+        "Expected " ++ show want ++ ", got " ++ show got
+
+
+write :: FilePath -> String -> IO ()
+write file x = do
+    print ("writeFile",file,x)
+    writeFile file x
+
+append :: FilePath -> String -> IO ()
+append file x = do
+    print ("appendFile",file,x)
+    appendFile file x
+
+rename :: FilePath -> FilePath -> IO ()
+rename from to = do
+    print ("renameFile",from,to)
+    renameFile from to
+
+
+
+---------------------------------------------------------------------
+-- ACTUAL TEST SUITE
+
+basicTest :: TestTree
+basicTest = testCase "Ghcid basic" $ freshDir $ do
+    write "Main.hs" "main = print 1"
+    withGhcid ["-cghci -fwarn-unused-binds Main.hs"] $ \require -> do
+        require [allGoodMessage]
+        write "Main.hs" "x"
+        require ["Main.hs:1:1"," Parse error:"]
+        write "Util.hs" "module Util where"
+        write "Main.hs" "import Util\nmain = print 1"
+        require [allGoodMessage]
+        write "Util.hs" "module Util where\nx"
+        require ["Util.hs:2:1","Parse error:"]
+        write "Util.hs" "module Util() where\nx = 1"
+        require ["Util.hs:2:1","Warning:","Defined but not used: `x'"]
+
+        -- check warnings persist properly
+        write "Main.hs" "import Util\nx"
+        require ["Main.hs:2:1","Parse error:"
+                ,"Util.hs:2:1","Warning:","Defined but not used: `x'"]
+        write "Main.hs" "import Util\nmain = print 2"
+        require ["Util.hs:2:1","Warning:","Defined but not used: `x'"]
+        write "Main.hs" "main = print 3"
+        require [allGoodMessage]
+        write "Main.hs" "import Util\nmain = print 4"
+        require ["Util.hs:2:1","Warning:","Defined but not used: `x'"]
+        write "Util.hs" "module Util where"
+        require [allGoodMessage]
+
+        -- check recursive modules work
+        write "Util.hs" "module Util where\nimport Main"
+        require ["imports form a cycle","Main.hs","Util.hs"]
+        write "Util.hs" "module Util where"
+        require [allGoodMessage]
+
+        ghcVer <- readVersion <$> systemOutput_ "ghc --numeric-version"
+
+        -- check renaming files works
+        when (ghcVer < makeVersion [8]) $ do
+            -- note that due to GHC bug #9648 and #11596 this doesn't work with newer GHC
+            -- see https://ghc.haskell.org/trac/ghc/ticket/11596
+            rename "Util.hs" "Util2.hs"
+            require ["Main.hs:1:8:","Could not find module `Util'"]
+            rename "Util2.hs" "Util.hs"
+            require [allGoodMessage]
+
+        -- after this point GHC bugs mean nothing really works too much
+
+
+dotGhciTest :: TestTree
+dotGhciTest = testCase "Ghcid .ghci" $ copyDir "test/foo" $ do
+    write "test.txt" ""
+    ignore $ void $ system "chmod go-w .ghci"
+    withGhcid ["--test=:test"] $ \require -> do
+        require [allGoodMessage]
+        sleep 1 -- time to write out the test
+        readFile "test.txt" >>= (@?= "X") -- the test writes out X
+        append "Test.hs" "\n"
+        require [allGoodMessage]
+        sleep 1 -- time to write out the test
+        readFile "test.txt" >>= (@?= "XX")
+        print =<< readFile ".ghci"
+        write ".ghci" ":set -fwarn-unused-imports\n:load Root Paths.hs Test"
+        require ["The import of Paths_foo is redundant"]
+        sleep 1 -- time to write out the test
+        readFile "test.txt" >>= (@?= "XX") -- but shouldn't run on warning
+
+
+cabalTest :: TestTree
+cabalTest = testCase "Ghcid Cabal" $ copyDir "test/bar" $ do
+    env <- getEnvironment
+    let db = ["--package-db=" ++ x | x <- maybe [] splitSearchPath $ lookup "GHC_PACKAGE_PATH" env]
+    (_, _, _, pid) <- createProcess $
+         (proc "cabal" $ "configure":db){env = Just $ filter ((/=) "GHC_PACKAGE_PATH" . fst) env}
+    ExitSuccess <- waitForProcess pid
+
+    withGhcid [] $ \require -> do
+        require [allGoodMessage]
+        orig <- readFile' "src/Literate.lhs"
+        append "src/Literate.lhs" "> x"
+        require ["src/Literate.lhs:5:3","Parse error:"]
+        write "src/Literate.lhs" orig
+        require [allGoodMessage]
+
+stackTest :: TestTree
+stackTest = testCase "Ghcid Stack" $ copyDir "test/bar" $ whenStack $ do
+    system_ "stack init"
+    createDirectoryIfMissing True ".stack-work"
+
+    withGhcid [] $ \require -> do
+        require [allGoodMessage]
+        append "src/Literate.lhs" "> x"
+        require ["src/Literate.lhs:5:3","Parse error:"]
+
+    withGhcid ["src/Boot.hs"] $ \require -> do
+        require [allGoodMessage]
+        writeFile "src/Boot.hs" "X"
+        require ["src/Boot.hs:1:1","Parse error:"]
diff --git a/src/Test/HighLevel.hs b/src/Test/HighLevel.hs
deleted file mode 100644
--- a/src/Test/HighLevel.hs
+++ /dev/null
@@ -1,106 +0,0 @@
--- | Test the high level library API
-module Test.HighLevel(highLevelTests) where
-
-import System.Directory
-import System.IO.Extra
-import System.Exit
-import System.FilePath
-import System.Environment
-import Control.Monad
-import System.Process
-
-import Test.Tasty
-import Test.Tasty.HUnit
-
-import Language.Haskell.Ghcid
-
-
-highLevelTests :: TestTree
-highLevelTests = testGroup "High Level tests"
-    [testStartRepl
-    ,testShowModules
-    ]
-
-testStartRepl :: TestTree
-testStartRepl = testCase "Start cabal repl" $
-    withTestProject $ \root -> do
-        (ghci,load) <- startGhci "cabal repl" (Just root) $ const putStrLn
-        stopGhci ghci
-        load @?=  [ Loading "B.C" (normalise "src/B/C.hs")
-                  , Loading "A" (normalise "src/A.hs")
-                  ]
-
-testShowModules :: TestTree
-testShowModules = testCase "Show Modules" $
-    withTestProject $ \root -> do
-        (ghci,_) <- startGhci "cabal repl" (Just root) $ const putStrLn
-        mods <- showModules ghci
-        stopGhci ghci
-        mods @?= [("A",normalise "src/A.hs"),("B.C",normalise "src/B/C.hs")]
-
-
-testFiles :: [(FilePath, [String])]
-testFiles =
-    [(projectName <.> "cabal",
-        ["name: " ++ projectName
-        ,"version:0.1"
-        ,"cabal-version:  >= 1.8"
-        ,"build-type:     Simple"
-        ,""
-        ,"library"
-        ,"  hs-source-dirs:  src"
-        ,"  exposed-modules: A"
-        ,"  other-modules:  B.C"
-        ,"  build-depends:  base"
-        ,""
-        ,"executable BWTest"
-        ,"  hs-source-dirs:  src"
-        ,"  main-is:         Main.hs"
-        ,"  other-modules:  B.D"
-        ,"  build-depends:  base"
-        ,"  ghc-options: -dynamic"
-        ,""
-        ,"test-suite BWTest-test"
-        ,"  type:            exitcode-stdio-1.0"
-        ,"  hs-source-dirs:  test"
-        ,"  main-is:         Main.hs"
-        ,"  other-modules:  TestA"
-        ,"  build-depends:  base"])
-    ,("Setup.hs",
-        ["#!/usr/bin/env runhaskell"
-        ,"import Distribution.Simple"
-        ,"main :: IO ()"
-        ,"main = defaultMain"])
-    ,("src/A.hs",
-        ["module A where"
-        ,"fA = undefined"])
-    ,("src/B/C.hs",
-        ["module B.C where"
-        ,"fC = undefined"])
-    ,("src/B/D.hs",
-        ["module B.D where"
-        ,"fD = undefined"])
-    ,("src/Main.hs",
-        ["module Main where"
-        ,"main = undefined"])
-    ,("test/Main.hs",
-        ["module Main where"
-        ,"main = undefined"])
-    ,("test/TestA.hs",
-        ["module TestA where"
-        ,"fTA = undefined"])
-    ]
-
-projectName = "BWTest"
-
-withTestProject :: (FilePath -> IO a) -> IO a
-withTestProject act = withTempDir $ \dir -> do
-    forM_ testFiles $ \(name,contents) -> do
-        createDirectoryIfMissing True $ takeDirectory $ dir </> name
-        writeFile (dir </> name) $ unlines contents
-    env <- getEnvironment
-    let db = ["--package-db=" ++ x | x <- maybe [] splitSearchPath $ lookup "GHC_PACKAGE_PATH" env]
-    (_, _, _, pid) <- createProcess $
-        (proc "cabal" $ "configure":db){env = Just $ filter ((/=) "GHC_PACKAGE_PATH" . fst) env, cwd = Just dir}
-    ExitSuccess <- waitForProcess pid
-    act dir
diff --git a/src/Test/Parser.hs b/src/Test/Parser.hs
--- a/src/Test/Parser.hs
+++ b/src/Test/Parser.hs
@@ -1,7 +1,5 @@
 -- | Test the message parser
-module Test.Parser
-  ( parserTests
-  )where
+module Test.Parser(parserTests) where
 
 import Test.Tasty
 import Test.Tasty.HUnit
@@ -11,40 +9,79 @@
 
 
 parserTests :: TestTree
-parserTests=testGroup "Parser tests"
-  [ testShowModules
-  , testParseLoad
-  ]
-
-testShowModules :: TestTree
-testShowModules =  testCase "Show Modules" $ parseShowModules [
-    "Main             ( src/Main.hs, interpreted )",
-    "AI.Neural.WiscDigit ( src/AI/Neural/WiscDigit.hs, interpreted )"
-    ] @?= [("Main","src/Main.hs"),("AI.Neural.WiscDigit","src/AI/Neural/WiscDigit.hs")]
+parserTests = testGroup "Parser tests"
+    [testParseShowModules
+    ,testParseLoad
+    ,testParseLoadSpans
+    ]
 
+testParseShowModules :: TestTree
+testParseShowModules = testCase "Show Modules" $ parseShowModules
+    ["Main             ( src/Main.hs, interpreted )"
+    ,"AI.Neural.WiscDigit ( src/AI/Neural/WiscDigit.hs, interpreted )"
+    ] @?=
+    [("Main","src/Main.hs")
+    ,("AI.Neural.WiscDigit","src/AI/Neural/WiscDigit.hs")
+    ]
 
 testParseLoad :: TestTree
-testParseLoad = testGroup "Load Parsing"
-  [ testCase "output1" $ parseLoad output1 @?=
-    [ Loading "GHCi" "GHCi.hs"
-    , Message {loadSeverity = Error, loadFile = "GHCi.hs", loadFilePos = (70,1), loadMessage = ["GHCi.hs:70:1: Parse error: naked expression at top level"]}
-    , Message {loadSeverity = Error, loadFile = "GHCi.hs", loadFilePos = (72,13), loadMessage = ["GHCi.hs:72:13:","    No instance for (Num ([String] -> [String]))","      arising from the literal `1'","    Possible fix:","      add an instance declaration for (Num ([String] -> [String]))","    In the expression: 1","    In an equation for `parseLoad': parseLoad = 1"]}
-    , Message {loadSeverity = Warning, loadFile = "GHCi.hs", loadFilePos = (81,1), loadMessage = ["GHCi.hs:81:1: Warning: Defined but not used: `foo'"]}
-    , Message {loadSeverity = Warning, loadFile = "C:\\GHCi.hs", loadFilePos = (82,1), loadMessage = ["C:\\GHCi.hs:82:1: warning: Defined but not used: \8216foo\8217"]}
+testParseLoad = testCase "Load Parsing" $ parseLoad
+    ["[1 of 2] Compiling GHCi             ( GHCi.hs, interpreted )"
+    ,"GHCi.hs:70:1: Parse error: naked expression at top level"
+    ,"GHCi.hs:72:13:"
+    ,"    No instance for (Num ([String] -> [String]))"
+    ,"      arising from the literal `1'"
+    ,"    Possible fix:"
+    ,"      add an instance declaration for (Num ([String] -> [String]))"
+    ,"    In the expression: 1"
+    ,"    In an equation for `parseLoad': parseLoad = 1"
+    ,"GHCi.hs:81:1: Warning: Defined but not used: `foo'"
+    ,"C:\\GHCi.hs:82:1: warning: Defined but not used: \8216foo\8217" -- GHC 7.12 uses lowercase
+    ,"src\\Haskell.hs:4:23:"
+    ,"    Warning: {-# SOURCE #-} unnecessary in import of  `Boot'"
+    ,"src\\Boot.hs-boot:2:8:"
+    ,"    File name does not match module name:"
+    ,"    Saw: `BootX'"
+    ,"    Expected: `Boot'"
+    ] @?=
+    [Loading "GHCi" "GHCi.hs"
+    ,Message {loadSeverity = Error, loadFile = "GHCi.hs", loadFilePos = (70,1), loadMessage = ["GHCi.hs:70:1: Parse error: naked expression at top level"]}
+    ,Message {loadSeverity = Error, loadFile = "GHCi.hs", loadFilePos = (72,13), loadMessage = ["GHCi.hs:72:13:","    No instance for (Num ([String] -> [String]))","      arising from the literal `1'","    Possible fix:","      add an instance declaration for (Num ([String] -> [String]))","    In the expression: 1","    In an equation for `parseLoad': parseLoad = 1"]}
+    ,Message {loadSeverity = Warning, loadFile = "GHCi.hs", loadFilePos = (81,1), loadMessage = ["GHCi.hs:81:1: Warning: Defined but not used: `foo'"]}
+    ,Message {loadSeverity = Warning, loadFile = "C:\\GHCi.hs", loadFilePos = (82,1), loadMessage = ["C:\\GHCi.hs:82:1: warning: Defined but not used: \8216foo\8217"]}
+    ,Message {loadSeverity = Warning, loadFile = "src\\Haskell.hs", loadFilePos = (4,23), loadMessage = ["src\\Haskell.hs:4:23:","    Warning: {-# SOURCE #-} unnecessary in import of  `Boot'"]}
+    ,Message {loadSeverity = Error, loadFile = "src\\Boot.hs-boot", loadFilePos = (2,8), loadMessage = ["src\\Boot.hs-boot:2:8:","    File name does not match module name:","    Saw: `BootX'","    Expected: `Boot'"]}
     ]
-  ]
 
-output1 :: [String]
-output1=
-  [ "[1 of 2] Compiling GHCi             ( GHCi.hs, interpreted )"
-  , "GHCi.hs:70:1: Parse error: naked expression at top level"
-  , "GHCi.hs:72:13:"
-  , "    No instance for (Num ([String] -> [String]))"
-  , "      arising from the literal `1'"
-  , "    Possible fix:"
-  , "      add an instance declaration for (Num ([String] -> [String]))"
-  , "    In the expression: 1"
-  , "    In an equation for `parseLoad': parseLoad = 1"
-  , "GHCi.hs:81:1: Warning: Defined but not used: `foo'"
-  , "C:\\GHCi.hs:82:1: warning: Defined but not used: \8216foo\8217" -- GHC 7.12 uses lowercase
-  ]
+-- | Test when error messages include spans (-ferror-spans)
+testParseLoadSpans :: TestTree
+testParseLoadSpans = testCase "Load Parsing when -ferror-spans is enabled" $ parseLoad
+    ["[1 of 2] Compiling GHCi             ( GHCi.hs, interpreted )"
+    ,"GHCi.hs:70:1-2: Parse error: naked expression at top level"
+    ,"GHCi.hs:72:13-14:"
+    ,"    No instance for (Num ([String] -> [String]))"
+    ,"      arising from the literal `1'"
+    ,"    Possible fix:"
+    ,"      add an instance declaration for (Num ([String] -> [String]))"
+    ,"    In the expression: 1"
+    ,"    In an equation for `parseLoad': parseLoad = 1"
+    ,"GHCi.hs:81:1-15: Warning: Defined but not used: `foo'"
+    ,"C:\\GHCi.hs:82:1-17: warning: Defined but not used: \8216foo\8217" -- GHC 7.12 uses lowercase
+    ,"src\\Haskell.hs:4:23-24:"
+    ,"    Warning: {-# SOURCE #-} unnecessary in import of  `Boot'"
+    ,"src\\Boot.hs-boot:2:8-5:"
+    ,"    File name does not match module name:"
+    ,"    Saw: `BootX'"
+    ,"    Expected: `Boot'"
+    ,"/src/TrieSpec.hs:(192,7)-(193,76): Warning:"
+    ,"    A do-notation statement discarded a result of type ‘[()]’"
+    ] @?=
+    [Loading "GHCi" "GHCi.hs"
+    ,Message {loadSeverity = Error, loadFile = "GHCi.hs", loadFilePos = (70,1), loadMessage = ["GHCi.hs:70:1-2: Parse error: naked expression at top level"]}
+    ,Message {loadSeverity = Error, loadFile = "GHCi.hs", loadFilePos = (72,13), loadMessage = ["GHCi.hs:72:13-14:","    No instance for (Num ([String] -> [String]))","      arising from the literal `1'","    Possible fix:","      add an instance declaration for (Num ([String] -> [String]))","    In the expression: 1","    In an equation for `parseLoad': parseLoad = 1"]}
+    ,Message {loadSeverity = Warning, loadFile = "GHCi.hs", loadFilePos = (81,1), loadMessage = ["GHCi.hs:81:1-15: Warning: Defined but not used: `foo'"]}
+    ,Message {loadSeverity = Warning, loadFile = "C:\\GHCi.hs", loadFilePos = (82,1), loadMessage = ["C:\\GHCi.hs:82:1-17: warning: Defined but not used: \8216foo\8217"]}
+    ,Message {loadSeverity = Warning, loadFile = "src\\Haskell.hs", loadFilePos = (4,23), loadMessage = ["src\\Haskell.hs:4:23-24:","    Warning: {-# SOURCE #-} unnecessary in import of  `Boot'"]}
+    ,Message {loadSeverity = Error, loadFile = "src\\Boot.hs-boot", loadFilePos = (2,8), loadMessage = ["src\\Boot.hs-boot:2:8-5:","    File name does not match module name:","    Saw: `BootX'","    Expected: `Boot'"]}
+    ,Message {loadSeverity = Warning, loadFile = "/src/TrieSpec.hs", loadFilePos = (192,7), loadMessage = ["/src/TrieSpec.hs:(192,7)-(193,76): Warning:","    A do-notation statement discarded a result of type ‘[()]’"]}
+    ]
diff --git a/src/Test/Polling.hs b/src/Test/Polling.hs
deleted file mode 100644
--- a/src/Test/Polling.hs
+++ /dev/null
@@ -1,137 +0,0 @@
-{-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
-
--- | Test behavior of the executable, polling files for changes
-module Test.Polling(pollingTest) where
-
-import Control.Concurrent
-import Control.Exception.Extra
-import Control.Monad
-import Data.Char
-import Data.List.Extra
-import System.FilePath
-import System.Directory.Extra
-import System.Process
-import System.Console.CmdArgs
-import System.Time.Extra
-
-import Test.Tasty
-import Test.Tasty.HUnit
-
-import Ghcid
-import Session
-import Wait
-import Language.Haskell.Ghcid.Util
-
-pollingTest :: TestTree
-pollingTest  = testCase "Scripted Test" $ do
-    setVerbosity Loud
-    tdir <- fmap (</> ".ghcid") getTemporaryDirectory
-    try_ $ removeDirectoryRecursive tdir
-    createDirectoryIfMissing True tdir
-    withCurrentDirectory tdir $ do
-        ref <- newEmptyMVar
-        let require predi = do
-                res <- takeMVarDelay ref 5
-                predi res
-                outStr "."
-                sleep 1
-        -- t <- myThreadId
-        writeFile "Main.hs" "main = print 1"
-        writeFile ".ghci" ":set -fwarn-unused-binds \n:load Main"
-        -- otherwise GHC warns about .ghci being accessible by others
-        try_ $ system "chmod og-w . .ghci"
-
-        withSession $ \session -> withWaiterPoll $ \waiter -> bracket (
-          forkIO $ runGhcid session waiter [] [] "ghci" [] Nothing False False (return (100, 50)) False $ \msg ->
-            unless (isLoading $ map snd msg) $ putMVarNow ref $ map snd msg
-          ) killThread $ \_ -> do
-            require requireAllGood
-            testScript require
-            outStrLn "\nSuccess"
-
-isLoading :: [String] -> Bool
-isLoading ("Reloading...":_) = True
-isLoading (x:_) | "Loading" `isPrefixOf` x && "..." `isSuffixOf` x = True
-isLoading _ = False
-
-putMVarNow :: MVar a -> a -> IO ()
-putMVarNow ref x = do
-    b <- tryPutMVar ref x
-    unless b $ error "Had a message and a new one arrived"
-
-takeMVarDelay :: MVar a -> Double -> IO a
-takeMVarDelay _ i | i <= 0 = error "timed out"
-takeMVarDelay x i = do
-    b <- tryTakeMVar x
-    case b of
-        Nothing -> sleep 0.1 >> takeMVarDelay x (i-0.1)
-        Just v -> return v
-
---(===) :: (Eq a, Show a) => a -> a -> IO ()
---(===) a b = unless (a == b) $ error $ "Mismatch\nLHS: " ++ show a ++ "\nRHS: " ++ show b
-
--- | The all good message
-requireAllGood :: [String] -> IO ()
-requireAllGood got = map (take (length allGoodMessage)) (filter (not . null) got) @?= [allGoodMessage]
-
---requireNonIndents :: [String] -> [String] -> IO ()
---requireNonIndents want got = [x | x@(c:_) <- got, not $ isSpace c] @?= want
---
---requirePrefix :: [String] -> [String] -> IO ()
---requirePrefix want got = take (length want) got @?= want
-
--- | Since different versions of GHCi give different messages, we only try to find what we require anywhere in the obtained messages
-requireSimilar :: [String] -> [String] -> IO ()
-requireSimilar want got = let
-  allGot = ignoreSpacesAndWeird $ concat got
-  in all (`isInfixOf` allGot) (map ignoreSpacesAndWeird want) @? (show (filter (not . null) got) ++ " does not contain " ++ show want)
-
--- | Spacing and quotes tend to be different on different GHCi versions
-ignoreSpacesAndWeird :: String -> String
-ignoreSpacesAndWeird = lower . filter (\x -> isLetter x || isDigit x || x == ':')
-
----------------------------------------------------------------------
--- ACTUAL TEST SUITE
-
-testScript :: (([String] -> IO ()) -> IO ()) -> IO ()
-testScript require = do
-    writeFile <- return $ \name x -> do print ("writeFile",name,x); writeFile name x
-    renameFile <- return $ \from to -> do print ("renameFile",from,to); renameFile from to
-
-    writeFile "Main.hs" "x"
-    require $ requireSimilar ["Main.hs:1:1"," Parse error: naked expression at top level"]
-    writeFile "Util.hs" "module Util where"
-    writeFile "Main.hs" "import Util\nmain = print 1"
-    require requireAllGood
-    writeFile "Util.hs" "module Util where\nx"
-    require $ requireSimilar ["Util.hs:2:1","Parse error: naked expression at top level"]
-    writeFile "Util.hs" "module Util() where\nx = 1"
-    require $ requireSimilar ["Util.hs:2:1","Warning: Defined but not used: `x'"]
-
-    -- check warnings persist properly
-    writeFile "Main.hs" "import Util\nx"
-    require $ requireSimilar ["Main.hs:2:1","Parse error: naked expression at top level"
-                             ,"Util.hs:2:1","Warning: Defined but not used: `x'"]
-    writeFile "Main.hs" "import Util\nmain = print 2"
-    require $ requireSimilar ["Util.hs:2:1","Warning: Defined but not used: `x'"]
-    writeFile "Main.hs" "main = print 3"
-    require requireAllGood
-    writeFile "Main.hs" "import Util\nmain = print 4"
-    require $ requireSimilar ["Util.hs:2:1","Warning: Defined but not used: `x'"]
-    writeFile "Util.hs" "module Util where"
-    require requireAllGood
-
-    -- check recursive modules work
-    writeFile "Util.hs" "module Util where\nimport Main"
-    require $ requireSimilar ["imports form a cycle","Main.hs","Util.hs"]
-    writeFile "Util.hs" "module Util where"
-    require requireAllGood
-
-    -- check renaming files works
-    -- note that due to GHC bug #9648 we can't save down a new file
-    renameFile "Util.hs" "Util2.hs"
-    require $ requireSimilar ["Main.hs:1:8:","Could not find module `Util'"]
-    renameFile "Util2.hs" "Util.hs"
-    require requireAllGood
-    -- after this point GHC bugs mean nothing really works too much
-
diff --git a/src/Test/Util.hs b/src/Test/Util.hs
--- a/src/Test/Util.hs
+++ b/src/Test/Util.hs
@@ -1,7 +1,5 @@
 -- | Test utility functions
-module Test.Util
-  ( utilsTests
-  ) where
+module Test.Util(utilsTests) where
 
 import Test.Tasty
 import Test.Tasty.HUnit
@@ -9,21 +7,21 @@
 import Language.Haskell.Ghcid.Util
 
 utilsTests :: TestTree
-utilsTests=testGroup "Utility tests"
-  [ dropPrefixTests
-  , chunksOfWordTests
-  ]
+utilsTests = testGroup "Utility tests"
+    [dropPrefixTests
+    ,chunksOfWordTests
+    ]
 
 dropPrefixTests :: TestTree
 dropPrefixTests = testGroup "dropPrefix"
-  [ testCase "Prefix not found" $ dropPrefixRepeatedly "prefix" "string"  @?= "string"
-  , testCase "Empty prefix" $ dropPrefixRepeatedly "" "string" @?= "string"
-  , testCase "Prefix found once" $ dropPrefixRepeatedly "str" "string" @?= "ing"
-  , testCase "Prefix found twice" $ dropPrefixRepeatedly "str" "strstring" @?= "ing"
-  ]
+    [testCase "Prefix not found" $ dropPrefixRepeatedly "prefix" "string"  @?= "string"
+    ,testCase "Empty prefix" $ dropPrefixRepeatedly "" "string" @?= "string"
+    ,testCase "Prefix found once" $ dropPrefixRepeatedly "str" "string" @?= "ing"
+    ,testCase "Prefix found twice" $ dropPrefixRepeatedly "str" "strstring" @?= "ing"
+    ]
 
 chunksOfWordTests :: TestTree
 chunksOfWordTests = testGroup "chunksOfWord"
-  [ testCase "Max 0" $ chunksOfWord 4 0 "ab cd efgh" @?= ["ab c","d ef","gh"]
-  , testCase "Max 2" $ chunksOfWord 4 2 "ab cd efgh" @?= ["ab ","cd ","efgh"]
-  ]
+    [testCase "Max 0" $ chunksOfWord 4 0 "ab cd efgh" @?= ["ab c","d ef","gh"]
+    ,testCase "Max 2" $ chunksOfWord 4 2 "ab cd efgh" @?= ["ab ","cd ","efgh"]
+    ]
