packages feed

hspec-junit-formatter-1.2.0.0: library/Test/Hspec/JUnit/Format.hs

module Test.Hspec.JUnit.Format
  ( junit
  ) where

import Prelude

import Conduit (runConduitRes, sinkFile, yield, (.|))
import Control.Applicative ((<|>))
import Data.Functor ((<&>))
import Data.Map.Strict qualified as Map
import Data.Maybe (fromMaybe)
import Data.Text (Text, pack, unpack)
import Data.Text qualified as T
import Data.Time (getCurrentTime)
import System.Directory (createDirectoryIfMissing)
import System.FilePath (splitFileName)
import Test.Hspec.Api.Format.V1
import Test.Hspec.JUnit.Config as Config
import Test.Hspec.JUnit.Render (renderJUnit)
import Test.Hspec.JUnit.Schema qualified as Schema
import Text.XML.Stream.Render (def, renderBytes)

junit :: JUnitConfig -> FormatConfig -> IO Format
junit junitConfig _config = pure $ \case
  Started -> pure ()
  GroupStarted _ -> pure ()
  GroupDone _ -> pure ()
  Progress _ _ -> pure ()
  ItemStarted _ -> pure ()
  ItemDone _ _ -> pure ()
  Done paths -> do
    time <- getCurrentTime

    let (directory, _) = splitFileName file
    createDirectoryIfMissing True directory

    let
      groups = groupItems paths
      output =
        Schema.Suites
          { name = suiteName
          , suites =
              groups <&> \(group, items) -> do
                let suite xs =
                      Schema.Suite
                        { name = group
                        , timestamp = time
                        , cases = xs
                        }
                suite $ uncurry (itemToTestCase applyPrefix group) <$> items
          }

    runConduitRes
      $ yield output
        .| renderJUnit dropConsoleFormatting
        .| renderBytes def
        .| sinkFile file
 where
  file = getJUnitConfigOutputFile junitConfig
  suiteName = getJUnitConfigSuiteName junitConfig
  applyPrefix = getJUnitPrefixSourcePath junitConfig
  dropConsoleFormatting = getJUnitConfigDropConsoleFormatting junitConfig

groupItems :: [(Path, Item)] -> [(Text, [(Text, Item)])]
groupItems = Map.toList . Map.fromListWith (<>) . fmap group
 where
  group ((path, name), item) =
    (T.intercalate "/" $ pack <$> path, [(pack name, item)])

itemToTestCase
  :: (FilePath -> FilePath) -> Text -> Text -> Item -> Schema.TestCase
itemToTestCase applyPrefix group name item =
  Schema.TestCase
    { location =
        toSchemaLocation applyPrefix
          <$> (itemResultLocation item <|> itemLocation item)
    , className = group
    , name = name
    , duration = unSeconds $ itemDuration item
    , result = case itemResult item of
        Success -> Nothing
        Pending mLocation mMessage ->
          Just
            $ Schema.Skipped
            $ prefixLocation mLocation
            $ prefixInfo
            $ maybe "" pack mMessage
        Failure mLocation reason ->
          Just
            $ Schema.Failure "error"
            $ prefixLocation mLocation
            $ prefixInfo
            $ reasonToText reason
    }
 where
  prefixLocation mLocation str = case mLocation of
    Nothing -> str
    Just l ->
      mconcat
        [ pack $ applyPrefix $ locationFile l
        , ":"
        , pack $ show $ locationLine l
        , ":"
        , pack $ show $ locationColumn l
        , "\n"
        ]
        <> str
  prefixInfo str
    | T.null $ T.strip $ pack $ itemInfo item = str
    | otherwise = pack (itemInfo item) <> "\n\n" <> str

itemResultLocation :: Item -> Maybe Location
itemResultLocation item = case itemResult item of
  Success -> Nothing
  Pending mLocation _ -> mLocation
  Failure mLocation _ -> mLocation

toSchemaLocation :: (FilePath -> FilePath) -> Location -> Schema.Location
toSchemaLocation applyPrefix l =
  Schema.Location
    { Schema.file = applyPrefix $ locationFile l
    , Schema.line = fromIntegral $ max 0 $ locationLine l
    }

unSeconds :: Seconds -> Double
unSeconds (Seconds x) = x

reasonToText :: FailureReason -> Text
reasonToText = \case
  Error _ err -> pack $ show err
  NoReason -> "no reason"
  Reason err -> pack err
  ExpectedButGot preface expected actual ->
    T.unlines
      $ pack
        <$> fromMaybe "" preface
          : ( foundLines "expected" expected
                <> foundLines " but got" actual
            )

foundLines :: Show a => Text -> a -> [String]
foundLines msg found = case lines' of
  [] -> []
  first : rest ->
    unpack (msg <> ": " <> first) : (unpack . (T.replicate 9 " " <>) <$> rest)
 where
  lines' = T.lines . pack $ show found