packages feed

sydtest-mutation-driver 0.4.2.0 → 0.4.3.0

raw patch · 3 files changed

+63/−24 lines, 3 files

Files

CHANGELOG.md view
@@ -1,5 +1,24 @@ # Changelog +## [0.4.3.0] - 2026-10-22++### Fixed++* The per-mutation `[X/Y]` counter counts the mutations that have started,+  instead of naming each mutation's position in the manifest.++  The manifest's order is not the order the mutations are tested in: the+  groups of a run are started most expensive first and run concurrently, so+  the positions arrived in the log out of order and in jumps of hundreds,+  which told a reader nothing about how far along the run was.++* A progress line is no longer cut in half by the end-of-run report.++  The mutation phase block-buffered its progress output, so a line could be+  flushed in two pieces, with the report that is written to stdout landing+  between them.  It is line-buffered now, like the coverage phase.++ ## [0.4.2.0] - 2026-10-13  ### Changed
src/Test/Syd/Mutation/Driver/Mutate.hs view
@@ -21,6 +21,7 @@  import Control.Concurrent (newQSem, signalQSem, threadDelay, waitQSem) import Control.Concurrent.Async (mapConcurrently, race)+import Control.Concurrent.MVar (MVar, modifyMVar_, newMVar) import Control.Concurrent.STM (atomically, modifyTVar', newTVarIO, readTVarIO) import Control.Exception (bracket, bracket_) import qualified Control.Exception as Exception@@ -40,7 +41,7 @@ import Path import Path.IO (copyFile, ensureDir, ignoringAbsence, withSystemTempDir) import System.Exit (ExitCode (..))-import System.IO (BufferMode (..), IOMode (..), hFlush, hSetBuffering, stderr, withFile)+import System.IO (BufferMode (..), IOMode (..), hSetBuffering, stderr, withFile) import System.Process.Typed (proc, setStderr, setStdout, setWorkingDir, startProcess, stopProcess, useHandleOpen, waitExitCode) import Test.Syd.Mutation.AugmentedManifest   ( AugmentedManifest (..),@@ -153,6 +154,35 @@       childAugDir       (TestBaselineMap (Map.restrictKeys allBaselines (Set.fromList coveringHere))) +-- | Count a mutation that is about to be tested, and write its progress line.+--+-- The number a mutation is shown under is how many have started, itself+-- included, so the counter climbs by one per line and a reader can see how far+-- along a run is.  It is not an identifier: which mutation gets which number+-- depends on the order the concurrent workers happen to reach here in, and+-- every mutation the report has anything to say about is named there by id.+--+-- Taking the number and writing the line happen under the same lock, so the+-- numbers reach the log in the order they were handed out, and a debug run's+-- multi-line diffs arrive whole instead of interleaved.+emitMutationProgress ::+  -- | How many mutations have started.+  MVar Int ->+  -- | Debug mode: follow the line with the mutation's source diff.+  Bool ->+  -- | How many mutations the run tests in total.+  Int ->+  AugmentedMutationRecord ->+  IO ()+emitMutationProgress startedVar debug total record =+  modifyMVar_ startedVar $ \started -> do+    let index = started + 1+    hPutChunksUtf8With+      With8BitColours+      stderr+      (unlinesChunks (renderMutationProgressEvent debug index total (MutationProgressEvent record)))+    pure index+ -- | Parent process: read @manifest-augmented.json@ and spawn one child -- subprocess per mutation per suite that covers it. --@@ -200,7 +230,13 @@   Map.Map Text SuiteConfig ->   IO MutationRunReport runMutationMode failFast debug augDir outDir childMemLimit mutationJobs suiteConfigs = do-  hSetBuffering stderr (BlockBuffering Nothing)+  -- LineBuffering on stderr, as the coverage phase does, so each progress+  -- line reaches the fd when it ends rather than when the buffer happens to+  -- fill.  Under a block buffer a line is cut wherever the flush lands, and+  -- the report this writes to stdout at the end arrives in the gap, which+  -- leaves a run's log with a half-written progress line wrapped around the+  -- summary.+  hSetBuffering stderr LineBuffering   ensureDir outDir   AugmentedManifest groups <- readAugmentedManifestFile augDir   -- Validate that every covering-suite name in the manifest is in@@ -228,16 +264,8 @@     Just j | j > 0 -> pure (fromIntegral j)     _ -> getNumCapabilities   sem <- newQSem n-  -- Progress counter: assign every mutation a stable 1-based index in source-  -- order up front, so the per-mutation progress line can show an [X/Y]-  -- indication.  This is a pure, immutable map (no shared mutable counter):-  -- each mutation's number is its position in the manifest, so the numbers are-  -- deterministic and reproducible rather than dependent on the-  -- non-deterministic order in which concurrent workers happen to start.-  let orderedRecords = concat [recs | AugmentedMutationGroup recs <- groups]-      totalMutations = length orderedRecords-      indexByMutationId =-        Map.fromList (zip (map augmentedMutationRecordId orderedRecords) [1 :: Int ..])+  let totalMutations = sum [length recs | AugmentedMutationGroup recs <- groups]+  startedVar <- newMVar (0 :: Int)   -- Each group's per-mutation results, accumulated in source order so a   -- partial report (after a global fail-fast abort) reflects the work   -- done.@@ -245,7 +273,7 @@   let runGroup' (gix, AugmentedMutationGroup recs) =         runOneGroup           failFast-          (runOne baselines indexByMutationId totalMutations sem)+          (runOne baselines startedVar totalMutations sem)           ( \r ->               atomically $                 modifyTVar' groupResultsVar (Map.insertWith (++) gix [r])@@ -301,12 +329,6 @@   let renderedChunks = renderMutationRunReport jsonReport   writeReportTxt renderedChunks outDir   putChunksUtf8With With8BitColours (unlinesChunks renderedChunks)-  -- Force out any block-buffered progress events before we return.  This-  -- matters even though we don't 'exitWith' here ourselves: callers (e.g.-  -- 'runDriver' under --fail-fast) may, and a buffered stderr line that-  -- lands after the caller's stdout summary block would look like trailing-  -- garbage in the build log.-  hFlush stderr   case mWorkerException of     Left e -> case Exception.fromException e of       Just MutationFailFast -> pure ()@@ -321,12 +343,10 @@   -- when fail-fast trips.   pure jsonReport   where-    runOne baselines indexByMutationId totalMutations sem record =+    runOne baselines startedVar totalMutations sem record =       bracket_ (waitQSem sem) (signalQSem sem) $ do         let mid = augmentedMutationRecordId record-            -- This mutation's stable 1-based position in the manifest.-            index = Map.findWithDefault 0 mid indexByMutationId-        hPutChunksUtf8With With8BitColours stderr (unlinesChunks (renderMutationProgressEvent debug index totalMutations (MutationProgressEvent record)))+        emitMutationProgress startedVar debug totalMutations record         -- Only run suites that have at least one covering test for this         -- mutation.         let coveringBySuite =
sydtest-mutation-driver.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           sydtest-mutation-driver-version:        0.4.2.0+version:        0.4.3.0 synopsis:       Out-of-process mutation testing driver for sydtest. description:    Standalone driver executable that orchestrates the coverage and mutation phases of sydtest's mutation testing infrastructure. Spawns instrumented sydtest test suite executables as children. category:       Testing