packages feed

hspec-junit-formatter 1.1.2.1 → 1.2.0.0

raw patch · 13 files changed

+268/−442 lines, 13 filesdep −markdown-unlitdep ~arraydep ~basedep ~conduit

Dependencies removed: markdown-unlit

Dependency ranges changed: array, base, conduit, containers, directory, exceptions, filepath, regex-tdfa, text, time, xml-conduit, xml-types

Files

README.lhs view
@@ -13,7 +13,6 @@  module Main (main) where import Prelude-import Text.Markdown.Unlit ()  -- Used in a later example import qualified Test.Hspec.JUnit.Formatter.Env as FormatterEnv@@ -119,6 +118,20 @@ We maintain specific golden XML files for GHC 8.x vs 9.x, so you will need to re-run the test suite with at least one of each series to regenerate all the necessary files.++## Release++To trigger a release, merge a commit to `main` that follows [Conventional+Commits][]. In short,++- `fix:` to trigger a patch release+- `feat:` to trigger a minor release+- `<type>!:` or use a `BREAKING CHANGE:` footer to trigger a major release++We don't enforce conventional commits generally (though you are free do so),+it's only required if you want to trigger release.++[conventional commits]: https://www.conventionalcommits.org/en/v1.0.0/#summary  --- 
README.md view
@@ -13,7 +13,6 @@  module Main (main) where import Prelude-import Text.Markdown.Unlit ()  -- Used in a later example import qualified Test.Hspec.JUnit.Formatter.Env as FormatterEnv@@ -119,6 +118,20 @@ We maintain specific golden XML files for GHC 8.x vs 9.x, so you will need to re-run the test suite with at least one of each series to regenerate all the necessary files.++## Release++To trigger a release, merge a commit to `main` that follows [Conventional+Commits][]. In short,++- `fix:` to trigger a patch release+- `feat:` to trigger a minor release+- `<type>!:` or use a `BREAKING CHANGE:` footer to trigger a major release++We don't enforce conventional commits generally (though you are free do so),+it's only required if you want to trigger release.++[conventional commits]: https://www.conventionalcommits.org/en/v1.0.0/#summary  --- 
hspec-junit-formatter.cabal view
@@ -1,6 +1,6 @@ cabal-version:      1.18 name:               hspec-junit-formatter-version:            1.1.2.1+version:            1.2.0.0 license:            MIT license-file:       LICENSE copyright:          2021 Renaissance Learning Inc@@ -30,7 +30,6 @@  library     exposed-modules:-        Test.Hspec.JUnit         Test.Hspec.JUnit.Config         Test.Hspec.JUnit.Config.Env         Test.Hspec.JUnit.Format@@ -41,87 +40,75 @@      hs-source-dirs:     library     other-modules:      Paths_hspec_junit_formatter-    default-language:   Haskell2010+    default-language:   GHC2021     default-extensions:-        BangPatterns DeriveAnyClass DeriveFoldable DeriveFunctor-        DeriveGeneric DeriveLift DeriveTraversable DerivingStrategies-        FlexibleContexts FlexibleInstances GADTs GeneralizedNewtypeDeriving-        LambdaCase MultiParamTypeClasses NoImplicitPrelude-        NoMonomorphismRestriction OverloadedStrings RankNTypes-        RecordWildCards ScopedTypeVariables StandaloneDeriving-        TypeApplications TypeFamilies+        DataKinds DeriveAnyClass DerivingStrategies DerivingVia+        DuplicateRecordFields GADTs LambdaCase NoFieldSelectors+        NoImplicitPrelude NoMonomorphismRestriction NoPostfixOperators+        OverloadedRecordDot OverloadedStrings QuasiQuotes TypeFamilies      ghc-options:-        -fignore-optim-changes -Weverything -Wno-all-missed-specialisations+        -Weverything -Wno-all-missed-specialisations         -Wno-missed-specialisations -Wno-missing-exported-signatures         -Wno-missing-import-lists -Wno-missing-local-signatures-        -Wno-monomorphism-restriction -Wno-safe -Wno-unsafe+        -Wno-missing-safe-haskell-mode -Wno-monomorphism-restriction+        -Wno-prepositive-qualified-module -Wno-safe -Wno-unsafe      build-depends:-        array >=0.5.2.0,-        base >=4.11.1.0 && <5,-        conduit >=1.3.1,-        containers >=0.5.11.0,-        directory >=1.3.1.5,-        exceptions >=0.10.0,-        filepath >=1.4.2,+        array >=0.5.4.0,+        base >=4.16.4.0 && <5,+        conduit >=1.3.5,+        containers >=0.6.5.1,+        directory >=1.3.6.2,+        exceptions >=0.10.4,+        filepath >=1.4.2.2,         hspec-api >=2.10.0,         hspec-core >=2.10.0,         iso8601-time >=0.1.5,         regex-base >=0.94.0.2,-        regex-tdfa >=1.3.2.2,-        text >=1.2.3.1,-        time >=1.8.0.2,-        xml-conduit >=1.8.0.1,-        xml-types >=0.3.6+        regex-tdfa >=1.3.2.1,+        text >=1.2.5.0,+        time >=1.11.1.1,+        xml-conduit >=1.9.1.2,+        xml-types >=0.3.8 +    if impl(ghc >=9.8)+        ghc-options: -Wno-missing-role-annotations+     if impl(ghc >=9.2)         ghc-options: -Wno-missing-kind-signatures -    if impl(ghc >=8.10)-        ghc-options:-            -Wno-missing-safe-haskell-mode -Wno-prepositive-qualified-module--    if impl(ghc >=8.8)-        ghc-options: -fwrite-ide-info- test-suite readme     type:               exitcode-stdio-1.0     main-is:            README.lhs+    build-tool-depends: markdown-unlit:markdown-unlit     other-modules:      Paths_hspec_junit_formatter-    default-language:   Haskell2010+    default-language:   GHC2021     default-extensions:-        BangPatterns DeriveAnyClass DeriveFoldable DeriveFunctor-        DeriveGeneric DeriveLift DeriveTraversable DerivingStrategies-        FlexibleContexts FlexibleInstances GADTs GeneralizedNewtypeDeriving-        LambdaCase MultiParamTypeClasses NoImplicitPrelude-        NoMonomorphismRestriction OverloadedStrings RankNTypes-        RecordWildCards ScopedTypeVariables StandaloneDeriving-        TypeApplications TypeFamilies+        DataKinds DeriveAnyClass DerivingStrategies DerivingVia+        DuplicateRecordFields GADTs LambdaCase NoFieldSelectors+        NoImplicitPrelude NoMonomorphismRestriction NoPostfixOperators+        OverloadedRecordDot OverloadedStrings QuasiQuotes TypeFamilies      ghc-options:-        -fignore-optim-changes -Weverything -Wno-all-missed-specialisations+        -Weverything -Wno-all-missed-specialisations         -Wno-missed-specialisations -Wno-missing-exported-signatures         -Wno-missing-import-lists -Wno-missing-local-signatures-        -Wno-monomorphism-restriction -Wno-safe -Wno-unsafe -pgmL+        -Wno-missing-safe-haskell-mode -Wno-monomorphism-restriction+        -Wno-prepositive-qualified-module -Wno-safe -Wno-unsafe -pgmL         markdown-unlit      build-depends:-        base >=4.11.1.0 && <5,+        base >=4.16.4.0 && <5,         hspec >=2.10.0,-        hspec-junit-formatter,-        markdown-unlit >=0.5.0+        hspec-junit-formatter +    if impl(ghc >=9.8)+        ghc-options: -Wno-missing-role-annotations+     if impl(ghc >=9.2)         ghc-options: -Wno-missing-kind-signatures -    if impl(ghc >=8.10)-        ghc-options:-            -Wno-missing-safe-haskell-mode -Wno-prepositive-qualified-module--    if impl(ghc >=8.8)-        ghc-options: -fwrite-ide-info- test-suite spec     type:               exitcode-stdio-1.0     main-is:            Spec.hs@@ -133,40 +120,34 @@         Test.Hspec.JUnit.FormatterSpec         Paths_hspec_junit_formatter -    default-language:   Haskell2010+    default-language:   GHC2021     default-extensions:-        BangPatterns DeriveAnyClass DeriveFoldable DeriveFunctor-        DeriveGeneric DeriveLift DeriveTraversable DerivingStrategies-        FlexibleContexts FlexibleInstances GADTs GeneralizedNewtypeDeriving-        LambdaCase MultiParamTypeClasses NoImplicitPrelude-        NoMonomorphismRestriction OverloadedStrings RankNTypes-        RecordWildCards ScopedTypeVariables StandaloneDeriving-        TypeApplications TypeFamilies+        DataKinds DeriveAnyClass DerivingStrategies DerivingVia+        DuplicateRecordFields GADTs LambdaCase NoFieldSelectors+        NoImplicitPrelude NoMonomorphismRestriction NoPostfixOperators+        OverloadedRecordDot OverloadedStrings QuasiQuotes TypeFamilies      ghc-options:-        -fignore-optim-changes -Weverything -Wno-all-missed-specialisations+        -Weverything -Wno-all-missed-specialisations         -Wno-missed-specialisations -Wno-missing-exported-signatures         -Wno-missing-import-lists -Wno-missing-local-signatures-        -Wno-monomorphism-restriction -Wno-safe -Wno-unsafe -threaded-        -rtsopts -O0 -with-rtsopts=-N+        -Wno-missing-safe-haskell-mode -Wno-monomorphism-restriction+        -Wno-prepositive-qualified-module -Wno-safe -Wno-unsafe -threaded+        -rtsopts -with-rtsopts=-N      build-depends:-        base >=4.11.1.0 && <5,-        containers >=0.5.11.0,-        filepath >=1.4.2,+        base >=4.16.4.0 && <5,+        containers >=0.6.5.1,+        filepath >=1.4.2.2,         hspec >=2.10.0,         hspec-golden >=0.2.1.0,         hspec-junit-formatter,         temporary >=1.3,-        text >=1.2.3.1,-        xml-conduit >=1.8.0.1+        text >=1.2.5.0,+        xml-conduit >=1.9.1.2 +    if impl(ghc >=9.8)+        ghc-options: -Wno-missing-role-annotations+     if impl(ghc >=9.2)         ghc-options: -Wno-missing-kind-signatures--    if impl(ghc >=8.10)-        ghc-options:-            -Wno-missing-safe-haskell-mode -Wno-prepositive-qualified-module--    if impl(ghc >=8.8)-        ghc-options: -fwrite-ide-info
− library/Test/Hspec/JUnit.hs
@@ -1,226 +0,0 @@-{-# LANGUAGE CPP #-}--module Test.Hspec.JUnit-  {-# DEPRECATED "Use Test.Hspec.JUnit.Formatter" #-}-  ( -- * Runners-    hspecJUnit-  , hspecJUnitWith--    -- * Directly modifying 'Config'-  , configWithJUnit-  , configWithJUnitAvailable--    -- * Actual format function-  , junitFormat--    -- * Configuration-  , module Test.Hspec.JUnit.Config-  ) where--import Prelude--import Control.Applicative ((<|>))-import Data.Conduit (runConduitRes, (.|))-import Data.Conduit.Combinators (sinkFile)-import Data.Conduit.List (sourceList)-import Data.Functor ((<&>))-import qualified Data.Map.Strict as Map-import Data.Maybe (fromMaybe)-import Data.Text (Text, pack, unpack)-import qualified Data.Text as T-import Data.Time (getCurrentTime)-import System.Directory (createDirectoryIfMissing)-import System.FilePath (splitFileName)-import Test.Hspec.Core.Format-  ( Event (..)-  , FailureReason (..)-  , Format-  , FormatConfig-  , Item (..)-  , Location (..)-  , Path-  , Result (..)-  , Seconds (..)-  )-import Test.Hspec.Core.Runner (Config (..), defaultConfig, hspecWith)-import Test.Hspec.Core.Spec (Spec)-import Test.Hspec.JUnit.Config-import Test.Hspec.JUnit.Config.Env-import Test.Hspec.JUnit.Render (renderJUnit)-import qualified Test.Hspec.JUnit.Schema as Schema-import Text.XML.Stream.Render (def, renderBytes)---- | Like 'hspec' but adds JUNit functionality------ To actually /use/ the JUnit format, you must set @JUNIT_ENABLED=1@ in the--- environment; by default, this function just behaves like 'hspec'.------ Running tests with @--test-arguments="-f junit"@ also works.------ All configuration of the JUnit report occurs through environment variables.------ See "Test.Hspec.JUnit.Config" and "Test.Hspec.JUnit.Config.Env".-hspecJUnit :: Spec -> IO ()-hspecJUnit = hspecJUnitWith defaultConfig---- | 'hspecJUnit' but built on a non-default 'Config'-hspecJUnitWith :: Config -> Spec -> IO ()-hspecJUnitWith config spec = do-  junitEnabled <- envJUnitEnabled-  junitConfig <- envJUnitConfig--  let-    modify = if junitEnabled then configWithJUnit junitConfig else id-    base = configWithJUnitAvailable junitConfig config--  hspecWith (modify base) spec---- | Modify an Hspec 'Config' to use 'junitFormat'-configWithJUnit :: JUnitConfig -> Config -> Config-configWithJUnit junitConfig config =-  config {configFormat = Just $ junitFormat junitConfig}---- | Modify an Hspec 'Config' to have the 'junitFormat' /available/------ Adds @junit@ to the list of available options for @-f, --format@.-configWithJUnitAvailable :: JUnitConfig -> Config -> Config-configWithJUnitAvailable junitConfig config =-  config-    { configAvailableFormatters =-        configAvailableFormatters config <> [("junit", junitFormat junitConfig)]-    }---- | Hspec 'configFormat' that generates a JUnit report-junitFormat :: JUnitConfig -> FormatConfig -> IO Format-junitFormat 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-          { suitesName = suiteName-          , suitesSuites =-              groups <&> \(group, items) -> do-                let suite xs =-                      Schema.Suite-                        { suiteName = group-                        , suiteTimestamp = time-                        , suiteCases = xs-                        }-                suite $ uncurry (itemToTestCase applyPrefix group) <$> items-          }--    runConduitRes $-      sourceList [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-    { testCaseLocation =-        toSchemaLocation applyPrefix-          <$> (itemResultLocation item <|> itemLocation item)-    , testCaseClassName = group-    , testCaseName = name-    , testCaseDuration = unSeconds $ itemDuration item-    , testCaseResult = 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 Location {..} ->-      mconcat-        [ pack $ applyPrefix locationFile-        , ":"-        , pack $ show locationLine-        , ":"-        , pack $ show locationColumn-        , "\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 Location {..} =-  Schema.Location-    { Schema.locationFile = applyPrefix locationFile-    , Schema.locationLine = fromIntegral $ max 0 locationLine-    }--unSeconds :: Seconds -> Double-unSeconds (Seconds x) = x--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--{- FOURMOLU_DISABLE -}-reasonToText :: FailureReason -> Text-reasonToText = \case-  Error _ err -> pack $ show err-  NoReason -> "no reason"-  Reason err -> pack err-#if MIN_VERSION_hspec_core(2,11,0)-  ColorizedReason err -> pack err-#endif-  ExpectedButGot preface expected actual ->-    T.unlines-      $ pack-      <$> fromMaybe "" preface-      : (foundLines "expected" expected-        <> foundLines " but got" actual-        )-{- FOURMOLU_ENABLE -}
library/Test/Hspec/JUnit/Config.hs view
@@ -24,12 +24,12 @@ import System.FilePath ((</>))  data JUnitConfig = JUnitConfig-  { junitConfigOutputDirectory :: FilePath-  , junitConfigOutputName :: FilePath-  , junitConfigOutputFile :: Maybe FilePath-  , junitConfigSuiteName :: Text-  , junitConfigSourcePathPrefix :: Maybe FilePath-  , junitConfigDropConsoleFormatting :: Bool+  { outputDirectory :: FilePath+  , outputName :: FilePath+  , outputFile :: Maybe FilePath+  , suiteName :: Text+  , sourcePathPrefix :: Maybe FilePath+  , dropConsoleFormatting :: Bool   }  -- | Construct a 'JUnitConfig' given a suite name@@ -38,35 +38,34 @@ defaultJUnitConfig :: Text -> JUnitConfig defaultJUnitConfig name =   JUnitConfig-    { junitConfigOutputDirectory = "."-    , junitConfigOutputName = "junit.xml"-    , junitConfigOutputFile = Nothing-    , junitConfigSuiteName = name-    , junitConfigSourcePathPrefix = Nothing-    , junitConfigDropConsoleFormatting = False+    { outputDirectory = "."+    , outputName = "junit.xml"+    , outputFile = Nothing+    , suiteName = name+    , sourcePathPrefix = Nothing+    , dropConsoleFormatting = False     }  -- | Set the directory within which to generate the report -- -- Default is current working directory. setJUnitConfigOutputDirectory :: FilePath -> JUnitConfig -> JUnitConfig-setJUnitConfigOutputDirectory x config =-  config {junitConfigOutputDirectory = x}+setJUnitConfigOutputDirectory x config = config {outputDirectory = x}  -- | Set the name for the generated report -- -- Default is @junit.xml@. setJUnitConfigOutputName :: FilePath -> JUnitConfig -> JUnitConfig-setJUnitConfigOutputName x config = config {junitConfigOutputName = x}+setJUnitConfigOutputName x config = config {outputName = x}  -- | Set the full path to the generated report -- -- If given, the directory and name configurations are ignored. setJUnitConfigOutputFile :: FilePath -> JUnitConfig -> JUnitConfig-setJUnitConfigOutputFile x config = config {junitConfigOutputFile = Just x}+setJUnitConfigOutputFile x config = config {outputFile = Just x}  setJUnitConfigSuiteName :: Text -> JUnitConfig -> JUnitConfig-setJUnitConfigSuiteName x config = config {junitConfigSuiteName = x}+setJUnitConfigSuiteName x config = config {suiteName = x}  -- | Set a prefix to apply to source paths in the report --@@ -74,8 +73,7 @@ -- in a monorepository, and you need reported paths to be from the repository -- root. setJUnitConfigSourcePathPrefix :: FilePath -> JUnitConfig -> JUnitConfig-setJUnitConfigSourcePathPrefix x config =-  config {junitConfigSourcePathPrefix = Just x}+setJUnitConfigSourcePathPrefix x config = config {sourcePathPrefix = Just x}  -- | Set whether console formatting characters should be dropped from failure -- reports.@@ -83,27 +81,23 @@ -- Default is False. Most XML processors will fail to parse the XML if it -- contains the ANSI control characters used by console formatting. setJUnitConfigDropConsoleFormatting :: Bool -> JUnitConfig -> JUnitConfig-setJUnitConfigDropConsoleFormatting x config = config {junitConfigDropConsoleFormatting = x}+setJUnitConfigDropConsoleFormatting x config = config {dropConsoleFormatting = x}  -- | Retrieve the full path to the generated report getJUnitConfigOutputFile :: JUnitConfig -> FilePath-getJUnitConfigOutputFile JUnitConfig {..} =-  fromMaybe-    (junitConfigOutputDirectory </> junitConfigOutputName)-    junitConfigOutputFile+getJUnitConfigOutputFile c = fromMaybe (c.outputDirectory </> c.outputName) c.outputFile  -- | Retrieve the suite name given on construction getJUnitConfigSuiteName :: JUnitConfig -> Text-getJUnitConfigSuiteName = junitConfigSuiteName+getJUnitConfigSuiteName c = c.suiteName  -- | Retrieve the function to apply to reported source paths -- -- Will be 'id' if no prefix configured. getJUnitPrefixSourcePath :: JUnitConfig -> FilePath -> FilePath-getJUnitPrefixSourcePath JUnitConfig {..} =-  maybe id (</>) junitConfigSourcePathPrefix+getJUnitPrefixSourcePath c = maybe id (</>) c.sourcePathPrefix  -- | Retrieve whether console formatting characters should be dropped from -- failure reports. getJUnitConfigDropConsoleFormatting :: JUnitConfig -> Bool-getJUnitConfigDropConsoleFormatting JUnitConfig {..} = junitConfigDropConsoleFormatting+getJUnitConfigDropConsoleFormatting c = c.dropConsoleFormatting
library/Test/Hspec/JUnit/Config/Env.hs view
@@ -11,7 +11,7 @@  import Data.Semigroup (Endo (..)) import Data.Text (pack, unpack)-import qualified Data.Text as T+import Data.Text qualified as T import System.Directory (getCurrentDirectory) import System.Environment (getEnvironment, lookupEnv) import System.FilePath (takeBaseName)@@ -42,16 +42,16 @@ readJUnitConfig base env = modify $ defaultJUnitConfig $ pack base  where   modify =-    appEndo $-      foldMap+    appEndo+      $ foldMap         Endo         [ readEnv "OUTPUT_DIRECTORY" setJUnitConfigOutputDirectory         , readEnv "OUTPUT_NAME" setJUnitConfigOutputName         , readEnv "OUTPUT_FILE" setJUnitConfigOutputFile         , readEnv "SUITE_NAME" $ setJUnitConfigSuiteName . pack         , readEnv "SOURCE_PATH_PREFIX" setJUnitConfigSourcePathPrefix-        , readEnv "DROP_CONSOLE_FORMATTING" $-            setJUnitConfigDropConsoleFormatting . (== "1")+        , readEnv "DROP_CONSOLE_FORMATTING"+            $ setJUnitConfigDropConsoleFormatting . (== "1")         ]    readEnv name setter =
library/Test/Hspec/JUnit/Format.hs view
@@ -4,22 +4,20 @@  import Prelude +import Conduit (runConduitRes, sinkFile, yield, (.|)) import Control.Applicative ((<|>))-import Data.Conduit (runConduitRes, (.|))-import Data.Conduit.Combinators (sinkFile)-import Data.Conduit.List (sourceList) import Data.Functor ((<&>))-import qualified Data.Map.Strict as Map+import Data.Map.Strict qualified as Map import Data.Maybe (fromMaybe) import Data.Text (Text, pack, unpack)-import qualified Data.Text as T+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 qualified Test.Hspec.JUnit.Schema as Schema+import Test.Hspec.JUnit.Schema qualified as Schema import Text.XML.Stream.Render (def, renderBytes)  junit :: JUnitConfig -> FormatConfig -> IO Format@@ -40,20 +38,20 @@       groups = groupItems paths       output =         Schema.Suites-          { suitesName = suiteName-          , suitesSuites =+          { name = suiteName+          , suites =               groups <&> \(group, items) -> do                 let suite xs =                       Schema.Suite-                        { suiteName = group-                        , suiteTimestamp = time-                        , suiteCases = xs+                        { name = group+                        , timestamp = time+                        , cases = xs                         }                 suite $ uncurry (itemToTestCase applyPrefix group) <$> items           } -    runConduitRes $-      sourceList [output]+    runConduitRes+      $ yield output         .| renderJUnit dropConsoleFormatting         .| renderBytes def         .| sinkFile file@@ -73,40 +71,37 @@   :: (FilePath -> FilePath) -> Text -> Text -> Item -> Schema.TestCase itemToTestCase applyPrefix group name item =   Schema.TestCase-    { testCaseLocation =+    { location =         toSchemaLocation applyPrefix           <$> (itemResultLocation item <|> itemLocation item)-    , testCaseClassName = group-    , testCaseName = name-    , testCaseDuration = unSeconds $ itemDuration item-    , testCaseResult = case itemResult item of+    , 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+          Just+            $ Schema.Skipped+            $ prefixLocation mLocation+            $ prefixInfo+            $ maybe "" pack mMessage         Failure mLocation reason ->-          Just $-            Schema.Failure "error" $-              prefixLocation mLocation $-                prefixInfo $-                  reasonToText reason+          Just+            $ Schema.Failure "error"+            $ prefixLocation mLocation+            $ prefixInfo+            $ reasonToText reason     }  where   prefixLocation mLocation str = case mLocation of     Nothing -> str-    Just Location {..} ->+    Just l ->       mconcat-        [ pack $ applyPrefix locationFile+        [ pack $ applyPrefix $ locationFile l         , ":"-        , pack $ show locationLine+        , pack $ show $ locationLine l         , ":"-        , pack $ show locationColumn+        , pack $ show $ locationColumn l         , "\n"         ]         <> str@@ -121,10 +116,10 @@   Failure mLocation _ -> mLocation  toSchemaLocation :: (FilePath -> FilePath) -> Location -> Schema.Location-toSchemaLocation applyPrefix Location {..} =+toSchemaLocation applyPrefix l =   Schema.Location-    { Schema.locationFile = applyPrefix locationFile-    , Schema.locationLine = fromIntegral $ max 0 locationLine+    { Schema.file = applyPrefix $ locationFile l+    , Schema.line = fromIntegral $ max 0 $ locationLine l     }  unSeconds :: Seconds -> Double@@ -136,8 +131,8 @@   NoReason -> "no reason"   Reason err -> pack err   ExpectedButGot preface expected actual ->-    T.unlines $-      pack+    T.unlines+      $ pack         <$> fromMaybe "" preface           : ( foundLines "expected" expected                 <> foundLines " but got" actual
library/Test/Hspec/JUnit/Formatter.hs view
@@ -8,7 +8,7 @@ -- import Test.Hspec.JUnit.Config -- import Test.Hspec.JUnit.Formatter qualified as Formatter ----- -- | To only produce a JUnit file, silencing other output+-- -- | To produce only a JUnit file, silencing other output -- hook :: Spec -> Spec -- hook = Formatter.use defaultJUnitConfig --@@ -16,7 +16,7 @@ -- hook :: Spec -> Spec -- hook = Formatter.add defaultJUnitConfig ----- -- | To only produce, but only when @--format=junit@ is used+-- -- | To produce only a JUnit file, and only when @--format=junit@ is used -- hook :: Spec -> Spec -- hook = Formatter.register defaultJUnitConfig -- @@@ -40,10 +40,9 @@ import Prelude  import Data.Maybe (fromMaybe)- import Test.Hspec.Api.Format.V1 as Api-import qualified Test.Hspec.Core.Format as Core-import qualified Test.Hspec.Core.Formatters.V2 as V2+import Test.Hspec.Core.Format qualified as Core+import Test.Hspec.Core.Formatters.V2 qualified as V2 import Test.Hspec.Core.Runner as Core (Config (..)) import Test.Hspec.JUnit.Config import Test.Hspec.JUnit.Format
library/Test/Hspec/JUnit/Formatter/Env.hs view
@@ -30,7 +30,7 @@ import Test.Hspec.Core.Spec (runIO) import Test.Hspec.JUnit.Config import Test.Hspec.JUnit.Config.Env-import qualified Test.Hspec.JUnit.Formatter as JUnit+import Test.Hspec.JUnit.Formatter qualified as JUnit  whenEnabled :: (SpecWith a -> SpecWith a) -> SpecWith a -> SpecWith a whenEnabled hook spec = do
library/Test/Hspec/JUnit/Render.hs view
@@ -4,13 +4,13 @@  import Prelude +import Conduit (ConduitT, awaitForever, mergeSource, yield, yieldMany, (.|)) import Control.Monad.Catch (MonadThrow)-import qualified Data.Array as Array-import Data.Conduit (ConduitT, awaitForever, mergeSource, yield, (.|))-import qualified Data.Conduit.List as CL+import Data.Array qualified as Array+import Data.Conduit.List qualified as CL import Data.Foldable (traverse_) import Data.Text (Text, pack)-import qualified Data.Text as Text+import Data.Text qualified as Text import Data.Time.ISO8601 (formatISO8601) import Data.XML.Types (Event) import Test.Hspec.JUnit.Schema@@ -21,46 +21,37 @@   , TestCase (..)   ) import Text.Printf-import qualified Text.Regex.Base as Regex-import qualified Text.Regex.TDFA.Text as Regex+import Text.Regex.Base qualified as Regex+import Text.Regex.TDFA.Text qualified as Regex import Text.XML.Stream.Render (attr, content, tag)  renderJUnit :: MonadThrow m => Bool -> ConduitT Suites Event m ()-renderJUnit shouldDropConsoleFormatting = awaitForever $ \Suites {..} ->-  tag "testsuites" (attr "name" suitesName) $-    CL.sourceList suitesSuites+renderJUnit shouldDropConsoleFormatting = awaitForever $ \s ->+  tag "testsuites" (attr "name" s.name)+    $ yieldMany s.suites       .| mergeSource idStream       .| suite shouldDropConsoleFormatting  where   idStream = CL.iterate (+ 1) 0  suite :: MonadThrow m => Bool -> ConduitT (Int, Suite) Event m ()-suite shouldDropConsoleFormatting = awaitForever $ \(i, theSuite@Suite {..}) ->-  tag "testsuite" (attributes i theSuite) $ do+suite shouldDropConsoleFormatting = awaitForever $ \(i, s) ->+  tag "testsuite" (attributes i s) $ do     tag "properties" mempty mempty-    CL.sourceList suiteCases .| do-      awaitForever $ \x -> yield x .| testCase shouldDropConsoleFormatting+    yieldMany s.cases .| testCase shouldDropConsoleFormatting  where   -- TODO these need to be made real values-  attributes i Suite {..} =-    attr "name" suiteName-      <> attr "package" suiteName+  attributes i s =+    attr "name" s.name+      <> attr "package" s.name       <> attr "id" (tshow i)-      <> attr "time" (roundToStr $ sumDurations suiteCases)-      <> attr "timestamp" (pack $ formatISO8601 suiteTimestamp)+      <> attr "time" (roundToStr $ sumDurations s.cases)+      <> attr "timestamp" (pack $ formatISO8601 s.timestamp)       <> attr "hostname" "localhost"-      <> attr "tests" (tshow $ length suiteCases)-      <> attr-        "failures"-        ( tshow $-            length [() | Just Failure {} <- testCaseResult <$> suiteCases]-        )+      <> attr "tests" (tshow $ length s.cases)+      <> attr "failures" (tshow $ length [() | Just Failure {} <- (.result) <$> s.cases])       <> attr "errors" "0"-      <> attr-        "skipped"-        ( tshow $-            length [() | Just Skipped {} <- testCaseResult <$> suiteCases]-        )+      <> attr "skipped" (tshow $ length [() | Just Skipped {} <- (.result) <$> s.cases])  tshow :: Show a => a -> Text tshow = pack . show@@ -68,13 +59,13 @@ testCase :: MonadThrow m => Bool -> ConduitT TestCase Event m () testCase shouldDropConsoleFormatting =   awaitForever $ \(TestCase mLocation className name duration mResult) ->-    tag "testcase" (attributes mLocation className name duration) $-      traverse_ yield mResult+    tag "testcase" (attributes mLocation className name duration)+      $ traverse_ yield mResult         .| result shouldDropConsoleFormatting  where   attributes mLocation className name duration =-    maybe mempty (attr "file" . pack . locationFile) mLocation-      <> maybe mempty (attr "line" . pack . show . locationLine) mLocation+    maybe mempty (attr "file" . pack . (.file)) mLocation+      <> maybe mempty (attr "line" . pack . show . (.line)) mLocation       <> attr "name" name       <> attr "classname" className       <> attr "time" (roundToStr duration)@@ -106,7 +97,7 @@     | otherwise = input  sumDurations :: [TestCase] -> Double-sumDurations cases = sum $ testCaseDuration <$> cases+sumDurations cases = sum $ (.duration) <$> cases  roundToStr :: PrintfArg a => a -> Text roundToStr = pack . printf "%0.9f"
library/Test/Hspec/JUnit/Schema.hs view
@@ -13,30 +13,30 @@ import Numeric.Natural  data Suites = Suites-  { suitesName :: Text-  , suitesSuites :: [Suite]+  { name :: Text+  , suites :: [Suite]   }   deriving stock (Show)  data Suite = Suite-  { suiteName :: Text-  , suiteTimestamp :: UTCTime-  , suiteCases :: [TestCase]+  { name :: Text+  , timestamp :: UTCTime+  , cases :: [TestCase]   }   deriving stock (Show)  data TestCase = TestCase-  { testCaseLocation :: Maybe Location-  , testCaseClassName :: Text-  , testCaseName :: Text-  , testCaseDuration :: Double-  , testCaseResult :: Maybe Result+  { location :: Maybe Location+  , className :: Text+  , name :: Text+  , duration :: Double+  , result :: Maybe Result   }   deriving stock (Show)  data Location = Location-  { locationFile :: FilePath-  , locationLine :: Natural+  { file :: FilePath+  , line :: Natural   }   deriving stock (Show) 
tests/Example.hs view
@@ -5,9 +5,9 @@ -- In fact, this sentence was added to retain them across a formatting change. module Example (spec) where -import Control.Exception import Prelude +import Control.Exception import Test.Hspec  spec :: Spec
tests/Test/Hspec/JUnit/FormatterSpec.hs view
@@ -8,26 +8,26 @@  import Control.Monad (void) import Data.Char (isSpace)-import qualified Data.Map.Strict as Map-import qualified Data.Text as T-import qualified Example+import Data.List (isInfixOf, isPrefixOf)+import Data.Map.Strict qualified as Map+import Data.Text qualified as T+import Example qualified import System.FilePath ((<.>), (</>)) import System.IO.Temp (withSystemTempDirectory) import Test.Hspec import Test.Hspec.Golden import Test.Hspec.JUnit.Config-import qualified Test.Hspec.JUnit.Formatter as Formatter+import Test.Hspec.JUnit.Formatter qualified as Formatter import Test.Hspec.Runner-import qualified Text.XML as XML+import Text.XML qualified as XML  spec :: Spec spec = do-  it "matches golden file" $+  it "matches golden file" $ do     junitGolden "default" id -  it "matches golden file with prefixing" $-    junitGolden "prefixed" $-      setJUnitConfigSourcePathPrefix "lol/monorepo"+  it "matches golden file with prefixing" $ do+    junitGolden "prefixed" $ setJUnitConfigSourcePathPrefix "lol/monorepo"  -- | Run @Example.spec@ and compare XML to a golden file junitGolden@@ -39,10 +39,10 @@ junitGolden name modifyConfig = do   actual <- withSystemTempDirectory "" $ \tmp -> do     let junitConfig =-          modifyConfig $-            setJUnitConfigOutputDirectory tmp $-              setJUnitConfigOutputName "test.xml" $-                defaultJUnitConfig "hspec-junit-format"+          modifyConfig+            $ setJUnitConfigOutputDirectory tmp+            $ setJUnitConfigOutputName "test.xml"+            $ defaultJUnitConfig "hspec-junit-format"      runSpec' $ Formatter.use junitConfig Example.spec     readNormalizedXML $ tmp </> "test.xml"@@ -68,7 +68,7 @@ readNormalizedXML = fmap normalizeDoc . XML.readFile XML.def  normalizeDoc :: XML.Document -> XML.Document-normalizeDoc = removeWhitespaceNodes . removeTimeAttributes+normalizeDoc = removeWhitespaceNodes . removeTimeAttributes . normalizeErrorMessages  removeWhitespaceNodes :: XML.Document -> XML.Document removeWhitespaceNodes doc =@@ -101,6 +101,72 @@       { XML.elementAttributes = Map.delete name $ XML.elementAttributes el       , XML.elementNodes = map (onNodeElement go) $ XML.elementNodes el       }++  onNodeElement f = \case+    XML.NodeElement el -> XML.NodeElement $ f el+    n -> n++normalizeErrorMessages :: XML.Document -> XML.Document+normalizeErrorMessages doc =+  doc+    { XML.documentRoot = go $ XML.documentRoot doc+    }+ where+  go el =+    el+      { XML.elementNodes =+          map (onNodeElement go . normalizeErrorContent . normalizeLineNumbers)+            $ XML.elementNodes el+      }++  normalizeErrorContent :: XML.Node -> XML.Node+  normalizeErrorContent = \case+    XML.NodeElement el+      | XML.elementName el == XML.Name "failure" Nothing Nothing ->+          XML.NodeElement+            $ el {XML.elementNodes = map stripLocationPrefix $ XML.elementNodes el}+      | XML.elementName el == XML.Name "skipped" Nothing Nothing ->+          XML.NodeElement+            $ el {XML.elementNodes = map stripLocationPrefix $ XML.elementNodes el}+      | otherwise -> XML.NodeElement el+    n -> n++  normalizeLineNumbers :: XML.Node -> XML.Node+  normalizeLineNumbers = \case+    XML.NodeElement el+      | XML.elementName el == XML.Name "testcase" Nothing Nothing ->+          let+            attrs = XML.elementAttributes el+            normalizedAttrs = Map.adjust normalizeLineAttr (XML.Name "line" Nothing Nothing) attrs+          in+            XML.NodeElement $ el {XML.elementAttributes = normalizedAttrs}+      | otherwise -> XML.NodeElement el+    n -> n++  normalizeLineAttr :: T.Text -> T.Text+  normalizeLineAttr lineText+    | lineText == "29" = "28" -- Normalize line 29 to 28 for version compatibility+    | otherwise = lineText++  stripLocationPrefix :: XML.Node -> XML.Node+  stripLocationPrefix = \case+    XML.NodeContent content ->+      let+        contentText = T.unpack content+        normalizedContent = case lines contentText of+          (firstLine : rest)+            | ( "tests/Example.hs:" `isPrefixOf` firstLine+                  || "lol/monorepo/tests/Example.hs:" `isPrefixOf` firstLine+              )+                && "\n" `isInfixOf` contentText ->+                unlines rest+          _ -> contentText+        trimmedContent = case reverse normalizedContent of+          '\n' : rest -> reverse rest+          _ -> normalizedContent+      in+        XML.NodeContent $ T.pack trimmedContent+    n -> n    onNodeElement f = \case     XML.NodeElement el -> XML.NodeElement $ f el