tasty-autocollect-0.4.2: test/Test/Tasty/AutoCollect/ConvertTestTest.hs
{- AUTOCOLLECT.TEST -}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
module Test.Tasty.AutoCollect.ConvertTestTest (
{- AUTOCOLLECT.TEST.export -}
) where
import Control.Monad (forM_)
import Data.Maybe (maybeToList)
import Data.Text (Text)
import Data.Text qualified as Text
import Test.Predicates
import Test.Predicates.HUnit
import Test.Tasty.HUnit
import Text.Printf (printf)
import TestUtils.Golden
import TestUtils.Integration
import TestUtils.Predicates
{----- General plugin tests -----}
test =
testCase "plugin works without tasty installed" $
assertSuccess_ $
runTestWith
( \proj ->
modifyFile "Test.hs" (filter (not . isTastyImport)) $
proj{dependencies = filter (/= "tasty") (dependencies proj)}
)
[ "test = testCase \"test\" $ 1 @?= 1"
]
where
isTastyImport line =
case Text.unpack <$> Text.stripPrefix "import Test.Tasty" line of
-- not an import / import from non-tasty library
Nothing -> False
-- import from `Test.Tasty`
Just "" -> True
-- import from `Test.Tasty (...)` or `Test.Tasty hiding (...)`
Just " " -> True
-- import from `Test.Tasty.Foo` or `Test.TastyFoo`, which is ok
_ -> False
test =
testCase "plugin works without Prelude" $
assertSuccess_ $
runTestWith
(modifyFile "Test.hs" (const testFile))
[]
where
testFile =
[ "{- AUTOCOLLECT.TEST -}"
, "module Test where"
, "import Prelude ()"
, "import Test.Tasty.HUnit"
, "test = testCase \"a test\" (1 @?= 1)"
]
test_batch =
[ testCase ("plugin works when " ++ mkLabel ext) $
assertSuccess_ $
runTestWith
(\proj -> proj{extraGhcArgs = maybeToList ext <> extraGhcArgs proj})
[ "test = testCase \"1 = 1\" $ 1 @?= 1"
]
| ext <-
[ Just "-XOverloadedStrings"
, Just "-XOverloadedLists"
, Nothing
]
]
where
mkLabel = \case
Nothing -> "no extensions are enabled"
Just ext -> "enabling " <> Text.unpack ext
{----- Overall test file -----}
test = testCase "tests fail when omitting export comment" $ do
(_, stderr) <-
assertAnyFailure . runTestWith (modifyFile "Test.hs" (map removeExports)) $
[ "test = testCase \"a test\" $ pure ()"
]
getTestLines stderr @?~ containsStripped (startsWith messagePreGHC94 `orP` startsWith messagePostGHC94)
where
removeExports s
| "module " `Text.isPrefixOf` s = "module Test () where"
| otherwise = s
messagePreGHC94 = "Module ‘Test’ does not export"
messagePostGHC94 = "NB: the module ‘Test’ does not export"
test = testCase "test file can omit an explicit export list" $ do
(stdout, _) <-
assertSuccess . runTestWith (modifyFile "Test.hs" (map removeExports)) $
[ "test = testCase \"a test\" $ pure ()"
]
getTestLines stdout @?~ containsStripped (eq "a test: OK")
where
removeExports s
| "module " `Text.isPrefixOf` s = "module Test where"
| otherwise = s
test =
testCase "test file can contain multi-function signature" $
assertSuccess_ . runTest $
[ "test = testCase \"test\" $ timesTen 1 @?= timesFive 2"
, ""
, "timesTen, timesFive :: Int -> Int"
, "timesTen = (* 10)"
, "timesFive = (* 5)"
]
{----- Generated tests -----}
test = testCase "generated test keeps where clause" $ do
(stdout, _) <-
assertSuccess . runTest $
[ "test = testCase \"a test\" $ constant @?= 42"
, " where"
, " constant = 42"
]
getTestLines stdout @?~ containsStripped (eq "a test: OK")
test =
testCase "test may specify type" $
assertSuccess_ . runTest $
[ "test :: TestTree"
, "test = testCase \"a test\" $ pure ()"
]
test = testGolden "test fails when given arguments" "test_args.golden" $ do
(_, stderr) <-
assertAnyFailure . runTest $
[ "test \"some name\" = testCase \"test\" $ pure ()"
]
pure stderr
test = testGolden "test fails when specifying wrong type" "test_type.golden" $ do
(_, stderr) <-
assertAnyFailure . runTest $
[ "test :: Int"
, "test = testCase \"test\" $ pure ()"
]
pure stderr
test = testCase "tests can omit type signatures" $ do
(stdout, _) <-
assertSuccess . runTest $
[ "test = testCase \"test 1\" $ pure ()"
, ""
, "test = testCase \"test 2\" $ pure ()"
]
getTestLines stdout @?~ containsStripped (eq "test 1: OK")
getTestLines stdout @?~ containsStripped (eq "test 2: OK")
test =
testCase "tests may omit type after specifying a type prior" $
assertSuccess_ . runQCTest $
[ "test :: TestTree"
, "test = testCase \"test 1\" $ pure ()"
, ""
, "test = testCase \"test 2\" $ pure ()"
]
{----- test_batch -----}
test = testCase "test_batch generates multiple tests" $ do
(stdout, _) <-
assertSuccess . runTest $
[ "test_batch ="
, " [ testCase (\"test #\" ++ show x) $ pure ()"
, " | x <- [1 .. 5]"
, " ]"
]
forM_ [1 .. 5 :: Int] $ \x ->
getTestLines stdout @?~ containsStripped (eq . Text.pack $ printf "test #%d: OK" x)
test = testCase "test_batch includes where clause" $ do
(stdout, _) <-
assertSuccess . runTest $
[ "test_batch ="
, " [ testCase (label x) $ pure ()"
, " | x <- [1 .. 5]"
, " ]"
, " where"
, " label x = \"test #\" ++ show x"
]
forM_ [1 .. 5 :: Int] $ \x ->
getTestLines stdout @?~ containsStripped (eq . Text.pack $ printf "test #%d: OK" x)
test =
testCase "test_batch may specify type" $
assertSuccess_ . runTest $
[ "test_batch :: [TestTree]"
, "test_batch = []"
]
test = testGolden "test_batch fails when given arguments" "test_batch_args.golden" $ do
(_, stderr) <-
assertAnyFailure . runTest $
[ "test_batch \"some name\" = []"
]
pure stderr
test = testGolden "test_batch fails when specifying wrong type" "test_batch_type.golden" $ do
(_, stderr) <-
assertAnyFailure . runTest $
[ "test_batch :: TestTree"
, "test_batch = []"
]
pure stderr
{----- test_prop -----}
test =
testCase "property tests may be written with test_prop" $ do
(stdout, _) <-
assertSuccess . runQCTest $
[ "test_prop :: Positive Int -> [Int] -> Bool"
, "test_prop \"take N returns at most N elements\" (Positive n) xs = length (take n xs) <= n"
]
getTestLines stdout @?~ containsStripped (eq "take N returns at most N elements: OK")
stdout @?~ hasSubstr "passed 100 tests"
test =
testCase "test_prop where clause has args in scope" $
assertSuccess_ . runQCTest $
[ "test_prop :: Positive Int -> Bool"
, "test_prop \"test\" (Positive n) = n > zero"
, " where"
, " zero = n - n"
]
test =
testCase "test_prop may omit type" $
assertSuccess_ . runQCTest $
[ "test_prop \"test\" x = (x :: Int) === x"
]
test =
testCase "test_prop may omit type after specifying a different type prior" $
assertSuccess_ . runQCTest $
[ "test_prop :: Property"
, "test_prop \"test 1\" = 1 === 1"
, ""
, "test_prop \"test 2\" x = (x :: Int) === x"
]
test =
testCase "test_prop uses any 'testProperty' function in scope" $ do
(stdout, _) <-
assertSuccess . runTest $
[ "test_prop :: Int -> Bool"
, "test_prop \"my property test\" x = x == x"
, ""
, "testProperty :: String -> (Int -> Bool) -> TestTree"
, "testProperty name f = testCase name (f 1 @?= True)"
]
getTestLines stdout @?~ containsStripped (eq "my property test: OK")
test =
testGolden "test_prop fails when no arguments provided" "test_prop_no_args.golden" $ do
(_, stderr) <- assertAnyFailure $ runTest ["test_prop = 1 === 1"]
pure stderr
test =
testGolden "test_prop fails when non-string argument provided" "test_prop_bad_arg.golden" $ do
(_, stderr) <- assertAnyFailure $ runTest ["test_prop 11 = True"]
pure stderr
test =
testCase "test_prop works when -XOverloadedStrings is enabled" $
assertSuccess_ $
runTestWith
( \proj -> addQuickCheck $ proj{extraGhcArgs = "-XOverloadedStrings" : extraGhcArgs proj}
)
[ "import Test.Tasty.QuickCheck"
, "test_prop \"a test\" = True"
]
runQCTest :: FileContents -> IO (ExitCode, Text, Text)
runQCTest = runTestWith addQuickCheck . ("import Test.Tasty.QuickCheck" :)
addQuickCheck :: GHCProject -> GHCProject
addQuickCheck proj = proj{dependencies = "tasty-quickcheck" : dependencies proj}
{----- tasty-expected-failure integration -----}
test =
testGoldenVersioned "expectFail succeeds when test fails" "test_expectFail_output.golden" $ do
(stdout, _) <-
assertSuccess . runTest $
[ "test_expectFail = testCase \"failing test\" $ 1 @?= 2"
]
pure (normalizeTestOutput stdout)
test =
testGoldenVersioned "expectFailBecause succeeds when test fails" "test_expectFailBecause_output.golden" $ do
(stdout, _) <-
assertSuccess . runTest $
[ "test_expectFailBecause \"some reason\" = testCase \"failing test\" $ 1 @?= 2"
]
pure (normalizeTestOutput stdout)
test =
testGolden "ignoreTest succeeds when test fails" "test_ignoreTest_output.golden" $ do
(stdout, _) <-
assertSuccess . runTest $
[ "test_ignoreTest = testCase \"failing test\" $ 1 @?= 2"
]
pure (normalizeTestOutput stdout)
test =
testGoldenVersioned "ignoreTestBecause succeeds when test fails" "test_ignoreTestBecause_output.golden" $ do
(stdout, _) <-
assertSuccess . runTest $
[ "test_ignoreTestBecause \"some reason\" = testCase \"failing test\" $ 1 @?= 2"
]
pure (normalizeTestOutput stdout)
test =
testGoldenVersioned "expected-failure modifiers work on test_batch" "test_batch_expectFailBecause_output.golden" $ do
(stdout, _) <-
assertAnyFailure . runTest $
[ "test_batch_expectFailBecause \"some reason\" ="
, " [ testCase (\"failing test #\" ++ show x) $ x @?= 1"
, " | x <- [1 .. 3 :: Int]"
, " ]"
]
pure (normalizeTestOutput stdout)
test =
testCase "expected-failure modifiers work on test_prop" $ do
(stdout, _) <-
assertSuccess . runQCTest $
[ "test_prop_expectFailBecause :: [Int] -> Bool"
, "test_prop_expectFailBecause \"some reason\" \"my property\" xs = length xs < 0"
]
getTestLines stdout @?~ containsStripped (eq "my property: FAIL (expected: some reason)")