packages feed

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

module Unit.Tricorder.TestOutputSpec (spec_TestOutput) where

import Test.Hspec

import Tricorder.BuildState (TestCase (..), TestCaseOutcome (..))
import Tricorder.TestOutput (parseHspecDuration, parseHspecOutput, stripGhciNoise)


spec_TestOutput :: Spec
spec_TestOutput = do
    describe "parseHspecOutput" do
        it "returns empty list for empty output" do
            parseHspecOutput "" `shouldBe` []

        it "parses a passing test" do
            let output = "  foo\n    bar baz:                                      OK\n"
            parseHspecOutput output
                `shouldBe` [TestCase {description = "bar baz:", outcome = TestCasePassed}]

        it "parses a failing test" do
            let output = "  foo\n    bar baz:                                      FAIL\n"
            parseHspecOutput output
                `shouldBe` [TestCase {description = "bar baz:", outcome = TestCaseFailed ""}]

        it "captures failure details" do
            let output =
                    "    a test:                                            FAIL\n"
                        <> "      expected: 1\n"
                        <> "       but got: 2\n"
                        <> "    another test:                                       OK\n"
            parseHspecOutput output
                `shouldBe` [ TestCase
                                { description = "a test:"
                                , outcome = TestCaseFailed "expected: 1\nbut got: 2"
                                }
                           , TestCase {description = "another test:", outcome = TestCasePassed}
                           ]

        it "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 `shouldBe` 2
            case cases of
                (c : _) -> c.outcome `shouldBe` TestCaseFailed "detail line"
                [] -> expectationFailure "expected at least one test case"

        it "skips group header lines" do
            let output =
                    "  MyModule\n"
                        <> "    someFunction\n"
                        <> "      does the thing:                                  OK\n"
            parseHspecOutput output
                `shouldBe` [TestCase {description = "does the thing:", outcome = TestCasePassed}]

        it "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
                `shouldBe` [ TestCase {description = "passes:", outcome = TestCasePassed}
                           , TestCase {description = "fails:", outcome = TestCaseFailed "reason"}
                           , TestCase {description = "also passes:", outcome = TestCasePassed}
                           ]

        it "parses a passing test with a timing annotation" do
            let output = "  slow test:                                          OK (0.05s)\n"
            parseHspecOutput output
                `shouldBe` [TestCase {description = "slow test:", outcome = TestCasePassed}]

        it "parses a passing test with a millisecond annotation" do
            let output = "  fast property:                                      OK (12ms)\n"
            parseHspecOutput output
                `shouldBe` [TestCase {description = "fast property:", outcome = TestCasePassed}]

        it "parses a failing test with a timing annotation" do
            let output = "  slow fail:                                          FAIL (0.03s)\n"
            parseHspecOutput output
                `shouldBe` [TestCase {description = "slow fail:", outcome = TestCaseFailed ""}]

        it "does not strip a non-timing parenthetical in the description" do
            let output = "  test (corner case):                                 OK\n"
            parseHspecOutput output
                `shouldBe` [TestCase {description = "test (corner case):", outcome = TestCasePassed}]

    describe "parseHspecDuration" do
        it "returns Nothing for empty output" do
            parseHspecDuration "" `shouldBe` Nothing

        it "returns Nothing when no timing line is present" do
            parseHspecDuration "2 examples, 0 failures\n" `shouldBe` Nothing

        it "parses duration from passing summary line" do
            parseHspecDuration "All 177 tests passed (0.05s)\n"
                `shouldBe` Just 50

        it "parses duration from failing summary line" do
            parseHspecDuration "1 out of 177 tests failed (0.06s)\n"
                `shouldBe` Just 60

        it "does not match indented individual test timing lines" do
            parseHspecDuration "      entry is evicted after cleanup thread fires past TTL:  OK (0.05s)\n"
                `shouldBe` Nothing

        it "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 `shouldBe` Just 500

    describe "stripGhciNoise" do
        it "passes through empty list" do
            stripGhciNoise [] `shouldBe` []

        it "passes through output with no ghci prompt" do
            let ls = ["line one", "line two", "line three"]
            stripGhciNoise ls `shouldBe` ls

        it "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
                `shouldBe` [ "  test one:                                          OK"
                           , "  test two:                                          OK"
                           ]

        it "strips trailing ghci prompt" do
            let ls =
                    [ "ghci> :reload"
                    , "  a test:                                            OK"
                    , "ghci> "
                    ]
            stripGhciNoise ls `shouldBe` ["  a test:                                            OK"]

        it "strips trailing \"Leaving GHCi.\" line" do
            let ls =
                    [ "ghci> :reload"
                    , "  a test:                                            OK"
                    , "Leaving GHCi."
                    ]
            stripGhciNoise ls `shouldBe` ["  a test:                                            OK"]

        it "strips trailing \"*** Exception: ...\" lines" do
            let ls =
                    [ "ghci> :reload"
                    , "  a test:                                            OK"
                    , "*** Exception: ExitSuccess"
                    ]
            stripGhciNoise ls `shouldBe` ["  a test:                                            OK"]

        it "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
                `shouldBe` [ "  Suite"
                           , "    passes:                                          OK"
                           , "    fails:                                           FAIL"
                           , "      some detail"
                           , ""
                           , "Finished in 0.0001 seconds"
                           ]