{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}
module Test.Hspec.Core.QuickCheck.UtilSpec (spec) where

import           Prelude ()
import           Helper

import qualified Test.QuickCheck.Property as QCP

import           Test.Hspec.Core.QuickCheck.Util

deriving instance Eq QuickCheckResult
deriving instance Eq Status
deriving instance Eq QuickCheckFailure

spec :: Spec
spec = do
  describe "formatNumbers" $ do
    it "includes number of tests" $ do
      formatNumbers 1 0 `shouldBe` "(after 1 test)"

    it "pluralizes number of tests" $ do
      formatNumbers 3 0 `shouldBe` "(after 3 tests)"

    it "includes number of shrinks" $ do
      formatNumbers 3 1 `shouldBe` "(after 3 tests and 1 shrink)"

    it "pluralizes number of shrinks" $ do
      formatNumbers 3 3 `shouldBe` "(after 3 tests and 3 shrinks)"

  describe "stripSuffix" $ do
    it "drops the given suffix from a list" $ do
      stripSuffix "bar" "foobar" `shouldBe` Just "foo"

  describe "splitBy" $ do
    it "splits a string by a given infix" $ do
      splitBy "bar" "foo bar baz" `shouldBe` Just ("foo ", " baz")

  describe "parseQuickCheckResult" $ do
    let
      args = stdArgs {chatty = False, replay = Just (mkGen 0, 0)}
      qc = quickCheckWithResult args

    context "with Success" $ do
      let p :: Int -> Bool
          p n = n == n

      it "parses result" $ do
        parseQuickCheckResult <$> qc p `shouldReturn`
          QuickCheckResult 100 "+++ OK, passed 100 tests." QuickCheckSuccess

      it "includes labels" $ do
        parseQuickCheckResult <$> qc (label "unit" p) `shouldReturn`
          QuickCheckResult 100 "+++ OK, passed 100 tests (100% unit)." QuickCheckSuccess

    context "with GaveUp" $ do
      let p :: Int -> Property
          p n = (n == 1234) ==> True

          qc = quickCheckWithResult args {maxSuccess = 2, maxDiscardRatio = 1}
          result = QuickCheckResult 0 "" (QuickCheckOtherFailure "Gave up after 0 tests; 2 discarded!")

      it "parses result" $ do
        parseQuickCheckResult <$> qc p `shouldReturn` result

      it "includes verbose output" $ do
        let
          info = intercalate "\n" [
              "Skipped (precondition false):"
            , "0"
            , ""
            , "Skipped (precondition false):"
            , "0"
            ]
        parseQuickCheckResult <$> qc (verbose p) `shouldReturn` result {quickCheckResultInfo = info}

    context "with NoExpectedFailure" $ do
      let
        p :: Int -> Property
        p _ = expectFailure True

      it "parses result" $ do
        parseQuickCheckResult <$> qc p `shouldReturn`
          QuickCheckResult 100 "" (QuickCheckOtherFailure "Passed 100 tests (expected failure).")

      it "includes verbose output" $ do
        let
          info = intercalate "\n" [
              "Passed:"
            , "0"
            , ""
            , "Passed:"
            , "23"
            ]
        parseQuickCheckResult <$> quickCheckWithResult args {maxSuccess = 2} (verbose p) `shouldReturn`
          QuickCheckResult 2 info (QuickCheckOtherFailure "Passed 2 tests (expected failure).")

    context "with cover" $ do
      context "without checkCoverage" $ do
        let
          p :: Int -> Property
          p n = cover 10 (n == 5) "is 5" True

        it "parses result" $ do
          parseQuickCheckResult <$> qc p `shouldReturn`
            QuickCheckResult 100 "+++ OK, passed 100 tests (1% is 5).\n\nOnly 1% is 5, but expected 10%" QuickCheckSuccess

        it "includes verbose output" $ do
          let
            info = intercalate "\n" [
                "Passed:"
              , "0"
              , ""
              , "Passed:"
              , "23"
              , ""
#if MIN_VERSION_QuickCheck(2,15,0)
              , "+++ OK, passed 2 tests (0% is 5)."
#else
              , "+++ OK, passed 2 tests."
#endif
              , ""
              , "Only 0% is 5, but expected 10%"
              ]
          parseQuickCheckResult <$> quickCheckWithResult args {maxSuccess = 2} (verbose p) `shouldReturn`
            QuickCheckResult 2 info QuickCheckSuccess

      context "with checkCoverage" $ do
        let
          p :: Int -> Property
          p n = checkCoverage $ cover 10 (n == 23) "is 23" True

          failure :: QuickCheckFailure
          failure = QCFailure {
              quickCheckFailureNumShrinks = 0
            , quickCheckFailureException = Nothing
            , quickCheckFailureReason = "Insufficient coverage"
            , quickCheckFailureCounterexample = [
                " 0.9% is 23"
              , ""
              , "Only 0.9% is 23, but expected 10.0%"
              ]
            }

        it "parses result" $ do
          parseQuickCheckResult <$> qc p `shouldReturn`
            QuickCheckResult 800 "" (QuickCheckFailure failure)

        it "includes verbose output" $ do
