packages feed

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)")