sydtest-mutation-driver 0.4.2.0 → 0.4.3.0
raw patch · 3 files changed
+63/−24 lines, 3 files
Files
- CHANGELOG.md +19/−0
- src/Test/Syd/Mutation/Driver/Mutate.hs +43/−23
- sydtest-mutation-driver.cabal +1/−1
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