packages feed

hadolint-2.1.0: test/Helpers.hs

module Helpers where

import qualified Control.Foldl as Foldl
import Control.Monad (unless, when)
import qualified Data.Sequence as Seq
import qualified Data.Text as Text
import Hadolint.Formatter.TTY (formatCheck)
import qualified Hadolint.Process
import Hadolint.Rule (CheckFailure (..), Failures, RuleCode (..))
import Language.Docker.Parser
import Language.Docker.Syntax
import Test.HUnit hiding (Label)
import Test.Hspec


assertChecks ::
  (HasCallStack, ?rulesConfig :: Hadolint.Process.RulesConfig) =>
  Text.Text ->
  (Failures -> IO a) ->
  IO a
assertChecks dockerfile makeAssertions =
  case parseText (dockerfile <> "\n") of
    Left err -> assertFailure $ show err
    Right dockerFile -> makeAssertions $ Hadolint.Process.run ?rulesConfig dockerFile

assertOnBuildChecks ::
  (HasCallStack, ?rulesConfig :: Hadolint.Process.RulesConfig) =>
  Text.Text ->
  (Failures -> IO a) ->
  IO a
assertOnBuildChecks dockerfile makeAssertions =
  case parseText (dockerfile <> "\n") of
    Left err -> assertFailure $ show err
    Right dockerFile -> checkOnBuild dockerFile
  where
    checkOnBuild dockerFile = makeAssertions $ Hadolint.Process.run ?rulesConfig (fmap wrapInOnBuild dockerFile)
    wrapInOnBuild (InstructionPos (Run args) so li) = InstructionPos (OnBuild (Run args)) so li
    wrapInOnBuild i = i

hasInvalidLines :: Failures -> Bool
hasInvalidLines =
  Foldl.fold
    (Foldl.any (\CheckFailure {line} -> line <= 0))

-- Assert a failed check exists for rule
ruleCatches ::
  (HasCallStack, ?rulesConfig :: Hadolint.Process.RulesConfig) =>
  RuleCode ->
  Text.Text ->
  Assertion
ruleCatches expectedCode dockerfile = assertChecks dockerfile f
  where
    f checks = do
      failsWithSome expectedCode checks
      assertBool "Incorrect line number for result" $ not $ hasInvalidLines checks

onBuildRuleCatches ::
  (HasCallStack, ?rulesConfig :: Hadolint.Process.RulesConfig) =>
  RuleCode ->
  Text.Text ->
  Assertion
onBuildRuleCatches ruleCode dockerfile = assertOnBuildChecks dockerfile f
  where
    f checks = do
      failsWith 1 ruleCode checks
      assertBool "Incorrect line number for result" $ not $ hasInvalidLines checks

ruleCatchesNot ::
  (HasCallStack, ?rulesConfig :: Hadolint.Process.RulesConfig) =>
  RuleCode ->
  Text.Text ->
  Assertion
ruleCatchesNot ruleCode dockerfile = assertChecks dockerfile f
  where
    f = failsWith 0 ruleCode

onBuildRuleCatchesNot ::
  (HasCallStack, ?rulesConfig :: Hadolint.Process.RulesConfig) =>
  RuleCode ->
  Text.Text ->
  Assertion
onBuildRuleCatchesNot ruleCode dockerfile = assertOnBuildChecks dockerfile f
  where
    f = failsWith 0 ruleCode

formatChecksNoColor :: Foldable f => f CheckFailure -> Text.Text
formatChecksNoColor = Foldl.fold (Foldl.premap (\c -> formatCheck True "line" c <> "\n") Foldl.mconcat)

failsWithSome :: HasCallStack => RuleCode -> Failures -> Assertion
failsWithSome expectedCode failures =
  when (null matched) $
    assertFailure $ "I was expecting to catch at least one error for " <> show (unRuleCode expectedCode)
  where
    matched = Seq.filter (\CheckFailure {code} -> expectedCode == code) failures

failsWith :: HasCallStack => Int -> RuleCode -> Failures -> Assertion
failsWith times expectedCode failures =
  when (length matched /= times) $
    assertFailure $
      "I was expecting to catch exactly " <> show times <> " error(s) for " <> show (unRuleCode expectedCode) <> ". Found: \n"
        <> (Text.unpack . formatChecksNoColor $ matched)
  where
    matched = Seq.filter (\CheckFailure {code} -> expectedCode == code) failures

failsShellcheck :: HasCallStack => Failures -> Assertion
failsShellcheck checks =
  when (null matched) $
    assertFailure
      "I was expecting to catch at least one error with shellcheck"
  where
    matched = Seq.filter (\CheckFailure {code = RuleCode rc} -> "SC" `Text.isPrefixOf` rc) checks

passesShellcheck :: HasCallStack => Failures -> Assertion
passesShellcheck checks =
  unless (null matched) $
    assertFailure $
      "I was expecting to catch no errors with shellcheck. Found: \n"
        <> (Text.unpack . formatChecksNoColor $ matched)
  where
    matched = Seq.filter (\CheckFailure {code = RuleCode rc} -> "SC" `Text.isPrefixOf` rc) checks