packages feed

skeletest-0.3.6: test/Skeletest/PropSpec.hs

{-# LANGUAGE OverloadedRecordDot #-}

module Skeletest.PropSpec (spec) where

import Skeletest
import Skeletest.Predicate qualified as P
import Skeletest.Prop.Gen qualified as Gen
import Skeletest.Prop.Range qualified as Range
import Skeletest.TestUtils.CallStack (sanitizeTraceback)
import Skeletest.TestUtils.Integration

spec :: Spec
spec = do
  describe "prop" $ do
    integration . it "shows hedgehog context for arbitrary failures" $ do
      runner <- getFixture @TestRunner
      runner.addTestFile "ExampleSpec.hs" $
        [ "module ExampleSpec (spec) where"
        , ""
        , "import Control.Exception"
        , "import Control.Monad.IO.Class (liftIO)"
        , "import Skeletest"
        , "import qualified Skeletest.Prop as Prop"
        , "import qualified Skeletest.Prop.Gen as Gen"
        , ""
        , "spec = do"
        , "  prop \"error\" $ do"
        , "    x <- forAll $ pure True"
        , "    if x then liftIO $ throwIO MyException else pure ()"
        , ""
        , "data MyException = MyException deriving (Show)"
        , "instance Exception MyException where"
        , "  displayException MyException = \"this is MyException\""
        ]

      (stdout, stderr) <- expectFailure $ runner.runTestsWith zeroSeed
      stderr `shouldBe` ""
      sanitizeTraceback stdout `shouldSatisfy` P.matchesSnapshot

    integration . it "renders Skeletest errors well" $ do
      runner <- getFixture @TestRunner
      runner.addTestFile "ExampleSpec.hs" $
        [ "module ExampleSpec (spec) where"
        , ""
        , "import Skeletest"
        , ""
        , "spec = do"
        , "  prop \"error\" $ do"
        , "    _ <- getFlag @MyFlag"
        , "    pure ()"
        , ""
        , "data MyFlag = MyFlag"
        , "instance IsFlag MyFlag where"
        , "  flagName = \"my-flag\""
        , "  flagHelp = \"example\""
        , "  flagSpec = RequiredFlag (const $ Right MyFlag)"
        ]

      (stdout, stderr) <- expectFailure $ runner.runTestsWith zeroSeed
      stderr `shouldBe` ""
      stdout `shouldSatisfy` P.matchesSnapshot

    integration . it "fails when configuration occurs after forAll" $ do
      runner <- getFixture @TestRunner
      runner.addTestFile "ExampleSpec.hs" $
        [ "module ExampleSpec (spec) where"
        , ""
        , "import Skeletest"
        , "import qualified Skeletest.Prop as Prop"
        , "import qualified Skeletest.Prop.Gen as Gen"
        , ""
        , "spec = prop \"discards\" $ do"
        , "  x <- forAll Gen.bool"
        , "  Prop.setDiscardLimit 10"
        , "  x `shouldBe` x"
        ]

      (stdout, stderr) <- expectFailure $ runner.runTestsWith zeroSeed
      stderr `shouldBe` ""
      stdout `shouldSatisfy` P.matchesSnapshot

    integration . it "fails when configuration occurs after IO actions" $ do
      runner <- getFixture @TestRunner
      runner.addTestFile "ExampleSpec.hs" $
        [ "module ExampleSpec (spec) where"
        , ""
        , "import Skeletest"
        , "import qualified Skeletest.Prop as Prop"
        , "import qualified Skeletest.Prop.Gen as Gen"
        , ""
        , "spec = prop \"discards\" $ do"
        , "  FixtureTmpDir _ <- getFixture"
        , "  Prop.setDiscardLimit 10"
        ]

      (stdout, stderr) <- expectFailure $ runner.runTestsWith zeroSeed
      stderr `shouldBe` ""
      stdout `shouldSatisfy` P.matchesSnapshot

    integration . it "supports MonadFail" $ do
      runner <- getFixture @TestRunner
      runner.addTestFile "ExampleSpec.hs" $
        [ "module ExampleSpec (spec) where"
        , ""
        , "import Skeletest"
        , "import qualified Skeletest.Prop as Prop"
        , "import qualified Skeletest.Prop.Gen as Gen"
        , ""
        , "spec = prop \"discards\" $ do"
        , "  Just _ <- forAll $ pure (Nothing :: Maybe Int)"
        , "  pure ()"
        ]

      (stdout, stderr) <- expectFailure $ runner.runTestsWith zeroSeed
      stderr `shouldBe` ""
      stdout `shouldSatisfy` P.matchesSnapshot

  describe "setDiscardLimit" $ do
    integration . it "sets discard limit" $ do
      runner <- getFixture @TestRunner
      runner.addTestFile "ExampleSpec.hs" $
        [ "module ExampleSpec (spec) where"
        , ""
        , "import Skeletest"
        , "import qualified Skeletest.Prop as Prop"
        , ""
        , "spec = prop \"discards\" $ do"
        , "  Prop.setDiscardLimit 10"
        , "  discard"
        ]

      (stdout, stderr) <- expectFailure runner.runTests
      stderr `shouldBe` ""
      stdout `shouldSatisfy` P.matchesSnapshot

  describe "===" $ do
    prop "checks two functions" $ do
      (read . show) P.=== id `shouldSatisfy` P.isoWith (Gen.int $ Range.exponential 0 10000000)
      (read . show) P.=== (+ 1) `shouldNotSatisfy` P.isoWith (Gen.int $ Range.exponential 0 10000000)

    integration . it "shows a helpful failure message" $ do
      runner <- getFixture @TestRunner
      runner.addTestFile "ExampleSpec.hs" $
        [ "module ExampleSpec (spec) where"
        , ""
        , "import Skeletest"
        , "import qualified Skeletest.Predicate as P"
        , "import qualified Skeletest.Prop.Gen as Gen"
        , "import qualified Skeletest.Prop.Range as Range"
        , ""
        , "spec = do"
        , "  prop \"is isomorphic\" $ do"
        , "    (read . show) P.=== (+ 1) `shouldSatisfy` P.isoWith (Gen.int $ Range.linear 0 10)"
        , "  prop \"is not isomorphic\" $ do"
        , "    (read . show) P.=== id `shouldNotSatisfy` P.isoWith (Gen.int $ Range.linear 0 10)"
        ]

      (stdout, stderr) <- expectFailure $ runner.runTestsWith zeroSeed
      stderr `shouldBe` ""
      stdout `shouldSatisfy` P.matchesSnapshot

zeroSeed :: TestArgs
zeroSeed = def{cliArgs = ["--seed=0:0"]}