packages feed

tadka-2.0.0.0: test/props/Phase11.hs

{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- | Phase 11 consolidation: the render-totality property broadened to every
-- 'Renderer' target, the cycle-detection marker property, and the vision's
-- Success Criterion exercised end-to-end (a genuinely staled span renders a
-- clear in-report reason rather than a silently shorter report).
module Phase11 (group) where

import           Control.Exception          (SomeException, evaluate, try)
import           Control.Monad.IO.Class     (liftIO)
import qualified Data.Aeson                 as A
import           Data.Text                  (Text)
import qualified Data.Text                  as T
import qualified Data.Text.Lazy             as TL
import qualified Data.Text.Lazy.Encoding    as TLE
import           Prettyprinter              (LayoutOptions (..), PageWidth (Unbounded),
                                             layoutPretty, pretty)
import           Prettyprinter.Render.Text  (renderStrict)

import           Hedgehog
import qualified Hedgehog.Gen               as Gen
import qualified Hedgehog.Range             as Range

import           GenDiag                    (GD (..), genGD)
import           Tadka
import           Tadka.Internal             (buildContext)

group :: Group
group = Group "Phase 11 - consolidation & release audit"
  [ ("render is total for every target (Hedgehog)", prop_totalAllTargets)
  , ("cycle marker renders at most once",           prop_cycleMarker)
  , ("staled span renders a clear reason",          prop_staleReason)
  , ("staled label is explicit in JSON",            prop_staleJson)
  ]

rightOrErr :: Show a => Either a b -> b
rightOrErr = either (error . show) id

-- Render any diagnostic through the chosen target, collapsed to Text.
renderTarget :: Target -> SomeDiagnostic -> Text
renderTarget tgt (SomeDiagnostic e) =
  case selectRenderer cfg of
    SomeRenderer r@(Graphical _)  -> renderStrict (layoutPretty (LayoutOptions Unbounded) (render r e))
    SomeRenderer r@(Narratable _) -> render r e
    SomeRenderer r@(Json _)       -> TL.toStrict (TLE.decodeUtf8 (A.encode (render r e)))
  where
    cfg = withColorMode ColorNever (withUnicodeMode UnicodeAlways (withTarget tgt defaultConfig))

-- Broadens the phase-local smoke checks into one Hedgehog property over every
-- Renderer target and fuel-bounded generated diagnostics.
prop_totalAllTargets :: Property
prop_totalAllTargets = property $ do
  fuel <- forAll (Gen.int (Range.linear 0 3))
  tgt  <- forAll (Gen.element [TGraphical, TNarratable, TJson])
  d    <- forAllWith (const "<generated diagnostic>") (genGD fuel)
  res  <- liftIO (try (evaluate (T.length (renderTarget tgt (SomeDiagnostic d))))
                    :: IO (Either SomeException Int))
  case res of
    Right _ -> success
    Left e  -> annotate (show e) >> failure

-- A node that relates to itself (same diagnosticId): the repeat is cycle-omitted,
-- so a marker in its message renders at most once, on every target.
marker :: Text
marker = "ZZMARKERZZ"

cyclic :: GD
cyclic =
  let d = GD ("dup problem " <> marker) Nothing NoContext Nothing Nothing
             [SomeDiagnostic d] (Just (mkDiagnosticId "dup")) Nothing
  in d

prop_cycleMarker :: Property
prop_cycleMarker = withTests 1 . property $
  mapM_ (\tgt -> assert (T.count marker (renderTarget tgt (SomeDiagnostic cyclic)) <= 1))
        [TGraphical, TNarratable, TJson]

-- Success Criterion: a genuinely staled span (out of bounds) renders a clear
-- in-report reason, not a silently shorter report.
staleDiag :: GD
staleDiag = GD "undefined variable" (Just (rightOrErr (mkDiagnosticCode "tadka::E0001")))
               ctx Nothing Nothing [] Nothing Nothing
  where
    ctx = buildContext (rightOrErr (mkNamedSource "f.hs" "abc"))
            [ (rightOrErr (mkSpan 100 3), Just (pretty ("not in scope" :: Text))) ]

prop_staleReason :: Property
prop_staleReason = withTests 1 . property $ do
  let g = renderTarget TGraphical (SomeDiagnostic staleDiag)
      n = renderTarget TNarratable (SomeDiagnostic staleDiag)
  assert ("span unavailable" `T.isInfixOf` g)
  assert ("could not be shown" `T.isInfixOf` n)

prop_staleJson :: Property
prop_staleJson = withTests 1 . property $ do
  let j = renderTarget TJson (SomeDiagnostic staleDiag)
  assert ("\"stale\":true" `T.isInfixOf` j)