packages feed

heni-tasty (empty) → 0.1.0.0

raw patch · 7 files changed

+249/−0 lines, 7 filesdep +basedep +containersdep +directory

Dependencies added: base, containers, directory, filepath, heni, heni-tasty, process, stm, tasty, tasty-hunit, text, unix

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# 0.1.0.0 — 2026-10-24++## New Features++Initial optional framework reporter with stable test IDs and complete attempt-bound evidence. Framework dependencies remain outside the core package.
+ LICENSE view
@@ -0,0 +1,27 @@+BSD 3-Clause License++Copyright (c) 2026, Nadeem Bitar+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++1. Redistributions of source code must retain the above copyright notice, this+   list of conditions and the following disclaimer.+2. Redistributions in binary form must reproduce the above copyright notice,+   this list of conditions and the following disclaimer in the documentation+   and/or other materials provided with the distribution.+3. Neither the name of the copyright holder nor the names of its contributors+   may be used to endorse or promote products derived from this software+   without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,7 @@+# heni-tasty++Replace Tasty.defaultMain with heniMain. Native Tasty selection is intersected with HENI_SELECTOR during an attempt. Outside Heni, normal framework behavior is preserved.++Add `heni-tasty ==0.1.0.0` to the test component and select `"adapter": "heni-v1"` in the Heni case. Complete attempt-bound records prove selected/executed IDs and named failures. Missing, stale or partial evidence cannot count as detection.++See the [adapter guide](https://github.com/shinzui/heni/blob/master/docs/design/adapters.md) and packaged test fixture for runnable examples.
+ heni-tasty.cabal view
@@ -0,0 +1,74 @@+cabal-version: 3.4+name: heni-tasty+version: 0.1.0.0+synopsis: Attempt-bound Tasty evidence for Heni+license: BSD-3-Clause+license-file: LICENSE+author: Nadeem Bitar+maintainer: nadeem@gmail.com+copyright: 2026 Nadeem Bitar+category: Testing+build-type: Simple+tested-with: ghc ==9.12.4+homepage: https://github.com/shinzui/heni+bug-reports: https://github.com/shinzui/heni/issues+description: Optional framework reporter for trustworthy named mutation evidence.+extra-doc-files:+  CHANGELOG.md+  README.md++common common+  default-language: GHC2024+  default-extensions:+    DuplicateRecordFields+    OverloadedStrings++  ghc-options: -Wall+  build-depends:+    base >=4.21.2 && <4.22,+    containers >=0.7 && <0.9,+    heni ==0.1.0.0,+    text >=2.1.4 && <2.2,++library+  import: common+  hs-source-dirs: src+  exposed-modules: Test.Heni.Tasty+  build-depends:+    stm >=2.5.3.1 && <2.6,+    tasty >=1.5.5 && <1.6,++executable heni-tasty-fixture+  import: common+  hs-source-dirs: test/fixture+  main-is: Main.hs+  build-depends:+    heni-tasty ==0.1.0.0,+    stm >=2.5.3.1 && <2.6,+    tasty >=1.5.5 && <1.6,+    tasty-hunit >=0.10.2 && <0.11,++  ghc-options: -threaded++test-suite heni-tasty-test+  import: common+  type: exitcode-stdio-1.0+  hs-source-dirs: test+  main-is: Main.hs+  build-depends:+    directory >=1.3.10.1 && <1.4,+    filepath >=1.5.5 && <1.6,+    heni-tasty ==0.1.0.0,+    process >=1.6.26.1 && <1.7,+    unix >=2.8.8 && <2.9,++  build-tool-depends:+    heni-cli:heni,+    heni-tasty:heni-tasty-fixture,++  ghc-options: -threaded++source-repository head+  type: git+  location: https://github.com/shinzui/heni.git+  subdir: adapters/heni-tasty
+ src/Test/Heni/Tasty.hs view
@@ -0,0 +1,51 @@+{-# LANGUAGE CPP #-}++-- | Replace @defaultMain@ with 'heniMain'. Outside a Heni attempt, Tasty's+-- ordinary console, filters and options remain available.+module Test.Heni.Tasty (heniMain) where++import Control.Concurrent.STM+import Control.Monad (unless)+import Data.IntMap.Strict qualified as IntMap+import Data.Text qualified as Text+import System.Environment (getArgs, lookupEnv, withArgs)+import Test.Heni.Adapter+import Test.Tasty qualified as Tasty+import Test.Tasty.Ingredients.Basic (consoleTestReporter)+import Test.Tasty.Runners qualified as R++-- | Under Heni, a dedicated reporter owns completion after resource finalizers.+-- HENI_SELECTOR is a native Tasty pattern, intersected with existing patterns.+heniMain :: Tasty.TestTree -> IO ()+heniMain tree =+  currentEvidenceAttempt >>= \case+    Nothing -> Tasty.defaultMain tree+    Just attempt -> guardEvidenceAttempt attempt $ do+      args <- getArgs+      selector <- lookupEnv "HENI_SELECTOR"+      let extra = maybe [] (\s -> ["--pattern", s]) selector+      withArgs (args ++ extra) (R.defaultMainWithIngredients [reporter attempt] tree)++reporter :: EvidenceAttempt -> R.Ingredient+reporter attempt = R.TestReporter consoleOptions $ \opts tree -> Just $ \statuses -> do+  let ids = map stableTestId (R.foldTestTree paths opts tree)+  results <- atomically (mapM waitDone (IntMap.elems statuses))+  unless (length ids == length results) (ioError (userError "Tasty status/selection identity mismatch"))+  pure $ \_ -> do+    property <- readPropertyReplay attempt+    let observations = zipWith observation ids results+    emitSuite attempt (SuiteEvidence ("tasty/" <> Text.pack VERSION_tasty <> ";heni-tasty/0.1.0.0") Nothing ids observations [] (fst <$> property) (maybe [] snd property))+    pure (all R.resultSuccessful results)+  where+    consoleOptions = case consoleTestReporter of R.TestReporter options _ -> options; _ -> []+    waitDone status = readTVar status >>= \case R.Done result -> pure result; _ -> retry+    paths =+      R.trivialFold+        { R.foldSingle = \_ name _ -> [[Text.pack name]],+          R.foldGroup = \_ name groups -> map (Text.pack name :) (concat groups)+        }+    observation ident result = case R.resultOutcome result of+      R.Success -> TestPassed ident+      R.Failure R.TestFailed -> TestFailed ident+      R.Failure R.TestDepFailed -> TestSkipped ident+      R.Failure reason -> TestSetupFailed ident (Text.pack (show reason))
+ test/Main.hs view
@@ -0,0 +1,66 @@+module Main (main) where++import Control.Exception+import Control.Monad (forM_, unless)+import Data.Map.Strict qualified as Map+import Data.Set qualified as Set+import Data.Text qualified as Text+import System.Directory qualified as Dir+import System.Exit (ExitCode (..))+import System.FilePath ((</>))+import System.Posix.Temp (mkdtemp)+import System.Process (readProcessWithExitCode)+import System.Timeout (timeout)+import Test.Heni++checked :: (Show e) => Either e a -> a+checked = either (error . show) id++assert' :: String -> Bool -> IO ()+assert' why good = unless good (error why)++main :: IO ()+main = timeout 120000000 checks >>= \case Nothing -> error "adapter integration watchdog"; Just () -> pure ()++checks :: IO ()+checks = bracket (Dir.getTemporaryDirectory >>= \p -> mkdtemp (p </> "heni-tasty-")) Dir.removePathForcibly $ \root -> do+  let source = root </> "source"+      exe = "heni-tasty-fixture"+      bid = checked (mkCommandId "build")+      tid = checked (mkCommandId "test")+      cid = checked (mkCaseId "named")+      command args = Command exe args Set.empty Nothing [] (checked (mkStageBudget 5000000)) (OutputPolicy 4096)+      definition mode selector intended replay =+        CaseDefinition+          cid+          "framework evidence"+          Set.empty+          [Replace (checked (mkRelativeTarget "input")) "anchor" mode 1]+          bid+          (Just tid)+          selector+          (NamedTests (Set.singleton intended) replay)+          StructuredTestsV1+      run name def hooks args =+        runCampaign+          (defaultRunOptions source (root </> name))+          (checked (mkCampaign (Map.fromList [(bid, command ["--heni-build"]), (tid, command args)]) [(def, hooks)]))+      is verdict RunResult {results = [CaseResult {verdict = actual}]} = actual == verdict+      is _ _ = False+  Dir.createDirectory source+  writeFile (source </> "input") "anchor"+  old <- Dir.getCurrentDirectory+  direct <- bracket_ (Dir.setCurrentDirectory source) (Dir.setCurrentDirectory old) (readProcessWithExitCode exe [] "")+  assert' "ordinary direct execution" (case direct of (ExitSuccess, _, _) -> True; _ -> False)+  forM_ [("intended", Caught), ("wrong", WrongFailure), ("setup", InfrastructureError)] $ \(mode, expected) -> do+    result <- run mode (definition (Text.pack mode) Nothing "suite/intended" NoReplay) noHooks []+    assert' (mode ++ ": " ++ show result) (is expected result)+  empty <- run "empty" (definition "intended" (Just "absent") "suite/intended" NoReplay) noHooks []+  assert' "empty selection baseline prevents candidate" (is NotRun empty)+  filtered <- run "filtered" (definition "intended" (Just "intended") "suite/intended" NoReplay) noHooks ["--pattern", "intended"]+  assert' ("native and Heni filters: " ++ show filtered) (is Caught filtered)++  writeFile (root </> "manifest.json") "{\"schemaVersion\": 1, \"sourceRoot\": \"source\", \"commands\": {\"build\": {\"executable\": \"heni-tasty-fixture\", \"arguments\": [\"--heni-build\"]}, \"test\": {\"executable\": \"heni-tasty-fixture\"}}, \"cases\": [{\"id\": \"named\", \"description\": \"framework evidence\", \"build\": \"build\", \"test\": \"test\", \"adapter\": \"heni-v1\", \"edits\": [{\"kind\": \"replace\", \"path\": \"input\", \"old\": \"anchor\", \"new\": \"intended\"}], \"expect\": {\"kind\": \"named-tests\", \"failures\": [\"suite/intended\"]}}]}"+  cli <- readProcessWithExitCode "heni" ["run", root </> "manifest.json", "--output", root </> "cli", "--json"] ""+  assert' ("CLI adapter parity: " ++ show cli) (case cli of (ExitSuccess, _, _) -> True; _ -> False)+  putStrLn "tasty direct execution and named subprocess evidence passed"
+ test/fixture/Main.hs view
@@ -0,0 +1,19 @@+module Main (main) where++import System.Environment (getArgs)+import Test.Heni.Tasty+import Test.Tasty+import Test.Tasty.HUnit++main :: IO ()+main =+  getArgs >>= \case+    ["--heni-build"] -> pure ()+    _ -> do+      mode <- readFile "input"+      heniMain $+        testGroup+          "suite"+          [ testCase "intended" (assertBool "intended assertion" (mode /= "intended")),+            testCase "other" (if mode == "setup" then ioError (userError "fixture setup failed") else assertBool "other assertion" (mode /= "wrong"))+          ]