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