packages feed

tadka-2.0.0.0: test/props/Phase7.hs

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

-- | Phase 7 properties: the @stale@ flag is derived from 'LabelState' (not
-- inferred from absence), and a totality smoke check for the JSON handler.
module Phase7 (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              (pretty)

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

import           GenDiag                    (GD (..), genGD, selfJust, selfNothing)
import           Tadka
import           Tadka.Internal             (buildContext)
import           Tadka.Internal.Related     (walkRelated)
import           Tadka.Internal.Renderer.Json (DiagnosticDTO (..), LabelDTO (..), toDTO)

group :: Group
group = Group "Phase 7 - JSON handler + DTO"
  [ ("ok label -> stale:false with position",   prop_okLabel)
  , ("stale label -> stale:true, null position", prop_staleLabel)
  , ("JSON render is total (fuelled)",           prop_totality)
  , ("JSON render is total (pathological)",      prop_totalityCycles)
  ]

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

-- Build a one-label DTO for a diagnostic with the given span over "abcdef".
labelOf :: Int -> Int -> LabelDTO
labelOf off len =
  case dtoLabels (toDTO 8 (walkRelated 8 (SomeDiagnostic d))) of
    (l:_) -> l
    []    -> error "labelOf: expected exactly one label"
  where
    src = rightOrErr (mkNamedSource "f.hs" "abcdef")
    d   = GD "m" Nothing
             (buildContext src [(rightOrErr (mkSpan off len), Just (pretty ("x" :: Text)))])
             Nothing Nothing [] Nothing Nothing

prop_okLabel :: Property
prop_okLabel = property $ do
  len <- forAll (Gen.int (Range.linear 1 3))
  let l = labelOf 1 len       -- in bounds => LabelOk
  ldStale l  === False
  ldLine l   === Just 1
  ldLength l === Just len

prop_staleLabel :: Property
prop_staleLabel = withTests 1 . property $ do
  let l = labelOf 100 3       -- out of bounds => LabelStale
  ldStale l  === True
  ldLine l   === Nothing
  ldColumn l === Nothing
  ldLength l === Nothing

renderJsonText :: Diagnostic e => e -> Text
renderJsonText e = case selectRenderer (withTarget TJson defaultConfig) of
  SomeRenderer r@(Json _) -> TL.toStrict (TLE.decodeUtf8 (A.encode (render r e)))
  _                       -> ""

prop_totality :: Property
prop_totality = property $ do
  fuel <- forAll (Gen.int (Range.linear 0 3))
  d    <- forAllWith (const "<generated diagnostic>") (genGD fuel)
  res  <- liftIO (try (evaluate (T.length (renderJsonText d))) :: IO (Either SomeException Int))
  case res of
    Right _ -> success
    Left e  -> annotate (show e) >> failure

prop_totalityCycles :: Property
prop_totalityCycles = withTests 1 . property $ do
  res <- liftIO (try (evaluate (sum (map (T.length . renderJsonText) [selfNothing, selfJust])))
                   :: IO (Either SomeException Int))
  case res of
    Right _ -> success
    Left e  -> annotate (show e) >> failure