Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ Here is an example that uses trap to always created the test report:
go install github.com/jstemmer/go-junit-report@latest
go install github.com/alexec/junit2html@latest

trap 'go-junit-report < test.out > junit.xml && junit2html < junit.xml > test-report.html' EXIT
trap 'go-junit-report < test.out > junit.xml && junit2html --xmlReports junit.xml > test-report.html' EXIT

go test -v -cover ./... 2>&1 > test.out
```

💡 Don't use pipes (i.e. `|`) in shell, pipes swallow exit codes. Use `<` and `>` which is POSIX compliant.
💡 Don't use pipes (i.e. `|`) in shell, pipes swallow exit codes. Use `>` which is POSIX compliant.

## Test

Expand All @@ -33,5 +33,24 @@ How to test this locally:
```bash
go test -v -cover ./... 2>&1 > test.out
go-junit-report < test.out > junit.xml
go run . < junit.xml > test-report.html
go run . --xmlReports junit.xml > test-report.html
```

## Using glob patterns:

Sometimes there is a need to parse multiple xml files and generate single html report.
`junit2html` supports that by using standard [`glob` expression](https://pkg.go.dev/path/filepath#Glob).

```bash
# Explicit single file
junit2html --xmlReports "reports/junit.xml" > report.html

# Multiple files
junit2html --xmlReports "reports/junit.xml,reports/coverage.xml" > report.html

# Single glob pattern
junit2html --xmlReports "reports/*.xml" > report.html

# Multiple glob patterns
junit2html --xmlReports "reports/junit*.xml,reports/coverage*.xml" > report.html
```
81 changes: 62 additions & 19 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package main

import (
"bytes"
_ "embed"
"encoding/xml"
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -40,12 +44,44 @@ func printTest(s formatter.JUnitTestSuite, c formatter.JUnitTestCase) {
fmt.Printf("</div>\n")
}

func main() {
suites := &formatter.JUnitTestSuites{}
// arguments
var (
xmlReports *string
)

err := xml.NewDecoder(os.Stdin).Decode(suites)
if err != nil {
panic(err)
func init() {
xmlReports = flag.String("xmlReports", "", "Comma delimited glob expressions describing the files to scan")
}

func main() {
flag.Parse()
if (*xmlReports) == "" {
panic("xmlReports cannot be empty")
}
patterns := strings.Split((*xmlReports), ",")
files := []string{}
for _, p := range patterns {
fmt.Fprintf(os.Stderr, "Given xmlReports '%s'\n", p)
matches, err := filepath.Glob(p)
if err != nil {
panic(err)
}
files = append(files, matches...)
}
allSuites := make([]formatter.JUnitTestSuites, 0, len(files))
for _, f := range files {
fmt.Fprintf(os.Stderr, "Parsing file '%s'\n", f)
res, err := ioutil.ReadFile(f)
if err != nil {
panic(err)
}
testResult := bytes.NewReader(res)
suites := &formatter.JUnitTestSuites{}
err = xml.NewDecoder(testResult).Decode(suites)
if err != nil {
panic(err)
}
allSuites = append(allSuites, *suites)
}

fmt.Println("<html>")
Expand All @@ -56,27 +92,34 @@ func main() {
fmt.Println("</style>")
fmt.Println("</head>")
fmt.Println("<body>")

failures, total := 0, 0
for _, s := range suites.Suites {
failures += s.Failures
total += len(s.TestCases)
for _, suites := range allSuites {
for _, s := range suites.Suites {
failures += s.Failures
total += len(s.TestCases)
}
}
fmt.Printf("<p>%d of %d tests failed</p>\n", failures, total)
for _, s := range suites.Suites {
if s.Failures > 0 {
printSuiteHeader(s)
for _, c := range s.TestCases {
if f := c.Failure; f != nil {
printTest(s, c)
for _, suites := range allSuites {
for _, s := range suites.Suites {
if s.Failures > 0 {
printSuiteHeader(s)
for _, c := range s.TestCases {
if f := c.Failure; f != nil {
printTest(s, c)
}
}
}
}
}
for _, s := range suites.Suites {
printSuiteHeader(s)
for _, c := range s.TestCases {
if c.Failure == nil {
printTest(s, c)
for _, suites := range allSuites {
for _, s := range suites.Suites {
printSuiteHeader(s)
for _, c := range s.TestCases {
if c.Failure == nil {
printTest(s, c)
}
}
}
}
Expand Down