packages feed

polysemy-test-0.11.0.1: lib/Polysemy/Test/Data/TestError.hs

{-# options_haddock prune #-}

-- |TestError data type, Internal
module Polysemy.Test.Data.TestError (
  TestError (TestError, SkipTest, UnsafeTestError),
  testError,
  skipTest,
  SkipTestDefaultValue (..),
) where

-- | An error that occurred in the test machinery.
--
-- The pattern synonym is used for construction to ensure that the call site's stack is stored.
-- There is no 'IsString' instance because it can't propagate the call stack.
-- Use 'testError' to throw a string literal.
--
-- The 'Bool' field indicates whether this is a skip request (@True@) or a regular error (@False@).
data TestError where
  UnsafeTestError :: HasCallStack => Bool -> Text -> TestError

deriving stock instance Eq TestError
deriving stock instance Show TestError

-- | Construct a test error so that the call site's stack is stored in the value, for printing the correct location in
-- hedgehog messages.
--
-- Match on errors that aren't skip requests.
pattern TestError :: HasCallStack => HasCallStack => Text -> TestError
pattern TestError err <- UnsafeTestError False err where
  TestError err = withFrozenCallStack $ UnsafeTestError False err

-- | Match on skip requests.
pattern SkipTest :: HasCallStack => HasCallStack => Text -> TestError
pattern SkipTest err <- UnsafeTestError True err

{-# complete TestError, SkipTest #-}

-- | Throw a 'TestError' with the call site's stack.
testError ::
  ∀ a r .
  HasCallStack =>
  Member (Error TestError) r =>
  Text ->
  Sem r a
testError msg =
  withFrozenCallStack $ throw (TestError msg)

-- | Skip the current test with a reason message.
--
-- This is intended for tests that require specific environmental conditions (e.g. programs in @$PATH@) that cannot be
-- checked at the test entry point.
-- The test will only be skipped if a fallback value is available, either via 'SkipTestDefaultValue' or an explicit
-- override.
skipTest ::
  ∀ a r .
  HasCallStack =>
  Member (Error TestError) r =>
  Text ->
  Sem r a
skipTest msg =
  withFrozenCallStack $ throw (UnsafeTestError True msg)

-- | Provides a default fallback value for skipped tests.
--
-- When a test calls 'skipTest', the runner can recover with the fallback value instead of failing.
-- The base instance returns 'Nothing', meaning skipped tests will fail.
-- The @()@ instance returns @'Just' ()@, allowing @'TestT' IO ()@ tests to be skipped silently.
class SkipTestDefaultValue a where
  skipTestDefaultValue :: Maybe a
  skipTestDefaultValue = Nothing

instance {-# overlappable #-} SkipTestDefaultValue a where

instance SkipTestDefaultValue () where
  skipTestDefaultValue = Just ()