heni-0.1.0.0: test/runner/Main.hs
module Main (main) where
import Control.Concurrent
import Control.Exception
import Control.Monad (forM_, unless, when)
import Data.ByteString qualified as BS
import Data.IORef
import Data.Map.Strict qualified as Map
import Data.Set qualified as Set
import Definitions (exampleCampaign)
import Paths_heni (getDataFileName)
import System.Directory qualified as Dir
import System.Environment (lookupEnv)
import System.FilePath (takeDirectory, (</>))
import System.Posix.Temp (mkdtemp)
import System.Timeout (timeout)
import Test.Heni
assert' :: String -> Bool -> IO ()
assert' name good = unless good (error name)
checked :: (Show e) => Either e a -> a
checked = either (error . show) id
main :: IO ()
main = do
result <- timeout 180000000 suite
assert' "outer campaign watchdog" (result == Just ())
suite :: IO ()
suite = bracket (Dir.getTemporaryDirectory >>= \tmp -> mkdtemp (tmp </> "heni-runner-test-")) Dir.removePathForcibly $ \root -> do
fixture <- getDataFileName "examples/minimal"
original <- BS.readFile (fixture </> "src/Capacity.hs")
actual <- runCampaign (defaultRunOptions fixture (root </> "real")) exampleCampaign
assert' "actual Cabal baseline/candidate classifications" (case actual of RunResult {results} -> [v | CaseResult {verdict = v} <- results] == [Caught, Survived, UnexpectedBuildFailure])
BS.readFile (fixture </> "src/Capacity.hs") >>= assert' "actual source unchanged" . (== original)
stored <- readReport (root </> "real/report.json")
assert' "portable report round trip" (case stored of Right StoredReport {result} -> result == actual; _ -> False)
schemaOutput <- lookupEnv "HENI_SCHEMA_REPORT"
forM_ schemaOutput $ \path -> do
Dir.createDirectoryIfMissing True (takeDirectory path)
Dir.copyFile (root </> "real/report.json") path
recovered <- recoverReport (root </> "real")
assert' "journal recovery is explicitly incomplete" (case recovered of Right StoredReport {result = RunResult {status = RunUntrustworthy, results}} -> length results == 3; _ -> False)
BS.appendFile (root </> "real/journal.jsonl") "{\"sequence\":"
truncated <- recoverReport (root </> "real")
assert' "truncated last record tolerated" (case truncated of Right _ -> True; _ -> False)
Dir.renameDirectory (root </> "real") (root </> "moved")
moved <- readReport (root </> "moved/report.json")
assert' "moved report retains artifacts" (case moved of Right _ -> True; _ -> False)
journal <- BS.readFile (root </> "moved/journal.jsonl")
BS.writeFile (root </> "moved/journal.jsonl") ("garbage\n" <> journal)
corrupt <- recoverReport (root </> "moved")
assert' "interior corruption rejected" (case corrupt of Left _ -> True; _ -> False)
let selectedId = checked (mkCaseId "case")
buildId = checked (mkCommandId "build")
testId = checked (mkCommandId "test")
budget = checked (mkStageBudget 1000000)
cmd args = Command "heni-fixture" args Set.empty Nothing [] budget (OutputPolicy 1024)
source = root </> "source"
definition = CaseDefinition selectedId "Detect the intended fixture fault" Set.empty [Replace (checked (mkRelativeTarget "input")) "anchor" "mutant" 1] buildId (Just testId) Nothing (Diagnostic Test 1 "capacity rule violated") GenericCommands
campaign mode c hooks = checked (mkCampaign (Map.fromList [(buildId, cmd ["exit", "0"]), (testId, cmd ["check", "input", mode])]) [(c, hooks)])
Dir.createDirectory source
BS.writeFile (source </> "input") "anchor\n"
forM_ [("caught", "diagnostic", Caught), ("wrong", "wrong", WrongFailure), ("timeout", "timeout", TimedOut), ("survivor", "pass", Survived)] $ \(name, mode, expected) -> do
observed <- runCampaign (defaultRunOptions source (root </> name)) (campaign mode definition noHooks)
assert' (name ++ " classification") (case observed of RunResult {results = [CaseResult {verdict}]} -> verdict == expected; _ -> False)
let invalid = case definition of CaseDefinition i d ts _ b t s e mode -> CaseDefinition i d ts [Replace (checked (mkRelativeTarget "input")) "missing" "bad" 1] b t s e mode
stale <- runCampaign (defaultRunOptions source (root </> "stale")) (campaign "diagnostic" invalid noHooks)
assert' "invalid edit distinct" (case stale of RunResult {results = [CaseResult {verdict = InvalidMutation}]} -> True; _ -> False)
let patch = "diff --git a/input b/input\n--- a/input\n+++ b/input\n@@ -1 +1 @@\n-anchor\n+mutant\n"
patchedDefinition = case definition of CaseDefinition i d ts _ b t s e mode -> CaseDefinition i d ts [Patch patch] b t s e mode
available <- Dir.findExecutable "git"
required <- lookupEnv "HENI_REQUIRE_GIT"
when (required == Just "1" && available == Nothing) (error "patch validation requires Git")
case available of
Nothing -> putStrLn "Git unavailable: optional production patch check skipped"
Just _ -> do
patched <- runCampaign (defaultRunOptions source (root </> "patch")) (campaign "diagnostic" patchedDefinition noHooks)
assert' "production Git path uses supervised patch runner" (case patched of RunResult {results = [CaseResult {verdict = Caught}]} -> True; _ -> False)
let control = case definition of CaseDefinition i d ts _ b t s _ mode -> CaseDefinition i d ts [] b t s Control mode
failedControl = checked (mkCampaign (Map.fromList [(buildId, cmd ["exit", "0"]), (testId, cmd ["counter", root </> "control-counter"])]) [(control, noHooks)])
controlResult <- runCampaign (defaultRunOptions source (root </> "control")) failedControl
assert' "failed control cannot pass run" (case controlResult of RunResult {status = RunFailed, results = [CaseResult {verdict = WrongFailure}], failures = [ControlFailure {}]} -> True; _ -> False)
BS.writeFile (source </> "input") "mutant"
baseline <- runCampaign (defaultRunOptions source (root </> "baseline")) (campaign "diagnostic" definition noHooks)
assert' "failed baseline prevents candidate" (case baseline of RunResult {results = [CaseResult {verdict = NotRun}], failures = [BaselineFailure {}]} -> True; _ -> False)
BS.writeFile (source </> "input") "anchor\n"
let failedObserver = (defaultRunOptions source (root </> "observer")) {progress = \_ -> ioError (userError "observer failed")}
observer <- runCampaign failedObserver (campaign "diagnostic" definition noHooks)
assert' "observer failure untrustworthy" (case observer of RunResult {status = RunUntrustworthy} -> True; _ -> False)
let named = case definition of CaseDefinition i d ts es b t s _ mode -> CaseDefinition i d ts es b t s (NamedTests (Set.singleton "intended") NoReplay) mode
uncoveredBaseline <- runCampaign (defaultRunOptions source (root </> "empty-selection")) (campaign "diagnostic" named noHooks)
assert' "absent named selection blocks candidate" (case uncoveredBaseline of RunResult {results = [CaseResult {verdict = NotRun}], failures = [BaselineFailure {}]} -> True; _ -> False)
let existing = root </> "caught"
duplicate <- try (runCampaign (defaultRunOptions source existing) (campaign "diagnostic" definition noHooks)) :: IO (Either ConfigurationFailure RunResult)
assert' "existing output refused" (case duplicate of Left _ -> True; _ -> False)
-- Interrupt while a real candidate command is running. Finalization preserves
-- the active and queued case exactly once before rethrowing cancellation.
let readyPath = root </> "candidate-ready"
done <- newEmptyMVar
let second = case definition of CaseDefinition _ d ts es b t s e mode -> CaseDefinition (checked (mkCaseId "queued")) d ts es b t s e mode
commands = Map.fromList [(buildId, cmd ["exit", "0"]), (testId, cmd ["check", "input", "timeout", readyPath])]
cancellationCampaign = checked (mkCampaign commands [(definition, noHooks), (second, noHooks)])
cancelOptions = defaultRunOptions source (root </> "cancelled")
tid <- forkFinally (runCampaign cancelOptions cancellationCampaign) (putMVar done)
let waitReady = do exists <- Dir.doesFileExist readyPath; unless exists (threadDelay 5000 >> waitReady)
waitReady
throwTo tid ThreadKilled
finished <- timeout 3000000 (takeMVar done)
assert' "cancellation rethrown after finalization" (case finished of Just (Left _) -> True; _ -> False)
partial <- readReport (root </> "cancelled/report.json")
assert' "interrupted ledger exactly once" (case partial of Right StoredReport {result = RunResult {results, status = RunInterrupted}} -> length results == 2 && all (\CaseResult {verdict} -> verdict == NotRun) results; _ -> False)
-- Environment values and explicitly sensitive argv are redacted in reports.
let secretCommand = Command "heni-fixture" ["echo", "private-token"] (Set.singleton 1) Nothing [("TOKEN", "private-token")] budget (OutputPolicy 1024)
secretCampaign = checked (mkCampaign (Map.fromList [(buildId, secretCommand), (testId, cmd ["check", "input", "diagnostic"])]) [(definition, noHooks)])
_ <- runCampaign (defaultRunOptions source (root </> "redacted")) secretCampaign
reportBytes <- BS.readFile (root </> "redacted/report.json")
assert' "explicit secrets redacted in portable report" (not ("private-token" `BS.isInfixOf` reportBytes))
cleanupCount <- newIORef (0 :: Int)
let badCleanup =
(defaultRunOptions source (root </> "cleanup-failure"))
{ workspaceCleanup = \_ -> do
n <- atomicModifyIORef' cleanupCount (\n -> (n + 1, n + 1))
if n == 2 then ioError (userError "consumer cleanup failed") else pure ()
}
cleanupFailed <- runCampaign badCleanup (campaign "diagnostic" definition noHooks)
assert'
"cleanup failure retains primary candidate evidence"
( case cleanupFailed of
RunResult {status = RunUntrustworthy, results = [CaseResult {verdict = InfrastructureError, evidence = CaseEvidence {stages, cleanupErrors}}]} -> length stages == 2 && not (null cleanupErrors)
_ -> False
)
-- A syntactically complete bad record is corruption, not a truncated suffix.
originalJournal <- BS.readFile (root </> "caught/journal.jsonl")
BS.appendFile (root </> "caught/journal.jsonl") "null"
badLast <- recoverReport (root </> "caught")
assert' "complete invalid final journal record rejected" (case badLast of Left _ -> True; _ -> False)
BS.writeFile (root </> "caught/journal.jsonl") originalJournal
Dir.removeFile (root </> "caught/case-baseline-test.stdout.log")
missingArtifact <- readReport (root </> "caught/report.json")
assert' "missing baseline artifact is rejected" (case missingArtifact of Left _ -> True; _ -> False)
-- Failed final writes still return infrastructure information in memory.
let sabotage =
(defaultRunOptions source (root </> "unwritable"))
{ progress = \case
CaseFinished {} -> Dir.removePathForcibly (root </> "unwritable") >> BS.writeFile (root </> "unwritable") "blocks report directory"
_ -> pure ()
}
diskFailure <- runCampaign sabotage (campaign "diagnostic" definition noHooks)
assert' "report IO failure outranks caught case" (case diskFailure of RunResult {status = RunUntrustworthy} -> True; _ -> False)
putStrLn "Real Cabal mutation cycle, lifecycle, portable reports and recovery checks passed"