-- This off-by-one error was fixed in QuickCheck 2.15
#if MIN_VERSION_QuickCheck(2,15,0)
          let info = intercalate "\n\n" (replicate 800 "Passed:")
#else
          let info = intercalate "\n\n" (replicate 799 "Passed:")
#endif
          parseQuickCheckResult <$> qc (verbose . p) `shouldReturn`
            QuickCheckResult 800 info (QuickCheckFailure failure)

    context "with Failure" $ do
      context "with single-line failure reason" $ do
        let
          p :: Int -> Bool
          p = (< 1)

          err = "Falsified"
          result = QuickCheckResult 4 "" (QuickCheckFailure $ QCFailure 2 Nothing err ["1"])

        it "parses result" $ do
          parseQuickCheckResult <$> qc p `shouldReturn` result

        it "includes verbose output" $ do
          let info = intercalate "\n" [
                  "Passed:"
                , "0"
                , ""
                , "Passed:"
                , "0"
                , ""
                , "Passed:"
                , "-2"
                , ""
                , "Failed:"
                , "3"
                , ""
                , "Passed:"
                , "0"
                , ""
                , "Failed:"
                , "2"
                , ""
                , "Passed:"
                , "0"
                , ""
                , "Failed:"
                , "1"
                , ""
                , "Passed:"
                , "0"
                ]

          parseQuickCheckResult <$> qc (verbose p) `shouldReturn` result {quickCheckResultInfo = info}

      context "with multi-line failure reason" $ do
        let
          p :: Int -> QCP.Result
          p n = if n /= 2 then QCP.succeeded else QCP.failed {QCP.reason = err}

          err = "foo\nbar"
          result = QuickCheckResult 5 "" (QuickCheckFailure $ QCFailure 0 Nothing err ["2"])

        it "parses result" $ do
          parseQuickCheckResult <$> qc p `shouldReturn` result

        it "includes verbose output" $ do
          let info = intercalate "\n" [
                  "Passed:"
                , "0"
                , ""
                , "Passed:"
                , "0"
                , ""
                , "Passed:"
                , "-2"
                , ""
                , "Passed:"
                , "3"
                , ""
                , "Failed:"
                , "2"
                , ""
                , "Passed:"
                , "0"
                , ""
                , "Passed:"
                , "1"
                ]
          parseQuickCheckResult <$> qc (verbose p) `shouldReturn` result {quickCheckResultInfo = info}

      context "with HUnit assertion" $ do
        let p :: Int -> Int -> Expectation
            p m n = do
              m `shouldBe` n

        it "includes counterexample" $ do
          result <- qc p
          let QuickCheckResult _ _ (QuickCheckFailure r) = parseQuickCheckResult result
          quickCheckFailureCounterexample r `shouldBe` ["0", "1"]
