packages feed

tricorder-0.5.0.0: test/Unit/Tricorder/TestOutputSpec.hs

module Unit.Tricorder.TestOutputSpec (test_TestOutput) where

import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (assertFailure, testCase, (@?=))

import Tricorder.Build.Duration (Duration (..))
import Tricorder.TestOutput (parseHspecDuration, parseHspecOutput, stripGhciNoise)

import Tricorder.Build.Test qualified as Test


test_TestOutput :: TestTree
test_TestOutput =
    testGroup
        "TestOutput"
        [ testGroup
            "parseHspecOutput"
            [ testCase "returns empty list for empty output" do
                parseHspecOutput "" @?= []
            , testCase "parses a passing test" do
                let output = "  foo\n    bar baz:                                      OK\n"
                parseHspecOutput output
                    @?= [Test.Case {description = "bar baz:", outcome = Test.Passed}]
            , testCase "parses a failing test" do
                let output = "  foo\n    bar baz:                                      FAIL\n"
                parseHspecOutput output
                    @?= [Test.Case {description = "bar baz:", outcome = Test.Failed ""}]
            , testCase "captures failure details" do
                let output =
                        "    a test:                                            FAIL\n"
                            <> "      expected: 1\n"
                            <> "       but got: 2\n"
                            <> "    another test:                                       OK\n"
                parseHspecOutput output
                    @?= [ Test.Case
                            { description = "a test:"
                            , outcome = Test.Failed "expected: 1\nbut got: 2"
                            }
                        , Test.Case {description = "another test:", outcome = Test.Passed}
                        ]
            , testCase "stops collecting details when indentation returns to test level" do
                let output =
                        "    failing:                                           FAIL\n"
                            <> "      detail line\n"
                            <> "    passing:                                          OK\n"
                let cases = parseHspecOutput output
                length cases @?= 2
                case cases of
                    (c : _) -> c.outcome @?= Test.Failed "detail line"
                    [] -> assertFailure "expected at least one test case"
            , testCase "skips group header lines" do
                let output =
                        "  MyModule\n"
                            <> "    someFunction\n"
                            <> "      does the thing:                                  OK\n"
                parseHspecOutput output
                    @?= [Test.Case {description = "does the thing:", outcome = Test.Passed}]
            , testCase "parses mixed passing and failing tests" do
                let output =
                        "  Suite\n"
                            <> "    passes:                                            OK\n"
                            <> "    fails:                                             FAIL\n"
                            <> "      reason\n"
                            <> "    also passes:                                       OK\n"
                parseHspecOutput output
                    @?= [ Test.Case {description = "passes:", outcome = Test.Passed}
                        , Test.Case {description = "fails:", outcome = Test.Failed "reason"}
                        , Test.Case {description = "also passes:", outcome = Test.Passed}
                        ]
            , testCase "parses a passing test with a timing annotation" do
                let output = "  slow test:                                          OK (0.05s)\n"
                parseHspecOutput output
                    @?= [Test.Case {description = "slow test:", outcome = Test.Passed}]
            , testCase "parses a passing test with a millisecond annotation" do
                let output = "  fast property:                                      OK (12ms)\n"
                parseHspecOutput output
                    @?= [Test.Case {description = "fast property:", outcome = Test.Passed}]
            , testCase "parses a failing test with a timing annotation" do
                let output = "  slow fail:                                          FAIL (0.03s)\n"
                parseHspecOutput output
                    @?= [Test.Case {description = "slow fail:", outcome = Test.Failed ""}]
            , testCase "does not strip a non-timing parenthetical in the description" do
                let output = "  test (corner case):                                 OK\n"
                parseHspecOutput output
                    @?= [Test.Case {description = "test (corner case):", outcome = Test.Passed}]
            ]
        , testGroup
            "parseHspecDuration"
            [ testCase "returns Nothing for empty output" do
                parseHspecDuration "" @?= Nothing
            , testCase "returns Nothing when no timing line is present" do
                parseHspecDuration "2 examples, 0 failures\n" @?= Nothing
            , testCase "parses duration from passing summary line" do
                parseHspecDuration "All 177 tests passed (0.05s)\n"
                    @?= Just (Duration 50)
            , testCase "parses duration from failing summary line" do
                parseHspecDuration "1 out of 177 tests failed (0.06s)\n"
                    @?= Just (Duration 60)
            , testCase "does not match indented individual test timing lines" do
                parseHspecDuration "      entry is evicted after cleanup thread fires past TTL:  OK (0.05s)\n"
                    @?= Nothing
            , testCase "parses duration embedded in full hspec output" do
                let output =
                        "  Suite\n"
                            <> "    passes:                                          OK\n"
                            <> "    slow test:                                       OK (0.05s)\n"
                            <> "\n"
                            <> "All 2 tests passed (0.5s)\n"
                parseHspecDuration output @?= Just (Duration 500)
            ]
        , testGroup
            "stripGhciNoise"
            [ testCase "passes through empty list" do
                stripGhciNoise [] @?= []
            , testCase "passes through output with no ghci prompt" do
                let ls = ["line one", "line two", "line three"]
                stripGhciNoise ls @?= ls
            , testCase "strips cabal build preamble" do
                let ls =
                        [ "Resolving dependencies..."
                        , "Build profile: -w ghc-9.6.3 -O1"
                        , "ghci> :reload"
                        , "  test one:                                          OK"
                        , "  test two:                                          OK"
                        ]
                stripGhciNoise ls
                    @?= [ "  test one:                                          OK"
                        , "  test two:                                          OK"
                        ]
            , testCase "strips trailing ghci prompt" do
                let ls =
                        [ "ghci> :reload"
                        , "  a test:                                            OK"
                        , "ghci> "
                        ]
                stripGhciNoise ls @?= ["  a test:                                            OK"]
            , testCase "strips trailing \"Leaving GHCi.\" line" do
                let ls =
                        [ "ghci> :reload"
                        , "  a test:                                            OK"
                        , "Leaving GHCi."
                        ]
                stripGhciNoise ls @?= ["  a test:                                            OK"]
            , testCase "strips trailing \"*** Exception: ...\" lines" do
                let ls =
                        [ "ghci> :reload"
                        , "  a test:                                            OK"
                        , "*** Exception: ExitSuccess"
                        ]
                stripGhciNoise ls @?= ["  a test:                                            OK"]
            , testCase "full round-trip strips build noise, keeps test output" do
                let ls =
                        [ "Resolving dependencies..."
                        , "Build profile: -w ghc-9.6.3 -O1"
                        , "Preprocessing test suite 'spec' for tricorder-0.1.0.0..."
                        , "ghci> :reload"
                        , "  Suite"
                        , "    passes:                                          OK"
                        , "    fails:                                           FAIL"
                        , "      some detail"
                        , ""
                        , "Finished in 0.0001 seconds"
                        , "ghci> "
                        , "Leaving GHCi."
                        , "*** Exception: ExitFailure 1"
                        ]
                stripGhciNoise ls
                    @?= [ "  Suite"
                        , "    passes:                                          OK"
                        , "    fails:                                           FAIL"
                        , "      some detail"
                        , ""
                        , "Finished in 0.0001 seconds"
                        ]
            ]
        ]