packages feed

aihc-parser-1.0.0.2: test/Test/ErrorMessages/Suite.hs

{-# LANGUAGE OverloadedStrings #-}

module Test.ErrorMessages.Suite
  ( errorMessageTests,
  )
where

import Control.Monad (unless, when)
import Data.Text qualified as T
import ParserErrorGolden qualified as PEG
import System.FilePath (takeExtension)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (Assertion, assertFailure, testCase)

errorMessageTests :: IO TestTree
errorMessageTests = do
  cases <- PEG.loadErrorMessageCases
  checks <- mapM mkCaseTest cases
  summary <- mkSummaryTest cases
  pure (testGroup "error-messages" ([fixtureValidationTests] <> checks <> [summary]))

mkCaseTest :: PEG.ErrorMessageCase -> IO TestTree
mkCaseTest meta = pure $ testCase (PEG.caseId meta) (assertCase meta)

mkSummaryTest :: [PEG.ErrorMessageCase] -> IO TestTree
mkSummaryTest cases = do
  let outcomes = map evaluate cases
  pure $ testCase "summary" (assertNoRegressions outcomes)

assertCase :: PEG.ErrorMessageCase -> Assertion
assertCase meta =
  case PEG.evaluateErrorMessageCase meta of
    (PEG.OutcomeFail, details) ->
      assertFailure
        ( "Regression in parser error case "
            <> PEG.caseId meta
            <> " ("
            <> PEG.caseCategory meta
            <> ")"
            <> " details="
            <> details
        )
    _ -> pure ()

assertNoRegressions :: [(PEG.ErrorMessageCase, PEG.Outcome, String)] -> Assertion
assertNoRegressions outcomes = do
  let (passN, failN) = PEG.progressSummary outcomes
      totalN = passN + failN
      completion = pct passN totalN
  when (failN > 0) $
    assertFailure
      ( "parser error regressions found. "
          <> "pass="
          <> show passN
          <> " fail="
          <> show failN
          <> " completion="
          <> show completion
          <> "%"
      )

evaluate :: PEG.ErrorMessageCase -> (PEG.ErrorMessageCase, PEG.Outcome, String)
evaluate meta =
  let (outcome, details) = PEG.evaluateErrorMessageCase meta
   in (meta, outcome, details)

pct :: Int -> Int -> Double
pct done totalN
  | totalN <= 0 = 0.0
  | otherwise = fromIntegral (done * 10000 `div` totalN) / 100.0

fixtureValidationTests :: TestTree
fixtureValidationTests =
  testGroup
    "fixture-parse"
    [ testCase "rejects missing required keys" $
        case PEG.parseErrorMessageCaseText "missing.yaml" "ghc: bad\n" of
          Left _ -> pure ()
          Right _ -> assertFailure "expected parse failure for missing required YAML keys",
      testCase "rejects unexpected status field" $
        case PEG.parseErrorMessageCaseText "status.yaml" invalidStatusFixture of
          Left _ -> pure ()
          Right _ -> assertFailure "expected parse failure when status field is present",
      testCase "accepts basic fixtures" $
        case PEG.parseErrorMessageCaseText "pass.yaml" validPassFixture of
          Left err -> assertFailure ("expected parse success, got: " <> err)
          Right _ -> pure (),
      testCase "only YAML fixtures are loaded" $ do
        cases <- PEG.loadErrorMessageCases
        mapM_
          ( \meta ->
              unless (takeExtension (PEG.casePath meta) `elem` [".yaml", ".yml"]) $
                assertFailure ("unexpected non-error-message fixture loaded: " <> PEG.casePath meta)
          )
          cases
    ]

invalidStatusFixture :: T.Text
invalidStatusFixture =
  T.unlines
    [ "src: bad",
      "ghc: bad",
      "aihc: bad",
      "status: pass"
    ]

validPassFixture :: T.Text
validPassFixture =
  T.unlines
    [ "src: bad",
      "ghc: bad",
      "aihc: bad"
    ]