packages feed

fragr-0.1.0.0: test/Spec/Error.hs

-- | Misuse that must be fatal rather than silently wrong.
module Spec.Error (tests) where

import Control.Monad (void)
import Data.Coerce (coerce)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (testCase)

import Fragr (Handle (..))
import Fragr qualified as FG
import Utils

tests :: TestTree
tests =
  testGroup
    "error model"
    [ staleRead
    , staleWrite
    , readOwnCreate
    , readOwnWrite
    , wrongResourceType
    , undeclaredGet
    , outOfRangeHandle
    , executeBeforeCompile
    ]

staleRead :: TestTree
staleRead = testCase "reading a stale handle is fatal" do
  g <- FG.newFrameGraph @Env @Env
  h1 <- FG.importResource g "BB" (tex "BB") (Tex 1)
  FG.addPass_ g "P" (FG.write_ h1) (pure ())
  assertFatal $ FG.addPass_ g "Q" (FG.read h1) (pure ())

staleWrite :: TestTree
staleWrite = testCase "writing a stale handle is fatal" do
  g <- FG.newFrameGraph @Env @Env
  h1 <- FG.importResource g "BB" (tex "BB") (Tex 1)
  FG.addPass_ g "P" (FG.write_ h1) (pure ())
  assertFatal $ FG.addPass_ g "Q" (FG.write_ h1) (pure ())

readOwnCreate :: TestTree
readOwnCreate = testCase "reading a handle the pass created is fatal" do
  g <- FG.newFrameGraph @Env @Env
  assertFatal $
    FG.addPass
      g
      "P"
      ( do
          h <- FG.create @Tex "R" (tex "R")
          FG.read h
      )
      \_data -> pure ()

readOwnWrite :: TestTree
readOwnWrite = testCase "reading a handle the pass wrote is fatal" do
  g <- FG.newFrameGraph @Env @Env
  h1 <- FG.importResource g "BB" (tex "BB") (Tex 1)
  assertFatal $
    FG.addPass
      g
      "P"
      ( do
          h2 <- FG.write h1
          FG.read h2
      )
      \_data -> pure ()

wrongResourceType :: TestTree
wrongResourceType = testCase "get with the wrong resource type is fatal" do
  g <- FG.newFrameGraph @Env @Env
  env <- newEnv
  _ <-
    FG.addPass
      g
      "P"
      ( do
          h <- FG.create @Tex "R" (tex "R")
          FG.write_ h
          FG.setSideEffect
          pure h
      )
      -- Honest handles carry their resource type; forging a
      -- wrongly-typed one takes a 'coerce'. The runtime guard
      -- still catches it.
      \h -> void (FG.get @Buf (coerce h))
  FG.compile g
  assertFatal $ FG.execute g env env

undeclaredGet :: TestTree
undeclaredGet = testCase "get on an undeclared handle is fatal" do
  g <- FG.newFrameGraph @Env @Env
  env <- newEnv
  h <-
    FG.addPass
      g
      "P"
      ( do
          h <- FG.create @Tex "R" (tex "R")
          FG.write_ h
          FG.setSideEffect
          pure h
      )
      \_data -> pure ()
  _ <-
    FG.addPass
      g
      "Q"
      ( do
          FG.read h
          FG.setSideEffect
      )
      \_data ->
        -- Q reads h, but this forged handle was never declared.
        void (FG.get @Tex (Handle 99))
  FG.compile g
  assertFatal $ FG.execute g env env

outOfRangeHandle :: TestTree
outOfRangeHandle = testCase "out-of-range handles are fatal, not merely invalid" do
  g <- FG.newFrameGraph @Env @Env
  assertFatal $ FG.isValid g (Handle 99)

executeBeforeCompile :: TestTree
executeBeforeCompile = testCase "execute before compile is fatal" do
  g <- FG.newFrameGraph @Env @Env
  env <- newEnv
  assertFatal $ FG.execute g env env