packages feed

aihc-parser-1.0.0.2: test/Test/StackageProgress/Summary.hs

module Test.StackageProgress.Summary (stackageProgressSummaryTests) where

import Data.List (isInfixOf)
import StackageProgress.Summary
import Test.Tasty
import Test.Tasty.HUnit

stackageProgressSummaryTests :: TestTree
stackageProgressSummaryTests =
  testGroup
    "stackage progress summary"
    [ testCase "tracks counts without retaining optional output" test_countsOnly,
      testCase "preserves succeeded and failed package output" test_keptOutputs,
      testCase "limits ghc errors and falls back to package reason" test_ghcErrors,
      testCase "extracts prompt candidates from parser failures" test_promptCandidate,
      testCase "extracts prompt candidates from any parser failure" test_promptCandidateIncludesAnyFailure,
      testCase "renders prompt text and deterministic candidate selection" test_promptRendering
    ]

test_countsOnly :: Assertion
test_countsOnly = do
  let summary =
        finalizeSummary $
          addPackageResults
            (SummaryOptions False False 0)
            [ packageResult "alpha" True True True "" Nothing 0,
              packageResult "beta" False True False "parser failed" Nothing 128
            ]
            emptySummary
  assertEqual "ours count" 1 (summarySuccessOursN summary)
  assertEqual "hse count" 2 (summarySuccessHseN summary)
  assertEqual "ghc count" 1 (summarySuccessGhcN summary)
  assertEqual "succeeded packages omitted" [] (summarySucceededPackages summary)
  assertEqual "failed packages omitted" [] (summaryFailedPackages summary)
  assertEqual "ghc errors omitted" [] (summaryGhcErrors summary)

test_keptOutputs :: Assertion
test_keptOutputs = do
  let summary =
        finalizeSummary $
          addPackageResults
            (SummaryOptions True True 0)
            [ packageResult "alpha" True True True "" Nothing 0,
              packageResult "beta" False True True "parser failed" Nothing 256,
              packageResult "gamma" True False True "" Nothing 0
            ]
            emptySummary
  assertEqual "succeeded packages keep encounter order" ["alpha-1.0.0", "gamma-1.0.0"] (summarySucceededPackages summary)
  assertEqual
    "failed table keeps parser failures only"
    [FailedPackage "beta-1.0.0" 256 []]
    (summaryFailedPackages summary)

test_ghcErrors :: Assertion
test_ghcErrors = do
  let summary =
        finalizeSummary $
          addPackageResults
            (SummaryOptions False False 2)
            [ packageResult "alpha" True True False "" (Just "ghc exploded") 0,
              packageResult "beta" False True False "  parser failed early  " Nothing 64,
              packageResult "gamma" False True False "should be truncated" (Just "ignored") 32
            ]
            emptySummary
  assertEqual
    "ghc errors are limited and use fallback reason text"
    [ ("alpha-1.0.0", "ghc exploded"),
      ("beta-1.0.0", "No direct GHC diagnostic; package failed before/around GHC check: parser failed early")
    ]
    (summaryGhcErrors summary)

test_promptCandidate :: Assertion
test_promptCandidate = do
  let result =
        packageResult
          "monad-st"
          False
          True
          True
          "parse failed in /tmp/Control/Monad/ST/Class.hs: Parse failed: unexpected {'"
          Nothing
          1024
  assertEqual
    "extracts prompt candidate preserving original error text"
    ( Just
        PromptCandidate
          { promptPackageName = "monad-st",
            promptErrorMessage = "parse failed in /tmp/Control/Monad/ST/Class.hs: Parse failed: unexpected {'"
          }
    )
    (promptCandidateFromResult result)

test_promptCandidateIncludesAnyFailure :: Assertion
test_promptCandidateIncludesAnyFailure = do
  let roundtripOnly = packageResult "roundtrip-only" False True True "roundtrip mismatch in /tmp/Foo.hs" Nothing 1024
      parseSucceeded = packageResult "parse-success" True True True "parse failed in /tmp/Bar.hs" Nothing 1024
  assertEqual
    "non-parse failure text is still treated as a parser failure"
    ( Just
        PromptCandidate
          { promptPackageName = "roundtrip-only",
            promptErrorMessage = "roundtrip mismatch in /tmp/Foo.hs"
          }
    )
    (promptCandidateFromResult roundtripOnly)
  assertEqual "successful parser result should be ignored" Nothing (promptCandidateFromResult parseSucceeded)

test_promptRendering :: Assertion
test_promptRendering = do
  let candidates =
        [ PromptCandidate "a" "PARSE_ERROR: a",
          PromptCandidate "b" "PARSE_ERROR: b",
          PromptCandidate "c" "PARSE_ERROR: c"
        ]
  assertEqual "seed 0 chooses first" (Just (PromptCandidate "a" "PARSE_ERROR: a")) (selectPromptCandidate 0 candidates)
  assertEqual "seed 1 chooses second" (Just (PromptCandidate "b" "PARSE_ERROR: b")) (selectPromptCandidate 1 candidates)
  assertEqual "seed wraps by modulo" (Just (PromptCandidate "c" "PARSE_ERROR: c")) (selectPromptCandidate 5 candidates)
  let template =
        unlines
          [ "# Error messages:",
            "{{ERROR_MESSAGES}}",
            "",
            "Re-test by running: nix run .#aihc-dev -- hackage-tester {{PACKAGE_NAME}}",
            "",
            "Fix '{{PACKAGE_NAME}}'"
          ]
      rendered =
        renderPrompt
          template
          PromptCandidate
            { promptPackageName = "monad-st",
              promptErrorMessage = "parse failed in /tmp/Control/Monad/ST/Class.hs"
            }
  assertBool "prompt includes heading" ("# Error messages:" `isInfixOf` rendered)
  assertBool "prompt includes re-test command" ("nix run .#aihc-dev -- hackage-tester monad-st" `isInfixOf` rendered)
  assertBool "prompt includes package replacement" ("Fix 'monad-st'" `isInfixOf` rendered)

packageResult :: String -> Bool -> Bool -> Bool -> String -> Maybe String -> Integer -> PackageResult
packageResult name oursOk hseOk ghcOk reason ghcError size =
  PackageResult
    { package = PackageSpec name "1.0.0",
      packageOursOk = oursOk,
      packageHseOk = hseOk,
      packageGhcOk = ghcOk,
      packageReason = reason,
      packageGhcError = ghcError,
      packageSourceSize = size,
      packageFileErrors = []
    }