Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: JUnit processing for large files #5879

Merged
merged 15 commits into from
Oct 3, 2024
Merged
46 changes: 22 additions & 24 deletions cmd/testworkflow-toolkit/artifacts/junit_post_processor.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package artifacts

import (
"bufio"
"bytes"
"context"
"fmt"
"io"
Expand Down Expand Up @@ -39,6 +37,7 @@ func (p *JUnitPostProcessor) Start() error {

// Add checks if the file is a JUnit report and sends it to the cloud.
func (p *JUnitPostProcessor) Add(path string) error {
fmt.Printf("Checking file: %s\n", ui.LightCyan(path))
devcatalin marked this conversation as resolved.
Show resolved Hide resolved
uploadPath := path
if p.pathPrefix != "" {
uploadPath = filepath.Join(p.pathPrefix, uploadPath)
Expand All @@ -63,10 +62,7 @@ func (p *JUnitPostProcessor) Add(path string) error {
if err != nil {
return errors.Wrapf(err, "failed to read %s", path)
}
ok, err := isJUnitReport(xmlData)
if err != nil {
return errors.Wrapf(err, "failed to check if %s is a JUnit report", path)
}
ok := isJUnitReport(xmlData)
devcatalin marked this conversation as resolved.
Show resolved Hide resolved
if !ok {
return nil
}
Expand All @@ -85,6 +81,7 @@ func (p *JUnitPostProcessor) sendJUnitReport(path string, report []byte) error {
}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
fmt.Printf("Sending JUnit report to the cloud: %s\n", ui.LightCyan(path))
_, err := p.client.Execute(ctx, testworkflow.CmdTestWorkflowExecutionAddReport, &testworkflow.ExecutionsAddReportRequest{
ID: env.ExecutionId(),
WorkflowName: env.WorkflowName(),
Expand All @@ -104,27 +101,28 @@ func isXMLFile(stat fs.FileInfo) bool {
return strings.HasSuffix(stat.Name(), ".xml")
}

// isJUnitReport checks if the file starts with a JUnit XML tag
func isJUnitReport(xmlData []byte) (bool, error) {
scanner := bufio.NewScanner(bytes.NewReader(xmlData))
// Read only the first few lines of the file
for scanner.Scan() {
line := scanner.Text()
line = strings.TrimSpace(line) // Remove leading and trailing whitespace

// Skip comments and declarations
if strings.HasPrefix(line, "<!--") || strings.HasPrefix(line, "<?xml") {
continue
}
if strings.Contains(line, "<testsuite") || strings.Contains(line, "<testsuites") {
return true, nil
}
if strings.Contains(line, "<") { // Stop if any non-JUnit tag is found
break
// isJUnitReport checks if the XML data is a JUnit report.
func isJUnitReport(xmlData []byte) bool {
const BYTE_SIZE_8KB = 8 * 1024

tags := []string{
"<testsuite",
"<testsuites",
}

if len(xmlData) > BYTE_SIZE_8KB {
xmlData = xmlData[:BYTE_SIZE_8KB]
}

content := string(xmlData)

for _, tag := range tags {
if strings.Contains(content, tag) {
return true
}
}

return false, scanner.Err()
return false
}

func (p *JUnitPostProcessor) End() error {
Expand Down
20 changes: 16 additions & 4 deletions cmd/testworkflow-toolkit/artifacts/junit_post_processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,21 @@ func TestIsJUnitReport(t *testing.T) {
file: filesystem.NewMockFile("invalid.xml", []byte(testdata.InvalidJUnit)),
want: false,
},
{
name: "one-line junit",
file: filesystem.NewMockFile("oneline.xml", []byte(testdata.OneLineJUnit)),
want: true,
},
{
name: "testsuites only junit",
file: filesystem.NewMockFile("testsuites.xml", []byte(testdata.TestsuitesOnlyJUnit)),
want: true,
},
{
name: "testsuite only junit",
file: filesystem.NewMockFile("testsuite.xml", []byte(testdata.TestsuiteOnlyJUnit)),
want: true,
},
}

for _, tc := range tests {
Expand All @@ -154,10 +169,7 @@ func TestIsJUnitReport(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
ok, err := isJUnitReport(data)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
ok := isJUnitReport(data)
assert.Equal(t, tc.want, ok)
})
}
Expand Down
3 changes: 3 additions & 0 deletions cmd/testworkflow-toolkit/artifacts/walker.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ func (w *walker) walk(fsys fs.FS, path string, walker WalkerFn) error {
sanitizedPath = "."
}

fmt.Printf("Walking: %s\n", sanitizedPath)
devcatalin marked this conversation as resolved.
Show resolved Hide resolved

return fs.WalkDir(fsys, sanitizedPath, func(filePath string, d fs.DirEntry, err error) error {
resolvedPath := "/" + filepath.ToSlash(filePath)
if !w.matches(resolvedPath) {
Expand All @@ -219,6 +221,7 @@ func (w *walker) walk(fsys fs.FS, path string, walker WalkerFn) error {

// Pass the data to final walker
relativeFilePath := strings.TrimLeft(resolvedPath[len(w.root):], "/")
fmt.Printf("Relative path: %s\n", relativeFilePath)
devcatalin marked this conversation as resolved.
Show resolved Hide resolved
file, err := fsys.Open(filePath)
return walker(relativeFilePath, file, stat, err)
})
Expand Down
2 changes: 2 additions & 0 deletions cmd/testworkflow-toolkit/commands/artifacts.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ func run(handler artifacts.Handler, walker artifacts.Walker, dirFS fs.FS) {

started := time.Now()
err = walker.Walk(dirFS, func(path string, file fs.File, _ fs.FileInfo, err error) error {
fmt.Printf("Processing: %s\n", ui.LightCyan(path))
devcatalin marked this conversation as resolved.
Show resolved Hide resolved

if err != nil {
fmt.Printf("Warning: '%s' has been ignored, as there was a problem reading it: %s\n", path, err.Error())
return nil
Expand Down
13 changes: 13 additions & 0 deletions cmd/testworkflow-toolkit/common/testdata/junit.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,16 @@ var InvalidJUnit = `<?xml version="1.0" encoding="UTF-8"?>
<foo>
<bar>
</foo>`

var OneLineJUnit = `<?xml version="1.0" encoding="UTF-8"?><testsuites><testsuite name="TestSuite" tests="2" errors="0" failures="1" skipped="0"><testcase name="Test1" classname="TestClass"><failure message="Test failed">Failure details</failure></testcase><testcase name="Test2" classname="TestClass"/></testsuite></testsuites>`

var TestsuitesOnlyJUnit = `<testsuites id="" name="" tests="2" failures="0" skipped="0" errors="0" time="14.511833">
<testsuite name="smoke.spec.js" timestamp="2024-10-01T12:48:47.332Z" hostname="chromium" tests="1" failures="0" skipped="0" time="6.259" errors="0">
<testcase name="Smoke 1 - has title" classname="smoke.spec.js" time="6.259"></testcase>
</testsuite>
<testsuite name="smoke2.spec.js" timestamp="2024-10-01T12:48:47.332Z" hostname="chromium" tests="1" failures="0" skipped="0" time="6.657" errors="0">
<testcase name="Smoke 2 - has title" classname="smoke2.spec.js" time="6.657"></testcase>
</testsuite>
</testsuites>`

var TestsuiteOnlyJUnit = `<testsuite name="smoke.spec.js" timestamp="2024-10-01T12:48:47.332Z" hostname="chromium" tests="1" failures="0" skipped="0" time="6.259" errors="0"><testcase name="Smoke 1 - has title" classname="smoke.spec.js" time="6.259"></testcase></testsuite>`
Loading