packages feed

moonlight-pale-0.1.0.0: test/compile-diagnostics/CompileDiagnosticsSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module CompileDiagnosticsSpec
  ( tests,
  )
where

import Data.Aeson (decode, encode)
import Data.List.NonEmpty (NonEmpty (..))
import Moonlight.Pale.Test.Assertions (expectRightWithLabel)
import Moonlight.Pale.TestSupport.CompileDiagnostics
  ( CompileDiagnosticsSession,
    CompileFixtureFailure (..),
    DiagnosticSnapshot (..),
    GhcPackageSpec (..),
    NormalizedDiagnostic (..),
    SnapshotExit (..),
    UnstructuredCompileFailure (..),
    compileFixtures,
    normalizeSnapshot,
    openCompileDiagnosticsSession,
  )
import System.Directory (getCurrentDirectory)
import System.Exit (ExitCode (..))
import Test.Tasty (TestTree, testGroup, withResource)
import Test.Tasty.HUnit (assertEqual, assertFailure, testCase)

tests :: TestTree
tests =
  withResource acquireCompileContext (const (pure ())) $ \getCompileContext ->
    testGroup
      "Moonlight.Pale.TestSupport.CompileDiagnostics"
      [ testCase "compileFixtures captures a round-trippable clean snapshot" $
          compileTrivialFixture getCompileContext,
        testCase "compileFixtures preserves unstructured failures" $
          compileUnstructuredFailure getCompileContext,
        testCase "snapshot JSON establishes canonical diagnostic order" $
          assertCanonicalSnapshotRoundTrip
      ]

compileTrivialFixture :: IO (FilePath, CompileDiagnosticsSession) -> IO ()
compileTrivialFixture getCompileContext = do
  (packageRoot, session) <- getCompileContext
  compileResult <- compileFixtures session [] (packageRelativeFixturePath :| [])
  fixtureResult <- expectRightWithLabel "compile fixture" compileResult
  let snapshot :: DiagnosticSnapshot
      snapshot = normalizeSnapshot packageRoot packageRelativeFixturePath fixtureResult
  assertEqual "clean fixture exits successfully" SnapshotSuccess (snapshotExit snapshot)
  assertEqual "diagnostic snapshot JSON round-trips" (pure snapshot) (roundTripDiagnosticSnapshot snapshot)

compileUnstructuredFailure :: IO (FilePath, CompileDiagnosticsSession) -> IO ()
compileUnstructuredFailure getCompileContext = do
  (_, session) <- getCompileContext
  compileResult <-
    compileFixtures
      session
      [GhcPackageId "pale-definitely-missing-unit-id"]
      (packageRelativeFixturePath :| [])
  case compileResult of
    Left (CompileFixtureUnstructuredFailure failureValue) ->
      case unstructuredCompileExitCode failureValue of
        ExitFailure _ -> pure ()
        ExitSuccess -> assertFailure "unstructured compiler failure cannot report success"
    Left otherFailure ->
      assertFailure ("expected unstructured compiler failure, got " <> show otherFailure)
    Right fixtureResult ->
      assertFailure ("expected fixture compilation to fail, got " <> show fixtureResult)

acquireCompileContext :: IO (FilePath, CompileDiagnosticsSession)
acquireCompileContext = do
  packageRoot <- getCurrentDirectory
  session <-
    expectRightWithLabel "compile diagnostics session"
      =<< openCompileDiagnosticsSession packageRoot
  pure (packageRoot, session)

assertCanonicalSnapshotRoundTrip :: IO ()
assertCanonicalSnapshotRoundTrip =
  assertEqual
    "decoded snapshot diagnostics are canonical"
    (Just canonicalSnapshot)
    (roundTripDiagnosticSnapshot nonCanonicalSnapshot)
  where
    canonicalSnapshot =
      nonCanonicalSnapshot
        { snapshotDiagnostics = [alphaDiagnostic, betaDiagnostic]
        }
    nonCanonicalSnapshot =
      DiagnosticSnapshot
        { snapshotFixture = "Fixture.hs",
          snapshotDiagnosticsFlag = "-fdiagnostics-as-json",
          snapshotExit = SnapshotFailure,
          snapshotDiagnostics = [betaDiagnostic, alphaDiagnostic]
        }
    alphaDiagnostic =
      NormalizedDiagnostic
        { normalizedCode = "GHC-001",
          normalizedFile = "Fixture.hs",
          normalizedStartLine = 1,
          normalizedStartCol = 1,
          normalizedEndLine = 1,
          normalizedEndCol = 2
        }
    betaDiagnostic =
      NormalizedDiagnostic
        { normalizedCode = "GHC-002",
          normalizedFile = "Fixture.hs",
          normalizedStartLine = 2,
          normalizedStartCol = 1,
          normalizedEndLine = 2,
          normalizedEndCol = 2
        }

packageRelativeFixturePath :: FilePath
packageRelativeFixturePath =
  "test/compile-diagnostics/fixtures/Trivial.hs"

roundTripDiagnosticSnapshot :: DiagnosticSnapshot -> Maybe DiagnosticSnapshot
roundTripDiagnosticSnapshot snapshot =
  decode (encode snapshot)