diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -64,7 +64,7 @@
 
 - Make `RunTests.hs` with `{-# OPTIONS_GHC -F -pgmF test-karya-generate #-}`
 
-- Make a `test-suite` like in `example`.
+- Add a `test-suite` stanza to the cabal file, like in `example`.
 
 - `cabal test`.  You can rerun the tests with `dist/build/test/test`, run
 only `M_test` with `dist/build/test/test M_test` or pass `--help` for more
diff --git a/src/EL/Private/Cpu.hs b/src/EL/Private/Cpu.hs
new file mode 100644
--- /dev/null
+++ b/src/EL/Private/Cpu.hs
@@ -0,0 +1,41 @@
+{-# LANGUAGE OverloadedStrings #-}
+-- | Get info about CPUs.
+module EL.Private.Cpu where
+import qualified Data.Set as Set
+import qualified Data.Text as Text
+import Data.Text (Text)
+import qualified Data.Text.IO as Text.IO
+
+import qualified System.Environment as Environment
+import qualified System.Info
+import qualified System.Process as Process
+
+import qualified Text.Read as Read
+
+
+-- | Get number of physical cores.  This can be overidden with a CPUS
+-- environment variable.  This is useful if you are running on a VM in CI
+-- and don't agree with how many cores it claims to have.
+physicalCores :: IO Int
+physicalCores = maybe getPhysicalCores return =<< envCores
+
+getPhysicalCores :: IO Int
+getPhysicalCores = case System.Info.os of
+    "darwin" -> read <$>
+        Process.readProcess "/usr/sbin/sysctl" ["-n", "hw.physicalcpu"] ""
+    "linux" -> linuxPhysicalCores <$> Text.IO.readFile "/proc/cpuinfo"
+    _ -> error $ "unknown platform: " ++ System.Info.os
+
+envCores :: IO (Maybe Int)
+envCores = (Read.readMaybe =<<) <$> Environment.lookupEnv "CPUS"
+
+-- | Parse /proc/cpuinfo for physical cpu count.
+linuxPhysicalCores :: Text -> Int
+linuxPhysicalCores = length . unique . map cpu . Text.splitOn "\n\n"
+    where
+    -- unique pairs of (physical id, core id)
+    cpu = filter (\s -> any (`Text.isPrefixOf` s) ["physical id", "core id"])
+        . Text.lines
+
+unique :: Ord a => [a] -> [a]
+unique = Set.toList . Set.fromList
diff --git a/src/EL/Test/RunTests.hs b/src/EL/Test/RunTests.hs
--- a/src/EL/Test/RunTests.hs
+++ b/src/EL/Test/RunTests.hs
@@ -5,7 +5,6 @@
 -- | Run tests.  This is meant to be invoked via a main module generated by
 -- "EL.Test.GenerateRunTests".
 module EL.Test.RunTests where
-import qualified Control.Concurrent as Concurrent
 import qualified Control.Concurrent.Async as Async
 import qualified Control.Concurrent.Chan as Chan
 import qualified Control.Concurrent.MVar as MVar
@@ -14,6 +13,7 @@
 
 import qualified Data.List as List
 import qualified Data.Maybe as Maybe
+import Data.Monoid ((<>))
 import qualified Data.Set as Set
 import qualified Data.Text as Text
 import qualified Data.Text.IO as Text.IO
@@ -33,6 +33,7 @@
 
 import qualified Text.Read as Read
 
+import qualified EL.Private.Cpu as Cpu
 import qualified EL.Private.File as File
 import qualified EL.Private.Process as EL.Process
 import qualified EL.Private.Regex as Regex
@@ -89,7 +90,7 @@
         \ This is probably just for cabal, which can't wrap tests in a shell\
         \ script."
     , GetOpt.Option [] ["jobs"] (GetOpt.ReqArg (Jobs . parseJobs) "1")
-        "Number of parallel jobs, or 'auto' for Concurrent.getNumCapabilities."
+        "Number of parallel jobs, or 'auto' for physical CPU count."
     , GetOpt.Option [] ["list"] (GetOpt.NoArg List) "display but don't run"
     , GetOpt.Option [] ["output"] (GetOpt.ReqArg Output "path")
         "Path to a directory to put output logs, if not given output goes to\
@@ -133,9 +134,9 @@
     when (mbOutputDir == Nothing && CheckOutput `elem` flags) $
         quitWithUsage [] ["--check-output requires --output"]
     when (ClearDirs `elem` flags) $ do
-        Directory.createDirectoryIfMissing True Testing.tmpBaseDir
         clearDirectory Testing.tmpBaseDir
         whenJust mbOutputDir clearDirectory
+    Directory.createDirectoryIfMissing True Testing.tmpBaseDir
     if  | List `elem` flags -> do
             mapM_ Text.IO.putStrLn $ List.sort $ map testName $
                 if null regexes then allTests else matches
@@ -152,7 +153,7 @@
 
 getJobs :: Jobs -> IO Int
 getJobs (NJobs n) = return n
-getJobs Auto = Concurrent.getNumCapabilities
+getJobs Auto = Cpu.physicalCores
 
 runOutput :: FilePath -> Int -> [Test] -> Bool -> IO Bool
 runOutput outputDir jobs tests check = do
@@ -307,7 +308,7 @@
 -- | Empty the directory, but don't remove it entirely, in case it's /tmp or
 -- something.
 clearDirectory :: FilePath -> IO ()
-clearDirectory dir = mapM_ rm =<< File.list dir
+clearDirectory dir = void . File.ignoreEnoent $ mapM_ rm =<< File.list dir
     where
     -- Let's not go all the way to Directory.removePathForcibly.
     rm fn = Directory.doesDirectoryExist fn >>= \isDir -> if isDir
@@ -328,24 +329,45 @@
 readFileEmpty = fmap (fromMaybe "") . File.ignoreEnoent . Text.Lazy.IO.readFile
 
 extractStats :: Text.Lazy.Text -> ([Text], Int, Int, Int)
+    -- ^ (failureContext, failures, checks, tests)
 extractStats = collect . drop 1 . Seq.split_before isTest . Text.Lazy.lines
     where
     collect tests = (failures, length failures, length extracted, length tests)
         where
         failures = Maybe.catMaybes extracted
-        extracted = concatMap extractFailures tests
+        extracted = concatMap (extractFailures . drop 1) tests
     isTest = ((Text.Lazy.fromStrict (metaPrefix <> " run-test"))
         `Text.Lazy.isPrefixOf`)
 
--- | Collect lines before each failure for context.
+-- | Collect lines before and after each failure for context.
+--
+-- I collect before because that's where debugging info about that test is
+-- likely to show up, and I collect after because the failure output may
+-- have multiple lines.
+--
+-- It can be confusing that I can get the failure lines of the previous test as
+-- context for the current one.  To fix that I'd have to explicitly mark all
+-- lines of the failure, or put some ending marker afterwards.  It's not hard
+-- but maybe not worth it.
 extractFailures :: [Text.Lazy.Text] -> [Maybe Text]
-extractFailures =
-    mapMaybe fmt . Seq.split_after (\x -> isFailure x || isSuccess x)
+    -- ^ Just context for a failure, Nothing for a success.
+extractFailures = map convert . toChunks
     where
-    fmt lines
-        | maybe False isFailure (Seq.last lines) =
-            Just $ Just $ Text.Lazy.toStrict (Text.Lazy.unlines lines)
-        | maybe False isSuccess (Seq.last lines) = Just Nothing
+    convert (pre, test, post)
+        | isFailure test = Just $ Text.Lazy.toStrict $ Text.Lazy.unlines $
+            pre ++ [test] ++ post
         | otherwise = Nothing
+    toChunks [] = []
+    toChunks lines
+        | test == "" = []
+        | otherwise = (pre, test, post) : toChunks rest2
+        where
+        (pre, rest1) = break isTest lines
+        (test, rest2) = case rest1 of
+            x : xs -> (x, xs)
+            [] -> ("", [])
+        post = takeWhile (not . isTest) rest2
+
+    isTest s = isFailure s || isSuccess s
     isFailure = ("__-> " `Text.Lazy.isPrefixOf`)
     isSuccess = ("++-> " `Text.Lazy.isPrefixOf`)
diff --git a/src/EL/Test/Testing.hs b/src/EL/Test/Testing.hs
--- a/src/EL/Test/Testing.hs
+++ b/src/EL/Test/Testing.hs
@@ -102,7 +102,7 @@
 checkVal :: Show a => HasCallStack => a -> (a -> Bool) -> IO Bool
 checkVal val f
     | f val = success $ "ok: " <> pshowt val
-    | otherwise = failure $ "failed:" <> pshowt val
+    | otherwise = failure $ "failed: " <> pshowt val
 
 -- * metadata
 
diff --git a/src/Global.hs b/src/Global.hs
--- a/src/Global.hs
+++ b/src/Global.hs
@@ -1,5 +1,6 @@
 module Global (
     when, forever, unless, void, Map, mapMaybe, fromMaybe
+    , (<>)
     , Text, txt, untxt, showt
     , whenJust, concatMapM
 ) where
@@ -7,6 +8,7 @@
 import Control.Monad (when, forever, unless, void)
 import Data.Map (Map)
 import Data.Maybe (mapMaybe, fromMaybe)
+import Data.Monoid ((<>))
 import qualified Data.Text as Text
 import Data.Text (Text)
 
diff --git a/test-karya.cabal b/test-karya.cabal
--- a/test-karya.cabal
+++ b/test-karya.cabal
@@ -1,5 +1,5 @@
 name: test-karya
-version: 0.0.1
+version: 0.0.2
 cabal-version: >=1.10
 build-type: Simple
 synopsis: Testing framework.
@@ -33,7 +33,7 @@
     build-depends:
         Diff
         , QuickCheck
-        , base >= 4.9 && < 4.12
+        , base >= 4.9 && < 5
         , bytestring
         , containers
         , data-ordlist
@@ -62,6 +62,7 @@
     -- Now I just copy paste the dependencies between the library and the
     -- executable as is traditional with cabal, and it seems to work.
     other-modules:
+        EL.Private.Cpu
         EL.Private.ExtractHs
         EL.Private.Map
         EL.Private.PPrint
