packages feed

sydtest 0.13.0.0 → 0.13.0.1

raw patch · 6 files changed

+1102/−231 lines, 6 filesdep +vectordep −Diffdep −split

Dependencies added: vector

Dependencies removed: Diff, split

Files

CHANGELOG.md view
@@ -1,5 +1,14 @@ # Changelog +## [0.13.0.1] - 2023-01-13++### Changed++* Replaced the diffing algorithm by a custom one.+  This has sped up diffing at least 100x and let us get rid of the `Diff` and `split` dependencies.+* Reworded 'likely not flaky' to 'does not look flaky' to be more technically+  accurate as we know nothing about the likelihood of flakiness.+ ## [0.13.0.0] - 2022-10-14  ### Changed
+ src/Test/Syd/Diff.hs view
@@ -0,0 +1,527 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# OPTIONS_GHC -Wno-unused-imports -Werror=name-shadowing #-}++module Test.Syd.Diff+  ( -- * Diffing+    Diff,+    PolyDiff (..),+    getTextDiff,+    getStringDiff,+    getGroupedStringDiff,+    getVectorDiff,+    getGroupedVectorDiff,+    getVectorDiffBy,+    getGroupedVectorDiffBy,++    -- ** Internals+    Edit (..),+    getEditScript,+    getEditScriptBy,+    computeDiffFromEditScript,+    computeGroupedDiffFromEditScript,++    -- ** Backwards compatibility with @Diff@+    getDiff,+    getDiffBy,+    getGroupedDiff,+    getGroupedDiffBy,+  )+where++import Control.Monad+import Control.Monad.ST+import Data.DList (DList)+import qualified Data.DList as DList+import Data.Maybe (fromJust)+import Data.STRef+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.Array as TA+import Data.Vector (Vector, (!))+import qualified Data.Vector as V+import Data.Vector.Mutable (MVector)+import qualified Data.Vector.Mutable as MV++type Diff a = PolyDiff a a++mapDiff :: (a -> b) -> Diff a -> Diff b+mapDiff f = bimapPolyDiff f f++-- | A value is either from the 'First' list, the 'Second' or from 'Both'.+-- 'Both' contains both the left and right values, in case you are using a form+-- of equality that doesn't check all data (for example, if you are using a+-- newtype to only perform equality on side of a tuple).+data PolyDiff a b = First a | Second b | Both a b+  deriving (Show, Eq)++bimapPolyDiff :: (a -> c) -> (b -> d) -> PolyDiff a b -> PolyDiff c d+bimapPolyDiff f g = \case+  First a -> First (f a)+  Second b -> Second (g b)+  Both a b -> Both (f a) (g b)++-- |+--+-- For backward compatibility with 'Diff', use more specific functions if you can.+getDiff :: Eq a => [a] -> [a] -> [Diff a]+getDiff = getDiffBy (==)++-- |+--+-- For backward compatibility with 'Diff', use more specific functions if you can.+getDiffBy :: (a -> b -> Bool) -> [a] -> [b] -> [PolyDiff a b]+getDiffBy eq as bs = V.toList (getVectorDiffBy eq (V.fromList as) (V.fromList bs))++-- |+--+-- For backward compatibility with 'Diff', use more specific functions if you can.+getGroupedDiff :: Eq a => [a] -> [a] -> [Diff [a]]+getGroupedDiff = getGroupedDiffBy (==)++-- |+--+-- For backward compatibility with 'Diff', use more specific functions if you can.+getGroupedDiffBy :: (a -> b -> Bool) -> [a] -> [b] -> [PolyDiff [a] [b]]+getGroupedDiffBy eq as bs = V.toList (V.map (bimapPolyDiff V.toList V.toList) (getGroupedVectorDiffBy eq (V.fromList as) (V.fromList bs)))++-- | Text diff+--+-- Uses pack and unpack, so does not roundtrip.+-- It uses pack and unpack because 'Text' is not the same as @Vector Char@;+-- You can't index a text in O(1) time, it takes O(n) time.+getTextDiff :: Text -> Text -> Vector (Diff Text)+getTextDiff expected actual = V.map (mapDiff packFromVector) $ getGroupedVectorDiff (unpackToVector expected) (unpackToVector actual)+  where+    packFromVector :: Vector Char -> Text+    packFromVector = T.pack . V.toList+    unpackToVector :: Text -> Vector Char+    unpackToVector = V.fromList . T.unpack++-- | 'String' diff+--+-- You probably want to use 'getTextDiff' with packed strings instead, but this+-- function doesn't have the roundtripping problem that 'getTextDiff' has.+getStringDiff :: String -> String -> [Diff Char]+getStringDiff actual expected = V.toList (getVectorDiff (V.fromList actual) (V.fromList expected))++-- | Grouped 'String' diff+--+-- Like 'getStringDiff' but with entire strings instead of individual characters.+getGroupedStringDiff :: String -> String -> [Diff String]+getGroupedStringDiff actual expected = V.toList $ V.map (mapDiff V.toList) $ getGroupedVectorDiff (V.fromList actual) (V.fromList expected)++-- | Diff two vectors+--+-- Prefer 'getGroupedVectorDiff' for performance reasons.+getVectorDiff :: Eq a => Vector a -> Vector a -> Vector (Diff a)+getVectorDiff = getVectorDiffBy (==)++-- | Diff two vectors with different types using a custom equality operator+--+-- Prefer 'getGroupedVectorDiffBy' for performance reasons.+getVectorDiffBy :: forall a b. (a -> b -> Bool) -> Vector a -> Vector b -> Vector (PolyDiff a b)+getVectorDiffBy eq old new = computeDiffFromEditScript old new (getEditScriptBy eq old new)++-- | Diff two vectors with grouped results+getGroupedVectorDiff :: Eq a => Vector a -> Vector a -> Vector (Diff (Vector a))+getGroupedVectorDiff = getGroupedVectorDiffBy (==)++-- | Diff two vectors with grouped results using a custom equality operator+getGroupedVectorDiffBy :: forall a b. (a -> b -> Bool) -> Vector a -> Vector b -> Vector (PolyDiff (Vector a) (Vector b))+getGroupedVectorDiffBy eq old new = computeGroupedDiffFromEditScript old new (getEditScriptBy eq old new)++-- | Compute the edit script to turn a given vector into the second given vector+getEditScript :: forall a. Eq a => Vector a -> Vector a -> Vector Edit+getEditScript = getEditScriptBy (==)++-- | Compute the edit script to turn a given vector into the second given vector with a custom equality operator+--+-- From https://blog.robertelder.org/diff-algorithm/+getEditScriptBy :: forall a b. (a -> b -> Bool) -> Vector a -> Vector b -> Vector Edit+getEditScriptBy eq old new = V.fromList $ DList.toList $ runST $ go old new 0 0+  where+    go :: forall s. Vector a -> Vector b -> Int -> Int -> ST s (DList Edit)+    go e f i j = do+      -- N,M,L,Z = len(e),len(f),len(e)+len(f),2*min(len(e),len(f))+2+      let upperN :: Int+          upperN = V.length e++      let upperM :: Int+          upperM = V.length f++      let upperL :: Int+          upperL = upperN + upperM++      let upperZ :: Int+          upperZ = 2 * min upperN upperM + 2++      -- if N > 0 and M > 0:+      if upperN > 0 && upperM > 0+        then do+          -- w,g,p = N-M,[0]*Z,[0]*Z+          let w :: Int+              w = upperN - upperM++          g <- MV.replicate upperZ 0 :: ST s (MVector s Int)++          p <- MV.replicate upperZ 0 :: ST s (MVector s Int)++          -- for h in range(0, (L//2+(L%2!=0))+1):+          let hs :: [Int]+              hs = [0 .. ((upperL `quot` 2) + (if odd upperL then 1 else 0))]++          mResult <- forUntilJust hs $ \h -> do+            -- for r in range(0, 2):+            forUntilJust [0, 1 :: Int] $ \r -> do+              -- c,d,o,m = (g,p,1,1) if r==0 else (p,g,0,-1)+              let (c, d, o, m) = if r == 0 then (g, p, 1, 1) else (p, g, 0, -1)++              -- for k in range(-(h-2*max(0,h-M)), h-2*max(0,h-N)+1, 2):+              let lo :: Int+                  lo = -(h - 2 * max 0 (h - upperM))++              let hi :: Int+                  hi = h - 2 * max 0 (h - upperN)++              let ks :: [Int]+                  ks = [lo, lo + 2 .. hi]++              forUntilJust ks $ \k -> do+                -- a = c[(k+1)%Z] if (k==-h or k!=h and c[(k-1)%Z]<c[(k+1)%Z]) else c[(k-1)%Z]+1+                initAVal <- do+                  let part1 = k == -h++                  let part2 = k /= h++                  -- (k+1)%Z+                  let kp1Ix = (k + 1) `modPortable` upperZ++                  -- (k-1)%Z+                  let km1Ix = (k - 1) `modPortable` upperZ+                  if part1+                    then MV.unsafeRead c kp1Ix+                    else do+                      if part2+                        then do+                          -- c[(k-1)%Z]+                          km1 <- MV.unsafeRead c km1Ix++                          -- c[(k+1)%Z]+                          kp1 <- MV.unsafeRead c kp1Ix+                          let part3 = km1 < kp1+                          pure $+                            if part3+                              then kp1+                              else km1 + 1+                        else do+                          km1 <- MV.unsafeRead c km1Ix++                          pure $ km1 + 1++                a <- newSTRef initAVal++                -- b = a-k+                let initBVal :: Int+                    initBVal = initAVal - k++                b <- newSTRef initBVal++                -- s,t = a,b+                s <- newSTRef initAVal+                t <- newSTRef initBVal++                -- while a<N and b<M and e[(1-o)*N+m*a+(o-1)]==f[(1-o)*M+m*b+(o-1)]:+                let computeWhileCond = do+                      aVal <- readSTRef a+                      -- a<N+                      let part1 = aVal < upperN+                      if part1+                        then do+                          bVal <- readSTRef b+                          -- b<M+                          let part2 = bVal < upperM+                          -- e[(1-o)*N+m*a+(o-1)]==f[(1-o)*M+m*b+(o-1)]:+                          let mkPart3 = do+                                let imo = 1 - o+                                    omi = o - 1+                                -- e[(1-o)*N+m*a+(o-1)]+                                leftVal <- do+                                  -- (1-o)*N+m*a+(o-1)+                                  let ix = imo * upperN + m * aVal + omi++                                  pure $ e ! ix+                                -- f[(1-o)*M+m*b+(o-1)]+                                rightVal <- do+                                  -- (1-o)*M+m*b+(o-1)+                                  let ix = imo * upperM + m * bVal + omi++                                  pure $ f ! ix+                                pure $ leftVal `eq` rightVal+                          part2 &&. mkPart3+                        else pure False+                whileM_ computeWhileCond $ do+                  --   a,b = a+1,b+1+                  modifySTRef a (+ 1)+                  modifySTRef b (+ 1)+                -- c[k%Z],z=a,-(k-w)+                do+                  aVal <- readSTRef a++                  MV.unsafeWrite c (k `modPortable` upperZ) aVal++                let z = -(k - w)++                -- if L%2==o and z>=-(h-o) and z<=h-o and c[k%Z]+d[z%Z] >= N:+                let -- L%2==o+                    part1 = upperL `rem` 2 == o+                    -- (h-o)+                    hmo = h - o+                    -- z>=-(h-o)+                    part2 = z >= -hmo+                    -- z<=h-o+                    part3 = z <= hmo+                    -- c[k%Z]+d[z%Z] >= N+                    mkPart4 = do+                      ck <- MV.unsafeRead c (k `modPortable` upperZ)++                      dz <- MV.unsafeRead d (z `modPortable` upperZ)++                      pure (ck + dz >= upperN)+                    mkCondition = part1 &&. (part2 &&. (part3 &&. mkPart4))+                condition <- mkCondition+                if condition+                  then do+                    -- D,x,y,u,v = (2*h-1,s,t,a,b) if o==1 else (2*h,N-a,M-b,N-s,M-t)+                    (upperD, x, y, u, v) <- do+                      aVal <- readSTRef a+                      bVal <- readSTRef b+                      sVal <- readSTRef s+                      tVal <- readSTRef t+                      pure $+                        if o == 1+                          then (2 * h - 1, sVal, tVal, aVal, bVal)+                          else (2 * h, upperN - aVal, upperM - bVal, upperN - sVal, upperM - tVal)++                    -- if D > 1 or (x != u and y != v):+                    if upperD > 1 || (x /= u && y /= v)+                      then do+                        -- return diff(e[0:x],f[0:y],i,j)+diff(e[u:N],f[v:M],i+u,j+v)+                        -- diff(e[0:x],f[0:y],i,j)+                        firstHalf <- go (V.slice 0 x e) (V.slice 0 y f) i j+                        -- diff(e[u:N],f[v:M],i+u,j+v)+                        secondHalf <- go (sliceIx u upperN e) (sliceIx v upperM f) (i + u) (j + v)+                        pure (Just (firstHalf <> secondHalf))+                      else -- elif M > N:++                        if upperM > upperN+                          then do+                            -- return diff([],f[N:M],i+N,j+N)+                            Just <$> go V.empty (sliceIx upperN upperM f) (i + upperN) (j + upperN)+                          else -- elif M < N:++                            if upperM < upperN+                              then do+                                --   return diff(e[M:N],[],i+M,j+M)+                                Just <$> go (sliceIx upperM upperN e) V.empty (i + upperM) (j + upperM)+                              else -- else:+                              --   return []+                                pure (Just mempty)+                  else pure Nothing+          case mResult of+            Nothing -> error "Test.Syd.Diff: This is a bug, the diffing algorithm was supposed to terminate and it didn't."+            Just result -> pure result+        else do+          -- elif N > 0: #  Modify the return statements below if you want a different edit+          if upperN > 0+            then do+              -- return [{"operation": "delete", "position_old": i+n} for n in range(0,N)]++              pure $ DList.singleton (Delete i upperN)+            else do+              -- return [{"operation": "insert", "position_old": i,"position_new":j+n} for n in range(0,M)]+              if upperM > 0+                then do+                  pure $ DList.singleton (Insert i j upperM)+                else do+                  pure DList.empty++-- | Compute a diff using an edit script.+--+-- Prefer `computeGroupedDiffFromEditScript` for performance reasons.+computeGroupedDiffFromEditScript :: Vector a -> Vector b -> Vector Edit -> Vector (PolyDiff (Vector a) (Vector b))+computeGroupedDiffFromEditScript old new editSteps = V.create $ do+  -- Computing the exact size is cumbersome, so we make enough space and cut down later.+  -- Enough space means: Space between every two edit steps, and one before and one after.+  let size = length editSteps * 2 + 1+  v <- MV.new size+  groupMarker <- newSTRef 0++  oldMarker <- newSTRef 0+  curMarker <- newSTRef 0+  newMarker <- newSTRef 0++  forM_ editSteps $ \editStep -> do+    -- Copy over the pieces between the last and current edit+    inbetweenIx <- readSTRef oldMarker+    let inbetweenLen = oldPosition editStep - inbetweenIx+    when (inbetweenLen > 0) $ do+      groupIx <- readSTRef groupMarker+      oldIx <- readSTRef oldMarker+      newIx <- readSTRef newMarker+      MV.unsafeWrite v groupIx (Both (V.slice oldIx inbetweenLen old) (V.slice newIx inbetweenLen new))+      modifySTRef groupMarker (+ 1)+      modifySTRef oldMarker (+ inbetweenLen)+      modifySTRef curMarker (+ inbetweenLen)+      modifySTRef newMarker (+ inbetweenLen)++    -- Apply the edit+    case editStep of+      Delete oldPosStart upperN -> do+        groupIx <- readSTRef groupMarker+        MV.unsafeWrite v groupIx (First (V.slice oldPosStart upperN old))+        modifySTRef groupMarker (+ 1)+        modifySTRef oldMarker (+ upperN)+        modifySTRef curMarker (+ upperN)+      Insert _ newPosStart upperM -> do+        groupIx <- readSTRef groupMarker+        MV.unsafeWrite v groupIx (Second (V.slice newPosStart upperM new))+        modifySTRef groupMarker (+ 1)+        modifySTRef curMarker (+ upperM)+        modifySTRef newMarker (+ upperM)++  oldIx <- readSTRef oldMarker+  let afterLen = V.length old - oldIx+  when (afterLen > 0) $ do+    newIx <- readSTRef newMarker+    groupIx <- readSTRef groupMarker+    MV.unsafeWrite v groupIx (Both (V.slice oldIx afterLen old) (V.slice newIx afterLen new))+    modifySTRef groupMarker (+ 1)+    modifySTRef oldMarker (+ 1)+    modifySTRef curMarker (+ 1)+    modifySTRef newMarker (+ 1)++  endGroupIx <- readSTRef groupMarker++  pure (MV.slice 0 endGroupIx v)++-- | Compute a diff using an edit script.+--+-- Prefer `computeGroupedDiffFromEditScript` for performance reasons.+computeDiffFromEditScript :: Vector a -> Vector b -> Vector Edit -> Vector (PolyDiff a b)+computeDiffFromEditScript old new editSteps = V.create $ do+  -- The total size of the diff is the size of the old vector plus the number+  -- of inserts that need to happen.+  -- Not minus the number of deletions, because they get a 'First' constructor and stay.+  let totalSize = V.length old + sum (V.map insertLength editSteps)++  v <- MV.new totalSize+  oldMarker <- newSTRef 0+  curMarker <- newSTRef 0+  newMarker <- newSTRef 0++  forM_ editSteps $ \editStep -> do+    let computeWhileCond1 = do+          oldIx <- readSTRef oldMarker+          pure $ oldPosition editStep > oldIx+    -- Copy over the pieces between the last and current edit+    whileM_ computeWhileCond1 $ do+      oldIx <- readSTRef oldMarker+      curIx <- readSTRef curMarker+      newIx <- readSTRef newMarker+      MV.unsafeWrite v curIx (Both (old ! oldIx) (new ! newIx))+      modifySTRef oldMarker (+ 1)+      modifySTRef curMarker (+ 1)+      modifySTRef newMarker (+ 1)++    -- Apply the edit+    case editStep of+      Delete oldPosStart upperN -> do+        curIx <- readSTRef curMarker+        forM_ [0 .. upperN - 1] $ \n -> do+          MV.unsafeWrite v (curIx + n) (First (old ! (oldPosStart + n)))+        modifySTRef oldMarker (+ upperN)+        modifySTRef curMarker (+ upperN)+      Insert _ newPosStart upperM -> do+        curIx <- readSTRef curMarker+        forM_ [0 .. upperM - 1] $ \n -> do+          MV.unsafeWrite v (curIx + n) (Second (new ! (newPosStart + n)))+        modifySTRef curMarker (+ upperM)+        modifySTRef newMarker (+ upperM)++  let computeWhileCond2 = do+        oldIx <- readSTRef oldMarker+        pure $ oldIx < V.length old++  -- Copy over the pieces between the last and current edit+  whileM_ computeWhileCond2 $ do+    oldIx <- readSTRef oldMarker+    curIx <- readSTRef curMarker+    newIx <- readSTRef newMarker++    MV.unsafeWrite v curIx (Both (old ! oldIx) (new ! newIx))+    modifySTRef oldMarker (+ 1)+    modifySTRef curMarker (+ 1)+    modifySTRef newMarker (+ 1)++  pure v++data Edit+  = -- | Delete from the old vector+    Delete+      Int+      -- ^ position in the old vector+      Int+      -- ^ number of items to delete+  | -- | Insert into the old vector+    Insert+      Int+      -- ^ position in the old vector+      Int+      -- ^ position in the new vector+      Int+      -- ^ number of items to insert+  deriving (Show, Eq, Ord)++oldPosition :: Edit -> Int+oldPosition = \case+  Delete i _ -> i+  Insert i _ _ -> i++insertLength :: Edit -> Int+insertLength = \case+  Delete _ _ -> 0+  Insert _ _ m -> m++modPortable :: Int -> Int -> Int+modPortable a b =+  let r = a `rem` b+   in if r >= 0 then r else r + b++sliceIx :: Int -> Int -> Vector a -> Vector a+sliceIx start end = V.slice start (end - start)++-- | Short-circuiting monadic (&&)+(&&.) :: Applicative m => Bool -> m Bool -> m Bool+(&&.) b1 mkB2 = do+  if b1+    then mkB2+    else pure False++forUntilJust :: Monad m => [a] -> (a -> m (Maybe b)) -> m (Maybe b)+forUntilJust [] _ = pure Nothing+forUntilJust (a : rest) func = do+  mRes <- func a+  case mRes of+    Nothing -> forUntilJust rest func+    Just res -> pure $ Just res++whileM_ :: (Monad m) => m Bool -> m a -> m ()+whileM_ p f = go+  where+    go = do+      x <- p+      if x+        then f >> go+        else return ()
src/Test/Syd/Output.hs view
@@ -8,9 +8,9 @@ module Test.Syd.Output where  import Control.Exception-import Data.Algorithm.Diff import qualified Data.List as L-import Data.List.Split (splitWhen)+import Data.List.NonEmpty (NonEmpty (..))+import qualified Data.List.NonEmpty as NE import Data.Map (Map) import qualified Data.Map as M import Data.Maybe@@ -19,10 +19,12 @@ import qualified Data.Text.Lazy.Builder as LTB import qualified Data.Text.Lazy.Builder as Text import qualified Data.Text.Lazy.IO as LTIO+import qualified Data.Vector as V import Data.Word import GHC.Stack import Safe import Test.QuickCheck.IO ()+import Test.Syd.Diff import Test.Syd.OptParse import Test.Syd.Run import Test.Syd.SpecDef@@ -225,7 +227,7 @@                 [ [["Retries: ", chunk (T.pack (show retries)), fore red " !!! FLAKY !!!"]],                   [[fore magenta $ chunk $ T.pack message] | message <- maybeToList mMessage]                 ]-            else [["Retries: ", chunk (T.pack (show retries)), " (likely not flaky)"]]+            else [["Retries: ", chunk (T.pack (show retries)), " (does not look flaky)"]]  labelsChunks :: Word -> Maybe (Map [String] Int) -> [[Chunk]] labelsChunks _ Nothing = []@@ -414,38 +416,81 @@   ExpectationFailed s -> stringChunks s   Context a' context -> outputAssertion a' ++ stringChunks context +-- | Split a list of 'Chunk's into lines of [Chunks].+--+-- This is rather complicated because chunks may contain newlines, in which+-- case they need to be split into two chunks on separate lines but with the+-- same colour information.+-- However, separate chunks are not necessarily on separate lines because there+-- may not be a newline inbetween.+splitChunksIntoLines :: [Chunk] -> [[Chunk]]+splitChunksIntoLines =+  -- We maintain a list of 'currently traversing lines'.+  -- These are already split into newlines and therefore definitely belong on separate lines.+  -- We still need to keep the last of the current line though, because it+  -- does not end in a newline and should therefore not necessarily belong on+  -- a separate line by itself.+  go ([] :| []) -- Start with an empty current line.+  where+    -- CurrentlyTraversingLines -> ChunksToStillSplit -> SplitChunks+    go :: NonEmpty [Chunk] -> [Chunk] -> [[Chunk]]+    go cls cs = case NE.uncons cls of+      (currentLine, mRest) -> case mRest of+        -- If there's only one current line, that's the last one of the currently traversing lines.+        -- We split the next chunk into lines and append the first line of that to the current line.+        Nothing -> case cs of+          -- If there is only one current line, and no more chunks, it's the last line.+          [] -> [currentLine]+          -- If there are chunks left, split the first one into lines.+          (c : rest) -> case T.splitOn "\n" (chunkText c) of+            -- Should not happen, but would be fine, just skip this chunk+            [] -> go cls rest+            -- If the chunk had more than one lines+            (l : ls) -> case NE.nonEmpty ls of+              -- If there was only one line in the chunk, we continue with the+              -- same current line onto the rest of the chunks+              Nothing -> go ((currentLine <> [c {chunkText = l}]) :| []) rest+              -- If there was more than one line in that chunk, that line is now considered finished.+              -- We then make all the lines of this new chunk the new current lines, one chunk per line.+              Just ne -> (currentLine <> [c {chunkText = l}]) : go (NE.map (\l' -> [c {chunkText = l'}]) ne) rest+        -- If there is more than one current line, all but the last one are considered finished.+        -- We skip them one by one.+        Just ne -> currentLine : go ne cs+ outputEqualityAssertionFailed :: String -> String -> [[Chunk]] outputEqualityAssertionFailed actual expected =-  let diff = getDiff actual expected -- TODO use 'getGroupedDiff' instead, but then we need to fix the 'splitWhen' below-      splitLines = splitWhen ((== "\n") . chunkText)+  let diff = V.toList $ getTextDiff (T.pack actual) (T.pack expected)+      -- Add a header to a list of lines of chunks       chunksLinesWithHeader :: Chunk -> [[Chunk]] -> [[Chunk]]       chunksLinesWithHeader header = \case+        -- If there is only one line, put the header on that line.         [cs] -> [header : cs]+        -- If there is more than one line, put the header on a separate line before         cs -> [header] : cs       actualChunks :: [[Chunk]]       actualChunks = chunksLinesWithHeader (fore blue "Actual:   ") $-        splitLines $+        splitChunksIntoLines $           flip mapMaybe diff $ \case-            First a -> Just $ fore red $ chunk (T.singleton a)+            First t -> Just $ fore red $ chunk t             Second _ -> Nothing-            Both a _ -> Just $ chunk (T.singleton a)+            Both t _ -> Just $ chunk t       expectedChunks :: [[Chunk]]       expectedChunks = chunksLinesWithHeader (fore blue "Expected: ") $-        splitLines $+        splitChunksIntoLines $           flip mapMaybe diff $ \case             First _ -> Nothing-            Second a -> Just $ fore green $ chunk (T.singleton a)-            Both a _ -> Just $ chunk (T.singleton a)+            Second t -> Just $ fore green $ chunk t+            Both t _ -> Just $ chunk t       inlineDiffChunks :: [[Chunk]]       inlineDiffChunks =         if length (lines actual) == 1 && length (lines expected) == 1           then []           else chunksLinesWithHeader (fore blue "Inline diff: ") $-            splitLines $+            splitChunksIntoLines $               flip map diff $ \case-                First a -> fore red $ chunk (T.singleton a)-                Second a -> fore green $ chunk (T.singleton a)-                Both a _ -> chunk (T.singleton a)+                First t -> fore red $ chunk t+                Second t -> fore green $ chunk t+                Both t _ -> chunk t    in concat         [ [[chunk "Expected these values to be equal:"]],           actualChunks,
sydtest.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           sydtest-version:        0.13.0.0+version:        0.13.0.1 synopsis:       A modern testing framework for Haskell with good defaults and advanced testing features. description:    A modern testing framework for Haskell with good defaults and advanced testing features. Sydtest aims to make the common easy and the hard possible. See https://github.com/NorfairKing/sydtest#readme for more information. category:       Testing@@ -45,6 +45,7 @@       Test.Syd.Def.SetupFunc       Test.Syd.Def.Specify       Test.Syd.Def.TestDefM+      Test.Syd.Diff       Test.Syd.Expectation       Test.Syd.HList       Test.Syd.Modify@@ -66,8 +67,7 @@   hs-source-dirs:       src   build-depends:-      Diff-    , MonadRandom+      MonadRandom     , QuickCheck     , async     , autodocodec@@ -88,9 +88,9 @@     , random-shuffle     , safe     , safe-coloured-text-    , split     , stm     , text+    , vector   if os(windows)     build-depends:         ansi-terminal@@ -127,6 +127,7 @@       Test.Syd.AroundCombinationSpec       Test.Syd.AroundSpec       Test.Syd.DescriptionsSpec+      Test.Syd.DiffSpec       Test.Syd.ExpectationSpec       Test.Syd.FootgunSpec       Test.Syd.GoldenSpec@@ -152,4 +153,5 @@     , stm     , sydtest     , text+    , vector   default-language: Haskell2010
+ test/Test/Syd/DiffSpec.hs view
@@ -0,0 +1,288 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedLists #-}+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -Wno-orphans #-}++module Test.Syd.DiffSpec (spec) where++import Control.Monad+import Data.String+import qualified Data.Text as T+import Data.Vector (Vector)+import qualified Data.Vector as V+import Test.Syd+import Test.Syd.Diff++-- Just for this test+instance IsString (Vector Char) where+  fromString = V.fromList++spec :: Spec+spec = do+  describe "getEditScript" $ do+    let exampleSpec :: Vector Char -> Vector Char -> Vector Edit -> Spec+        exampleSpec old new expected =+          it+            ( unwords+                [ "works for",+                  show old,+                  show new,+                  show expected+                ]+            )+            $ do+              let actual = getEditScript old new+              when (actual /= expected) $+                expectationFailure $+                  unlines+                    [ unwords ["actual:", show actual],+                      unwords ["expected:", show expected]+                    ]++    exampleSpec "" "" []+    exampleSpec "a" "a" []+    exampleSpec "a" "b" [Delete 0 1, Insert 1 0 1]+    exampleSpec "ab" "ac" [Delete 1 1, Insert 2 1 1]+    exampleSpec "b" "aba" [Insert 0 0 1, Insert 1 2 1]+    exampleSpec "abc" "acb" [Delete 1 1, Insert 3 2 1]+    exampleSpec "aaa" "aba" [Delete 0 1, Insert 2 1 1]+    exampleSpec "foofoo" "foo" [Delete 1 1, Delete 3 1, Delete 5 1]+    exampleSpec "foo" "foofoo" [Insert 1 1 1, Insert 2 3 1, Insert 3 5 1]++  describe "computeDiffFromEditScript" $ do+    let exampleSpec :: Vector Char -> Vector Char -> Vector Edit -> Vector (Diff Char) -> Spec+        exampleSpec old new editScript expected =+          it+            ( unwords+                [ "works for",+                  show old,+                  show new,+                  show editScript,+                  show expected+                ]+            )+            $ do+              let actual = computeDiffFromEditScript old new editScript+              when (actual /= expected) $+                expectationFailure $+                  unlines+                    [ unwords ["actual:", show actual],+                      unwords ["expected:", show expected]+                    ]++    exampleSpec "" "" [] []+    exampleSpec "a" "a" [] [Both 'a' 'a']+    exampleSpec "a" "b" [Delete 0 1, Insert 1 0 1] [First 'a', Second 'b']+    exampleSpec "ab" "ac" [Delete 1 1, Insert 2 1 1] [Both 'a' 'a', First 'b', Second 'c']+    exampleSpec "b" "aba" [Insert 0 0 1, Insert 1 2 1] [Second 'a', Both 'b' 'b', Second 'a']+    exampleSpec "abc" "acb" [Delete 1 1, Insert 3 2 1] [Both 'a' 'a', First 'b', Both 'c' 'c', Second 'b']+    exampleSpec "aaa" "aba" [Delete 0 1, Insert 2 1 1] [First 'a', Both 'a' 'a', Second 'b', Both 'a' 'a']+    exampleSpec "foofoo" "foo" [Delete 1 1, Delete 3 1, Delete 5 1] [Both 'f' 'f', First 'o', Both 'o' 'o', First 'f', Both 'o' 'o', First 'o']+    exampleSpec "foo" "foofoo" [Insert 1 1 1, Insert 2 3 1, Insert 3 5 1] [Both 'f' 'f', Second 'o', Both 'o' 'o', Second 'f', Both 'o' 'o', Second 'o']++  describe "getVectorDiff" $ do+    let exampleSpec :: Vector Char -> Vector Char -> Vector (Diff Char) -> Spec+        exampleSpec old new expected =+          it+            ( unwords+                [ "works for",+                  show old,+                  show new,+                  show expected+                ]+            )+            $ do+              let actual = getVectorDiff old new+              when (actual /= expected) $+                expectationFailure $+                  unlines+                    [ unwords ["actual:", show actual],+                      unwords ["expected:", show expected]+                    ]+    exampleSpec "" "" []+    exampleSpec "a" "a" [Both 'a' 'a']+    exampleSpec "a" "b" [First 'a', Second 'b']+    exampleSpec "ab" "ac" [Both 'a' 'a', First 'b', Second 'c']+    exampleSpec "aaa" "aba" [First 'a', Both 'a' 'a', Second 'b', Both 'a' 'a']+    exampleSpec "abgdef" "gh" [First 'a', First 'b', Both 'g' 'g', First 'd', First 'e', Second 'h', First 'f']+    exampleSpec "foofoo" "foo" [Both 'f' 'f', First 'o', Both 'o' 'o', First 'f', Both 'o' 'o', First 'o']++    prop "says that any list is entirely different from the empty list (left)" $ \ls ->+      let v = V.fromList (ls :: String)+       in getVectorDiff v "" `shouldBe` V.map First v++    prop "says that any list is entirely different from the empty list (right)" $ \ls ->+      let v = V.fromList (ls :: String)+       in getVectorDiff "" v `shouldBe` V.map Second v++    prop "does not find diffs in identical strings" $ \ls ->+      let v = V.fromList (ls :: String)+       in getVectorDiff v v `shouldBe` V.map (\a -> Both a a) v++    prop "only puts equal characters in 'Both'" $ \(ls1, ls2) ->+      let v1 = V.fromList (ls1 :: String)+          v2 = V.fromList (ls2 :: String)+          diff = getVectorDiff v1 v2+          valid = \case+            First _ -> True+            Second _ -> True+            Both a b -> a == b+       in all valid diff++    let rebuildFirst :: Vector (PolyDiff a b) -> Vector a+        rebuildFirst = V.mapMaybe $ \case+          First a -> Just a+          Second _ -> Nothing+          Both a _ -> Just a+    prop "lets you rebuild the old vector" $ \(ls1, ls2) -> do+      let v1 = V.fromList (ls1 :: String)+          v2 = V.fromList (ls2 :: String)+      rebuildFirst (getVectorDiff v1 v2) `shouldBe` v1++    let rebuildSecond :: Vector (PolyDiff a b) -> Vector b+        rebuildSecond = V.mapMaybe $ \case+          First _ -> Nothing+          Second b -> Just b+          Both _ b -> Just b+    prop "lets you rebuild the new vector" $ \(ls1, ls2) -> do+      let v1 = V.fromList (ls1 :: String)+          v2 = V.fromList (ls2 :: String)+      rebuildSecond (getVectorDiff v1 v2) `shouldBe` v2++    let unrollGroupedDiff :: Vector (PolyDiff (Vector a) (Vector b)) -> Vector (PolyDiff a b)+        unrollGroupedDiff = foldMap unrollPolyDiff++        -- Not performant, only for testing+        unrollPolyDiff :: PolyDiff (Vector a) (Vector b) -> Vector (PolyDiff a b)+        unrollPolyDiff = \case+          First va -> V.map First va+          Second vb -> V.map Second vb+          Both va vb -> V.zipWith Both va vb++    prop "is the same thing as getGroupedVectorDiff, but unrolled (unroll grouped)" $ \(ls1, ls2) ->+      let v1 = V.fromList (ls1 :: String)+          v2 = V.fromList (ls2 :: String)+          grouped = getGroupedVectorDiff v1 v2+          individual = getVectorDiff v1 v2+       in unrollGroupedDiff grouped `shouldBe` individual++  describe "getGroupedVectorDiff" $ do+    prop "says that any list is entirely different from the empty list (left)" $ \ls ->+      let v = V.fromList (ls :: String)+       in getGroupedVectorDiff v "" `shouldBe` (if V.null v then V.empty else V.singleton (First v))++    prop "says that any list is entirely different from the empty list (right)" $ \ls ->+      let v = V.fromList (ls :: String)+       in getGroupedVectorDiff "" v `shouldBe` (if V.null v then V.empty else V.singleton (Second v))++    prop "does not find diffs in identical strings" $ \ls ->+      let v = V.fromList (ls :: String)+       in getGroupedVectorDiff v v `shouldBe` (if V.null v then V.empty else V.singleton (Both v v))++    prop "only puts equal vectors in 'Both'" $ \(ls1, ls2) ->+      let v1 = V.fromList (ls1 :: String)+          v2 = V.fromList (ls2 :: String)+          diff = getGroupedVectorDiff v1 v2+          valid = \case+            First _ -> True+            Second _ -> True+            Both a b -> a == b+       in all valid diff++    let rebuildFirst :: Vector (PolyDiff (Vector a) (Vector b)) -> Vector a+        rebuildFirst =+          V.concat . V.toList+            . V.mapMaybe+              ( \case+                  First a -> Just a+                  Second _ -> Nothing+                  Both a _ -> Just a+              )+    prop "lets you rebuild the old vector" $ \(ls1, ls2) -> do+      let v1 = V.fromList (ls1 :: String)+          v2 = V.fromList (ls2 :: String)+      rebuildFirst (getGroupedVectorDiff v1 v2) `shouldBe` v1++    let rebuildSecond :: Vector (PolyDiff (Vector a) (Vector b)) -> Vector b+        rebuildSecond =+          V.concat . V.toList+            . V.mapMaybe+              ( \case+                  First _ -> Nothing+                  Second b -> Just b+                  Both _ b -> Just b+              )+    prop "lets you rebuild the new vector" $ \(ls1, ls2) -> do+      let v1 = V.fromList (ls1 :: String)+          v2 = V.fromList (ls2 :: String)+      rebuildSecond (getGroupedVectorDiff v1 v2) `shouldBe` v2++    -- Not performant, only for testing.+    let rollupUngroupedDiff :: Vector (PolyDiff a b) -> Vector (PolyDiff (Vector a) (Vector b))+        rollupUngroupedDiff = V.fromList . go . V.toList+          where+            go = \case+              [] -> []+              (d : ds) -> case d of+                First _ ->+                  let (fs, rest) = goFirsts (d : ds)+                   in First (V.fromList fs) : go rest+                Second _ ->+                  let (ss, rest) = goSeconds (d : ds)+                   in Second (V.fromList ss) : go rest+                Both _ _ ->+                  let (bs, rest) = goBoths (d : ds)+                   in Both (V.fromList (map fst bs)) (V.fromList (map snd bs)) : go rest+            goFirsts = \case+              (First a : ds) ->+                let (fs, rest) = goFirsts ds+                 in (a : fs, rest)+              rest -> ([], rest)+            goSeconds = \case+              (Second a : ds) ->+                let (fs, rest) = goSeconds ds+                 in (a : fs, rest)+              rest -> ([], rest)+            goBoths = \case+              (Both a b : ds) ->+                let (fs, rest) = goBoths ds+                 in ((a, b) : fs, rest)+              rest -> ([], rest)++    let squishGroupedDiff :: Vector (PolyDiff (Vector a) (Vector b)) -> Vector (PolyDiff (Vector a) (Vector b))+        squishGroupedDiff = V.fromList . go . V.toList+          where+            go = \case+              [] -> []+              [pd] -> [pd]+              (First a : First b : rest) -> go (First (a <> b) : rest)+              (Second a : Second b : rest) -> go (Second (a <> b) : rest)+              (Both a b : Both c d : rest) -> go (Both (a <> c) (b <> d) : rest)+              (d : rest) -> d : go rest+    prop "is the same thing as getVectorDiff, but rolled up and squished (rollup individual)" $ \(ls1, ls2) ->+      let v1 = V.fromList (ls1 :: String)+          v2 = V.fromList (ls2 :: String)+          grouped = getGroupedVectorDiff v1 v2+          individual = getVectorDiff v1 v2+       in squishGroupedDiff grouped `shouldBe` rollupUngroupedDiff individual++  describe "getStringDiff" $ do+    it "can output a large diff quickly enough if there is no diff" $+      let s = replicate 10000 'a'+       in length (getStringDiff s s) `shouldBe` 10000++    it "can output a large diff quickly enough if it's only diff" $+      length (getStringDiff (replicate 10000 'a') "b") `shouldBe` 10001++  describe "getTextDiff" $ do+    it "can output a large diff quickly enough if there is no diff" $+      let s = T.pack $ replicate 10000 'a'+       in length (getTextDiff s s) `shouldBe` 1++    it "can output a large diff quickly enough if it's only diff" $+      length (getTextDiff (T.pack (replicate 10000 'a')) "b") `shouldBe` 15++  describe "outputEqualityAssertionFailed" $ do+    it "can output a large diff quickly enough" $+      length (outputEqualityAssertionFailed (replicate 10000 'a') "b") `shouldBe` 3
test_resources/output-test.txt view
@@ -3,71 +3,71 @@ [32m✓ [m[32mPasses[m                                                                            [32m      0.00 ms[m [33merror[m   [31m✗ [m[31mPure error[m                                                                      [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [31m✗ [m[31mImpure error[m                                                                    [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky) [33mundefined[m   [31m✗ [m[31mPure undefined[m                                                                  [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [31m✗ [m[31mImpure undefined[m                                                                [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky) [31m✗ [m[31mExit code[m                                                                         [32m      0.00 ms[m-  Retries: 3 (likely not flaky)+  Retries: 3 (does not look flaky) [33mexceptions[m   [31m✗ [m[31mRecord construction error[m                                                       [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [33mRecord construction error[m     [31m✗ [m[31mfails in IO, as the result[m                                                    [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)     [31m✗ [m[31mfails in IO, as the action[m                                                    [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)     [31m✗ [m[31mfails in pure code[m                                                            [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)   [31m✗ [m[31mRecord selection error[m                                                          [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [33mRecord selection error[m     [31m✗ [m[31mfails in IO, as the result[m                                                    [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)     [31m✗ [m[31mfails in IO, as the action[m                                                    [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)     [31m✗ [m[31mfails in pure code[m                                                            [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)   [31m✗ [m[31mRecord update error[m                                                             [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [33mRecord update error[m     [31m✗ [m[31mfails in IO, as the result[m                                                    [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)     [31m✗ [m[31mfails in IO, as the action[m                                                    [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)     [31m✗ [m[31mfails in pure code[m                                                            [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)   [31m✗ [m[31mPattern matching error[m                                                          [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [33mPattern matching error[m     [31m✗ [m[31mfails in IO, as the result[m                                                    [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)     [31m✗ [m[31mfails in IO, as the action[m                                                    [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)     [31m✗ [m[31mfails in pure code[m                                                            [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)   [31m✗ [m[31mArithException[m                                                                  [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [33mPattern matching error[m     [31m✗ [m[31mfails in IO, as the result[m                                                    [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)     [31m✗ [m[31mfails in IO, as the action[m                                                    [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)     [31m✗ [m[31mfails in pure code[m                                                            [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)   [31m✗ [m[31mNoMethodError[m                                                                   [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [33mPattern matching error[m     [31m✗ [m[31mfails in IO, as the result[m                                                    [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)     [31m✗ [m[31mfails in IO, as the action[m                                                    [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)     [31m✗ [m[31mfails in pure code[m                                                            [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky) [33mPrinting[m   [32m✓ [m[32mprint[m                                                                           [32m      0.00 ms[m   [32m✓ [m[32mputStrLn[m                                                                        [32m      0.00 ms[m@@ -76,14 +76,14 @@     [32m✓ [m[32mreversing a list twice is the same as reversing it once[m                       [32m      0.00 ms[m       passed for all of [32m10[m inputs.     [31m✗ [m[31mshould fail to show that sorting does nothing[m                                 [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)     [32m✓ [m[32mshould work with custom generators too[m                                        [32m      0.00 ms[m       passed for all of [32m10[m inputs.   [33mimpure[m     [32m✓ [m[32mreversing a list twice is the same as reversing it once[m                       [32m      0.00 ms[m       passed for all of [32m10[m inputs.     [31m✗ [m[31mshould fail to show that sorting does nothing[m                                 [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)     [32m✓ [m[32mshould work with custom generators too[m                                        [32m      0.00 ms[m       passed for all of [32m10[m inputs. [33mLong running tests[m@@ -99,88 +99,88 @@   [32m✓ [m[32mtakes a while (10)[m                                                              [32m      0.00 ms[m [33mDiff[m   [31m✗ [m[31mshows nice multi-line diffs[m                                                     [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [31m✗ [m[31mshows nice multi-line diffs[m                                                     [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky) [33massertions[m   [31m✗ [m[31mshouldBe[m                                                                        [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [31m✗ [m[31mshouldNotBe[m                                                                     [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [31m✗ [m[31mshouldSatisfy[m                                                                   [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [31m✗ [m[31mshouldNotSatisfy[m                                                                [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [31m✗ [m[31mshouldSatisfyNamed[m                                                              [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [31m✗ [m[31mshouldNotSatisfyNamed[m                                                           [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky) [35mpending test[m [33mGolden[m   [31m✗ [m[31mdoes not fail the suite when an exception happens while reading[m                 [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [31m✗ [m[31mdoes not fail the suite when an exception happens while producing[m               [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)     [31mGolden output not found[m   [31m✗ [m[31mdoes not fail the suite when an exception happens while writing[m                 [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)     [31mGolden output not found[m   [31m✗ [m[31mdoes not fail the suite when an exception happens while checking for equality[m   [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [33moutputResultForest[m     [32m✓ [m[32moutputs the same as last time[m                                                 [32m      0.00 ms[m [33mAround[m   [33mbefore[m     [31m✗ [m[31mdoes not kill the test suite[m                                                  [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)   [33mbefore_[m     [31m✗ [m[31mdoes not kill the test suite[m                                                  [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)   [33mafter[m     [31m✗ [m[31mdoes not kill the test suite[m                                                  [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)   [33mafter_[m     [31m✗ [m[31mdoes not kill the test suite[m                                                  [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)   [33maround[m     [31m✗ [m[31mdoes not kill the test suite[m                                                  [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)   [33maround_[m     [31m✗ [m[31mdoes not kill the test suite[m                                                  [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)   [33maroundWith[m     [31m✗ [m[31mdoes not kill the test suite[m                                                  [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)   [33maroundWith'[m     [31m✗ [m[31mdoes not kill the test suite[m                                                  [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky) [31m✗ [m[31mexpectationFailure[m                                                                [32m      0.00 ms[m-  Retries: 3 (likely not flaky)+  Retries: 3 (does not look flaky) [33mString[m   [31m✗ [m[31mcompares strings[m                                                                [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [31m✗ [m[31mcompares strings[m                                                                [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [31m✗ [m[31mcompares texts[m                                                                  [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [31m✗ [m[31mcompares texts[m                                                                  [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [31m✗ [m[31mcompares bytestrings[m                                                            [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky) [33mContext[m   [31m✗ [m[31mshows a nice context[m                                                            [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [31m✗ [m[31mshows a nice context multiple levels deep[m                                       [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [31m✗ [m[31mshows a context when an exception is thrown as well[m                             [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky) [33mProperty[m   [33m0 tests run[m     [32m✓ [m[32mshows a red '0 tests' when no tests are run[m                                   [32m      0.00 ms[m       passed for all of [31m0[m inputs.   [33mgenerated values[m     [31m✗ [m[31mshows many generated values too[m                                               [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)   [33mlabels[m     [32m✓ [m[32mshows the labels in use on success[m                                            [32m      0.00 ms[m       passed for all of [32m100[m inputs.@@ -216,7 +216,7 @@          2.00% "length of input is 7", "magnitude (digits) of sum of input is 1"          1.00% "length of input is 8", "magnitude (digits) of sum of input is 0"     [31m✗ [m[31mshows the labels in use on failure[m                                            [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)       Labels         100.00% "length of input is 0", "magnitude (digits) of sum of input is 0"   [33mclasses[m@@ -231,7 +231,7 @@         48.00% non-trivial         21.00% single element     [31m✗ [m[31mshows the classes in use on failure[m                                           [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)       Classes         100.00% empty   [33mtables[m@@ -260,22 +260,22 @@          1.38% 9 [33mShrinking[m   [31m✗ [m[31mcan grab the mvar during shrinking[m                                              [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky) [33mRetries[m   [31m✗ [m[31mdoes not retry if the test is configured withoutRetries[m                         [32m      0.00 ms[m   [31m✗ [m[31mRetries this five times[m                                                         [32m      0.00 ms[m-    Retries: 5 (likely not flaky)+    Retries: 5 (does not look flaky) [33mFlakiness[m   [32m✓ [m[32mAllows flakiness on True eventhough there is none (should succeed)[m              [32m      0.00 ms[m   [31m✗ [m[31mAllows flakiness on False eventhough there is none (should fail)[m                [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [32m✓ [m[32mallows this intentionally flaky test with the default number of retries[m         [32m      0.00 ms[m     Retries: 2[31m !!! FLAKY !!![m     [35mWe're on it![m   [31m✗ [m[31mDoes not allow flakiness if flakiness is not allowed even if retries happen[m     [32m      0.00 ms[m     Retries: 2[31m !!! FLAKY !!![m   [31m✗ [m[31mAllows flakiness in this boolean five times (should fail with 5 retries)[m        [32m      0.00 ms[m-    Retries: 5 (likely not flaky)+    Retries: 5 (does not look flaky)   [32m✓ [m[32mallows this intentionally flaky test with up to four retries[m                    [32m      0.00 ms[m     Retries: 2[31m !!! FLAKY !!![m     [35mWe're on it![m@@ -291,61 +291,61 @@       [35mfour[m [33mcallstack[m   [31m✗ [m[31mit[m                                                                              [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [31m✗ [m[31mspecify[m                                                                         [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [31m✗ [m[31mprop[m                                                                            [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [33mdescribe[m     [31m✗ [m[31mdescribe-it[m                                                                   [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky)     [31m✗ [m[31mdescribe-specify[m                                                              [32m      0.00 ms[m-      Retries: 3 (likely not flaky)+      Retries: 3 (does not look flaky) [33mexpectations[m   [32m✓ [m[32mconsidered passing[m                                                              [32m      0.00 ms[m   [32m✓ [m[32mconsidered passing[m                                                              [32m      0.00 ms[m   [31m✗ [m[31mconsidered failing[m                                                              [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [31m✗ [m[31mconsidered failing[m                                                              [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky) [33mcombinators[m   [31m✗ [m[31mshould fail[m                                                                     [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [32m✓ [m[32mshould pass[m                                                                     [32m      0.00 ms[m     passed for all of [32m100[m inputs.   [31m✗ [m[31mshould not crash (undefined value)[m                                              [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [31m✗ [m[31mshould not crash (undefined generator)[m                                          [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [31m✗ [m[31mshould be even[m                                                                  [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [32m✓ [m[32mshould be even[m                                                                  [32m      0.00 ms[m   [31m✗ [m[31mshould be even[m                                                                  [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky) [33mrandomness[m   [31m✗ [m[31malways outputs the same pseudorandomness[m                                        [32m      0.00 ms[m-    Retries: 3 (likely not flaky)+    Retries: 3 (does not look flaky)   [34mFailures:[m    [36m  output-test/Spec.hs:35[m   [31m✗ [m[31m1 [m[31merror.Pure error[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        foobar        CallStack (from HasCallStack):          error, called at output-test/Spec.hs:35:28 in main:Spec      [36m  output-test/Spec.hs:36[m   [31m✗ [m[31m2 [m[31merror.Impure error[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        foobar        CallStack (from HasCallStack):          error, called at output-test/Spec.hs:36:24 in main:Spec      [36m  output-test/Spec.hs:38[m   [31m✗ [m[31m3 [m[31mundefined.Pure undefined[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Prelude.undefined        CallStack (from HasCallStack):          error, called at libraries/base/GHC/Err.hs:75:14 in base:GHC.Err@@ -353,7 +353,7 @@      [36m  output-test/Spec.hs:39[m   [31m✗ [m[31m4 [m[31mundefined.Impure undefined[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Prelude.undefined        CallStack (from HasCallStack):          error, called at libraries/base/GHC/Err.hs:75:14 in base:GHC.Err@@ -361,147 +361,147 @@      [36m  output-test/Spec.hs:40[m   [31m✗ [m[31m5 [m[31mExit code[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        ExitFailure 1      [36m  output-test/Spec.hs:48[m   [31m✗ [m[31m6 [m[31mexceptions.Record construction error[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        test      [36m  output-test/Spec.hs:45[m   [31m✗ [m[31m7 [m[31mexceptions.Record construction error.fails in IO, as the result[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        output-test/Spec.hs:49:57-64: Missing field in record construction field      [36m  output-test/Spec.hs:46[m   [31m✗ [m[31m8 [m[31mexceptions.Record construction error.fails in IO, as the action[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        output-test/Spec.hs:49:57-64: Missing field in record construction field      [36m  output-test/Spec.hs:47[m   [31m✗ [m[31m9 [m[31mexceptions.Record construction error.fails in pure code[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        output-test/Spec.hs:49:57-64: Missing field in record construction field      [36m  output-test/Spec.hs:50[m   [31m✗ [m[31m10 [m[31mexceptions.Record selection error[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        test      [36m  output-test/Spec.hs:45[m   [31m✗ [m[31m11 [m[31mexceptions.Record selection error.fails in IO, as the result[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        No match in record selector field      [36m  output-test/Spec.hs:46[m   [31m✗ [m[31m12 [m[31mexceptions.Record selection error.fails in IO, as the action[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        No match in record selector field      [36m  output-test/Spec.hs:47[m   [31m✗ [m[31m13 [m[31mexceptions.Record selection error.fails in pure code[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        No match in record selector field      [36m  output-test/Spec.hs:52[m   [31m✗ [m[31m14 [m[31mexceptions.Record update error[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        test      [36m  output-test/Spec.hs:45[m   [31m✗ [m[31m15 [m[31mexceptions.Record update error.fails in IO, as the result[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        output-test/Spec.hs:53:60-88: Non-exhaustive patterns in record update      [36m  output-test/Spec.hs:46[m   [31m✗ [m[31m16 [m[31mexceptions.Record update error.fails in IO, as the action[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        output-test/Spec.hs:53:60-88: Non-exhaustive patterns in record update      [36m  output-test/Spec.hs:47[m   [31m✗ [m[31m17 [m[31mexceptions.Record update error.fails in pure code[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        output-test/Spec.hs:53:60-88: Non-exhaustive patterns in record update      [36m  output-test/Spec.hs:54[m   [31m✗ [m[31m18 [m[31mexceptions.Pattern matching error[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        test      [36m  output-test/Spec.hs:45[m   [31m✗ [m[31m19 [m[31mexceptions.Pattern matching error.fails in IO, as the result[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        output-test/Spec.hs:55:50-64: Non-exhaustive patterns in Cons1 s      [36m  output-test/Spec.hs:46[m   [31m✗ [m[31m20 [m[31mexceptions.Pattern matching error.fails in IO, as the action[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        output-test/Spec.hs:55:50-64: Non-exhaustive patterns in Cons1 s      [36m  output-test/Spec.hs:47[m   [31m✗ [m[31m21 [m[31mexceptions.Pattern matching error.fails in pure code[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        output-test/Spec.hs:55:50-64: Non-exhaustive patterns in Cons1 s      [36m  output-test/Spec.hs:56[m   [31m✗ [m[31m22 [m[31mexceptions.ArithException[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        arithmetic underflow      [36m  output-test/Spec.hs:45[m   [31m✗ [m[31m23 [m[31mexceptions.Pattern matching error.fails in IO, as the result[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        divide by zero      [36m  output-test/Spec.hs:46[m   [31m✗ [m[31m24 [m[31mexceptions.Pattern matching error.fails in IO, as the action[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        divide by zero      [36m  output-test/Spec.hs:47[m   [31m✗ [m[31m25 [m[31mexceptions.Pattern matching error.fails in pure code[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        divide by zero      [36m  output-test/Spec.hs:58[m   [31m✗ [m[31m26 [m[31mexceptions.NoMethodError[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        test      [36m  output-test/Spec.hs:45[m   [31m✗ [m[31m27 [m[31mexceptions.Pattern matching error.fails in IO, as the result[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        output-test/Spec.hs:29:10-19: No instance nor default method for class operation toUnit      [36m  output-test/Spec.hs:46[m   [31m✗ [m[31m28 [m[31mexceptions.Pattern matching error.fails in IO, as the action[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        output-test/Spec.hs:29:10-19: No instance nor default method for class operation toUnit      [36m  output-test/Spec.hs:47[m   [31m✗ [m[31m29 [m[31mexceptions.Pattern matching error.fails in pure code[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        output-test/Spec.hs:29:10-19: No instance nor default method for class operation toUnit      [36m  output-test/Spec.hs:72[m   [31m✗ [m[31m30 [m[31mProperty tests.pure.should fail to show that sorting does nothing[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Failed after 3 tests        Generated: [33m[-5,3,11,-3,-13,0,19,-10,15,-13,1,-12,10,-18,-3,-2,6,-9,-6][m      [36m  output-test/Spec.hs:80[m   [31m✗ [m[31m31 [m[31mProperty tests.impure.should fail to show that sorting does nothing[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Failed after 3 tests        Generated: [33m[-5,3,11,-3,-13,0,19,-10,15,-13,1,-12,10,-18,-3,-2,6,-9,-6][m        Expected these values to be equal:        [34mActual:   [m        [ -[31m1[m[31m8[m-       , [31m-[m[31m1[m3        , [31m-[m1[31m3[m-       , -[31m1[m[31m2[m-       , -10+       , -[31m1[m3+       , -1[31m2[m+       , [31m-1[m0        , [31m-[m9        , -[31m6[m        , [31m-[m5@@ -512,18 +512,18 @@        , 1        , 3        , [31m6[m-       [31m,[m[31m [m[31m1[m[31m0[m-       , [31m1[m[31m1[m-       , [31m1[m[31m5[m+       , [31m1[m[31m0[m+       , [31m11[m+       [31m, 1[m[31m5[m        , [31m1[m[31m9[m        ]        [34mExpected: [m-       [ -[32m5[m-       , 3+       [ -[32m5[m[32m[m+       [32m[m[32m, [m[32m3[m        , 1[32m1[m-       , -[32m3[m+       , -3        , -1[32m3[m-       [32m,[m[32m [m0+       , 0        , [32m1[m9        , -[32m1[m[32m0[m        , [32m1[m5@@ -536,241 +536,241 @@        , [32m-[m[32m2[m        , [32m6[m        , [32m-[m[32m9[m-       , [32m-[m[32m6[m+       , [32m-6[m        ]        [34mInline diff: [m-       [ -[31m1[m[31m8[m[32m5[m-       , [31m-[m[31m1[m3-       , [31m-[m1[31m3[m[32m1[m-       , -[31m1[m[31m2[m[32m3[m-       , -1[32m3[m-       [32m,[m[32m [m0+       [ -[32m5[m[32m[m+       [32m[m[32m, [m[31m1[m[32m3[m[31m8[m+       , [31m-[m1[32m1[m[31m3[m+       , -[31m1[m3+       , -1[32m3[m[31m2[m+       , [31m-1[m0        , [31m-[m[32m1[m9-       , -[31m6[m[32m1[m[32m0[m-       , [31m-[m[32m1[m5+       , -[32m1[m[31m6[m[32m0[m+       , [32m1[m[31m-[m5        , -[32m1[m3        , [31m-[m[31m3[m[32m1[m        , -[32m1[m2        , [32m1[m0        , [32m-[m1[32m8[m        , [32m-[m3-       , [31m6[m-       [31m,[m[31m [m[31m1[m[31m0[m[32m-[m[32m2[m-       , [31m1[m[31m1[m[32m6[m-       , [31m1[m[31m5[m[32m-[m[32m9[m-       , [31m1[m[31m9[m[32m-[m[32m6[m+       , [32m-[m[31m6[m[32m2[m+       , [31m1[m[31m0[m[32m6[m+       , [32m-[m[31m11[m+       [31m, 1[m[31m5[m[32m9[m+       , [31m1[m[32m-6[m[31m9[m        ]      [36m  output-test/Spec.hs:90[m   [31m✗ [m[31m32 [m[31mDiff.shows nice multi-line diffs[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Expected these values to be equal:        [34mActual:   [m        ( "foo"-       , [ "quux" , "quux" , "quux" , "quux" , "quux" , "quux" [31m,[m[31m [m[31m"[m[31mq[m[31mu[m[31mu[m[31mx[m[31m"[m[31m [m]+       , [[31m [m[31m"qu[m[31mux[m[31m"[m [31m,[m[31m [m"quux" , "quux" , "quux" , "quux" , "quux" , "quux" ]        , "ba[31mr[m"        )        [34mExpected: [m-       ( "foo[32mf[m[32mo[m[32mo[m"+       ( "[32mf[m[32mo[m[32mo[mfoo"        , [ "quux" , "quux" , "quux" , "quux" , "quux" , "quux" ]        , "ba[32mz[m"        )        [34mInline diff: [m-       ( "foo[32mf[m[32mo[m[32mo[m"-       , [ "quux" , "quux" , "quux" , "quux" , "quux" , "quux" [31m,[m[31m [m[31m"[m[31mq[m[31mu[m[31mu[m[31mx[m[31m"[m[31m [m]-       , "ba[31mr[m[32mz[m"+       ( "[32mf[m[32mo[m[32mo[mfoo"+       , [[31m [m[31m"qu[m[31mux[m[31m"[m [31m,[m[31m [m"quux" , "quux" , "quux" , "quux" , "quux" , "quux" ]+       , "ba[32mz[m[31mr[m"        )      [36m  output-test/Spec.hs:92[m   [31m✗ [m[31m33 [m[31mDiff.shows nice multi-line diffs[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Expected these values to be equal:        [34mActual:   [m( "foo"[31m [m, [[31m][m , "ba[31mr[m"[31m [m)        [34mExpected: [m-       ( "foo[32mf[m[32mo[m[32mo[m"-       , [ [32m"[m[32mq[m[32mu[m[32mu[m[32mx[m[32m"[m[32m [m, "[32mq[m[32mu[m[32mu[m[32mx[m[32m"[m[32m [m[32m,[m[32m [m[32m"[m[32mq[m[32mu[m[32mu[m[32mx[m[32m"[m[32m [m[32m,[m[32m [m[32m"[m[32mq[m[32mu[m[32mu[m[32mx[m[32m"[m[32m [m[32m,[m[32m [m[32m"[m[32mq[m[32mu[m[32mu[m[32mx[m[32m"[m[32m [m[32m,[m[32m [m[32m"[m[32mq[m[32mu[m[32mu[m[32mx[m[32m"[m[32m [m[32m][m-       [32m,[m[32m [m[32m"[mba[32mz[m"-       )+       ( "[32mf[m[32mo[m[32mo[mfoo"[32m[m+       [32m[m, [[32m "[m[32mquux" , [m[32m"quux" , "quux" [m[32m, "quux" , "quux[m[32m" , "quu[m[32mx"[m [32m][m+       [32m[m, "ba[32mz[m"[32m[m+       [32m[m)        [34mInline diff: [m-       ( "foo[32mf[m[32mo[m[32mo[m"[31m [m-       , [[31m][m [32m"[m[32mq[m[32mu[m[32mu[m[32mx[m[32m"[m[32m [m, "[32mq[m[32mu[m[32mu[m[32mx[m[32m"[m[32m [m[32m,[m[32m [m[32m"[m[32mq[m[32mu[m[32mu[m[32mx[m[32m"[m[32m [m[32m,[m[32m [m[32m"[m[32mq[m[32mu[m[32mu[m[32mx[m[32m"[m[32m [m[32m,[m[32m [m[32m"[m[32mq[m[32mu[m[32mu[m[32mx[m[32m"[m[32m [m[32m,[m[32m [m[32m"[m[32mq[m[32mu[m[32mu[m[32mx[m[32m"[m[32m [m[32m][m-       [32m,[m[32m [m[32m"[mba[31mr[m[32mz[m"[31m [m-       )+       ( "[32mf[m[32mo[m[32mo[mfoo"[31m [m[32m[m+       [32m[m, [[31m][m[32m "[m[32mquux" , [m[32m"quux" , "quux" [m[32m, "quux" , "quux[m[32m" , "quu[m[32mx"[m [32m][m+       [32m[m, "ba[31mr[m[32mz[m"[31m [m[32m[m+       [32m[m)      [36m  output-test/Spec.hs:95[m   [31m✗ [m[31m34 [m[31massertions.shouldBe[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Expected these values to be equal:        [34mActual:   [m[31m3[m        [34mExpected: [m[32m4[m      [36m  output-test/Spec.hs:96[m   [31m✗ [m[31m35 [m[31massertions.shouldNotBe[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Did not expect equality of the values but both were:        3      [36m  output-test/Spec.hs:97[m   [31m✗ [m[31m36 [m[31massertions.shouldSatisfy[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Predicate failed, but should have succeeded, on this value:        3      [36m  output-test/Spec.hs:98[m   [31m✗ [m[31m37 [m[31massertions.shouldNotSatisfy[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Predicate succeeded, but should have failed, on this value:        3      [36m  output-test/Spec.hs:99[m   [31m✗ [m[31m38 [m[31massertions.shouldSatisfyNamed[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Predicate failed, but should have succeeded, on this value:        3        Predicate: even      [36m  output-test/Spec.hs:100[m   [31m✗ [m[31m39 [m[31massertions.shouldNotSatisfyNamed[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Predicate succeeded, but should have failed, on this value:        3        Predicate: odd      [36m  output-test/Spec.hs:103[m   [31m✗ [m[31m40 [m[31mGolden.does not fail the suite when an exception happens while reading[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        ExitFailure 1      [36m  output-test/Spec.hs:111[m   [31m✗ [m[31m41 [m[31mGolden.does not fail the suite when an exception happens while producing[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        [31mGolden output not found[m      [36m  output-test/Spec.hs:119[m   [31m✗ [m[31m42 [m[31mGolden.does not fail the suite when an exception happens while writing[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        [31mGolden output not found[m      [36m  output-test/Spec.hs:126[m   [31m✗ [m[31m43 [m[31mGolden.does not fail the suite when an exception happens while checking for equality[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        divide by zero      [36m  output-test/Spec.hs:149[m   [31m✗ [m[31m44 [m[31mAround.before.does not kill the test suite[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        user error (test)      [36m  output-test/Spec.hs:154[m   [31m✗ [m[31m45 [m[31mAround.before_.does not kill the test suite[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        user error (test)      [36m  output-test/Spec.hs:159[m   [31m✗ [m[31m46 [m[31mAround.after.does not kill the test suite[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        user error (test)      [36m  output-test/Spec.hs:164[m   [31m✗ [m[31m47 [m[31mAround.after_.does not kill the test suite[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        user error (test)      [36m  output-test/Spec.hs:169[m   [31m✗ [m[31m48 [m[31mAround.around.does not kill the test suite[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        user error (test)      [36m  output-test/Spec.hs:174[m   [31m✗ [m[31m49 [m[31mAround.around_.does not kill the test suite[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        user error (test)      [36m  output-test/Spec.hs:179[m   [31m✗ [m[31m50 [m[31mAround.aroundWith.does not kill the test suite[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        user error (test)      [36m  output-test/Spec.hs:184[m   [31m✗ [m[31m51 [m[31mAround.aroundWith'.does not kill the test suite[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        user error (test)      [36m  output-test/Spec.hs:187[m   [31m✗ [m[31m52 [m[31mexpectationFailure[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        fails      [36m  output-test/Spec.hs:190[m   [31m✗ [m[31m53 [m[31mString.compares strings[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Expected these values to be equal:-       [34mActual:   [m"fo[31mo[m\nba[31mr[m\tq[31mu[m[31mu[mx[31m [m"+       [34mActual:   [m"f[31mo[mo\nba[31mr[m\tq[31muu[mx[31m [m"        [34mExpected: [m"fo[32mq[m\nba[32mz[m\tq[32me[mx"      [36m  output-test/Spec.hs:191[m   [31m✗ [m[31m54 [m[31mString.compares strings[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Expected these values to be equal:        [34mActual:   [m-       fo[31mo[m-       ba[31mr[m	q[31mu[m[31mu[mx[31m [m+       f[31mo[mo+       ba[31mr[m	q[31muu[mx[31m [m        [34mExpected: [m        fo[32mq[m        ba[32mz[m	q[32me[mx        [34mInline diff: [m-       fo[31mo[m[32mq[m-       ba[31mr[m[32mz[m	q[31mu[m[31mu[m[32me[mx[31m [m+       f[31mo[mo[32mq[m+       ba[31mr[m[32mz[m	q[31muu[m[32me[mx[31m [m      [36m  output-test/Spec.hs:192[m   [31m✗ [m[31m55 [m[31mString.compares texts[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Expected these values to be equal:-       [34mActual:   [m"fo[31mo[m\nba[31mr[m\tq[31mu[m[31mu[mx[31m [m"+       [34mActual:   [m"f[31mo[mo\nba[31mr[m\tq[31muu[mx[31m [m"        [34mExpected: [m"fo[32mq[m\nba[32mz[m\tq[32me[mx"      [36m  output-test/Spec.hs:193[m   [31m✗ [m[31m56 [m[31mString.compares texts[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Expected these values to be equal:        [34mActual:   [m-       fo[31mo[m-       ba[31mr[m	q[31mu[m[31mu[mx[31m [m+       f[31mo[mo+       ba[31mr[m	q[31muu[mx[31m [m        [34mExpected: [m        fo[32mq[m        ba[32mz[m	q[32me[mx        [34mInline diff: [m-       fo[31mo[m[32mq[m-       ba[31mr[m[32mz[m	q[31mu[m[31mu[m[32me[mx[31m [m+       f[31mo[mo[32mq[m+       ba[31mr[m[32mz[m	q[31muu[m[32me[mx[31m [m      [36m  output-test/Spec.hs:194[m   [31m✗ [m[31m57 [m[31mString.compares bytestrings[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Expected these values to be equal:-       [34mActual:   [m"fo[31mo[m\nba[31mr[m\tq[31mu[m[31mu[mx[31m [m"+       [34mActual:   [m"f[31mo[mo\nba[31mr[m\tq[31muu[mx[31m [m"        [34mExpected: [m"fo[32mq[m\nba[32mz[m\tq[32me[mx"      [36m  output-test/Spec.hs:197[m   [31m✗ [m[31m58 [m[31mContext.shows a nice context[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Expected these values to be equal:-       [34mActual:   [m[31mT[m[31mr[m[31mu[me-       [34mExpected: [m[32mF[m[32ma[m[32ml[m[32ms[me+       [34mActual:   [m[31mTr[m[31mu[me+       [34mExpected: [m[32mFals[me        Context      [36m  output-test/Spec.hs:198[m   [31m✗ [m[31m59 [m[31mContext.shows a nice context multiple levels deep[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Expected these values to be equal:-       [34mActual:   [m[31mT[m[31mr[m[31mu[me-       [34mExpected: [m[32mF[m[32ma[m[32ml[m[32ms[me+       [34mActual:   [m[31mTr[m[31mu[me+       [34mExpected: [m[32mFals[me        Context3        Context2        Context1      [36m  output-test/Spec.hs:203[m   [31m✗ [m[31m60 [m[31mContext.shows a context when an exception is thrown as well[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Prelude.undefined        CallStack (from HasCallStack):          error, called at libraries/base/GHC/Err.hs:75:14 in base:GHC.Err@@ -779,7 +779,7 @@      [36m  output-test/Spec.hs:210[m   [31m✗ [m[31m61 [m[31mProperty.generated values.shows many generated values too[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Failed after 1 tests        Generated: [33m0[m        Generated: [33m0[m@@ -792,7 +792,7 @@      [36m  output-test/Spec.hs:229[m   [31m✗ [m[31m62 [m[31mProperty.labels.shows the labels in use on failure[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Failed after 1 tests        Generated: [33m[][m        Labels: "length of input is 0", "magnitude (digits) of sum of input is 0"@@ -802,7 +802,7 @@      [36m  output-test/Spec.hs:246[m   [31m✗ [m[31m63 [m[31mProperty.classes.shows the classes in use on failure[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Failed after 1 tests        Generated: [33m[][m        Class: empty@@ -812,7 +812,7 @@      [36m  output-test/Spec.hs:269[m   [31m✗ [m[31m64 [m[31mShrinking.can grab the mvar during shrinking[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Failed after 21 tests        Generated: [33m20[m        Predicate failed, but should have succeeded, on this value:@@ -823,11 +823,11 @@      [36m  output-test/Spec.hs:278[m   [31m✗ [m[31m66 [m[31mRetries.Retries this five times[m-       Retries: 5 (likely not flaky)+       Retries: 5 (does not look flaky)      [36m  output-test/Spec.hs:283[m   [31m✗ [m[31m67 [m[31mFlakiness.Allows flakiness on False eventhough there is none (should fail)[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)      [36m  output-test/Spec.hs:292[m   [31m✗ [m[31m68 [m[31mFlakiness.Does not allow flakiness if flakiness is not allowed even if retries happen[m@@ -838,46 +838,46 @@      [36m  output-test/Spec.hs:296[m   [31m✗ [m[31m69 [m[31mFlakiness.Allows flakiness in this boolean five times (should fail with 5 retries)[m-       Retries: 5 (likely not flaky)+       Retries: 5 (does not look flaky)      [36m  output-test/Spec.hs:316[m   [31m✗ [m[31m70 [m[31mcallstack.it[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)      [36m  output-test/Spec.hs:317[m   [31m✗ [m[31m71 [m[31mcallstack.specify[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)      [36m  Unknown location[m   [31m✗ [m[31m72 [m[31mcallstack.prop[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Failed after 1 tests      [36m  output-test/Spec.hs:320[m   [31m✗ [m[31m73 [m[31mcallstack.describe.describe-it[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)      [36m  output-test/Spec.hs:321[m   [31m✗ [m[31m74 [m[31mcallstack.describe.describe-specify[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)      [36m  output-test/Spec.hs:328[m   [31m✗ [m[31m75 [m[31mexpectations.considered failing[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)      [36m  output-test/Spec.hs:329[m   [31m✗ [m[31m76 [m[31mexpectations.considered failing[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)      [36m  output-test/Spec.hs:336[m   [31m✗ [m[31m77 [m[31mcombinators.should fail[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Failed after 2 tests        Generated: [33m-1[m      [36m  output-test/Spec.hs:338[m   [31m✗ [m[31m78 [m[31mcombinators.should not crash (undefined value)[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Failed after 1 tests        Generated: [33m0[m        Prelude.undefined@@ -887,7 +887,7 @@      [36m  output-test/Spec.hs:339[m   [31m✗ [m[31m79 [m[31mcombinators.should not crash (undefined generator)[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Failed after 1 tests        Generated: [33mException thrown while showing test case:   Prelude.undefined@@ -902,11 +902,11 @@      [36m  output-test/Spec.hs:342[m   [31m✗ [m[31m80 [m[31mcombinators.should be even[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)      [36m  output-test/Spec.hs:342[m   [31m✗ [m[31m81 [m[31mcombinators.should be even[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Prelude.undefined        CallStack (from HasCallStack):          error, called at libraries/base/GHC/Err.hs:75:14 in base:GHC.Err@@ -914,7 +914,7 @@      [36m  output-test/Spec.hs:348[m   [31m✗ [m[31m82 [m[31mrandomness.always outputs the same pseudorandomness[m-       Retries: 3 (likely not flaky)+       Retries: 3 (does not look flaky)        Expected these values to be equal:        [34mActual:   [m[31m4[m[31m9[m        [34mExpected: [m[32m2[m