Skip to content

Commit

Permalink
OPCT-304: cmd: adm parser-junit
Browse files Browse the repository at this point in the history
Introduce the command 'opct adm parser-junit' to be used as a helper
when reviewing raw XML/JUnit files.
  • Loading branch information
mtulio committed Sep 5, 2024
1 parent 61ec642 commit 8f78fc4
Show file tree
Hide file tree
Showing 4 changed files with 299 additions and 0 deletions.
121 changes: 121 additions & 0 deletions pkg/api/junit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package api

import (
"encoding/xml"
"fmt"
"os"

log "github.com/sirupsen/logrus"
)

// Parse the XML data (JUnit created by openshift-tests)
type TestStatus string

const (
TestStatusPass TestStatus = "pass"
TestStatusFail TestStatus = "fail"
TestStatusSkipped TestStatus = "skipped"
)

type propSkipped struct {
Message string `xml:"message,attr"`
}

type Property struct {
Name string `xml:"name,attr"`
Value string `xml:"value,attr"`
}

type TestCase struct {
Name string `xml:"name,attr"`
Time string `xml:"time,attr"`
Failure string `xml:"failure"`
Skipped propSkipped `xml:"skipped"`
SystemOut string `xml:"system-out"`
Status TestStatus
}

type TestSuite struct {
XMLName xml.Name `xml:"testsuite"`
Name string `xml:"name,attr"`
Tests int `xml:"tests,attr"`
Skipped int `xml:"skipped,attr"`
Failures int `xml:"failures,attr"`
Time string `xml:"time,attr"`
Property Property `xml:"property"`
Properties Property `xml:"properties,omitempty"`
TestCases []TestCase `xml:"testcase"`
}

type TestSuites struct {
Tests int `xml:"tests,attr"`
Disabled int `xml:"disabled,attr"`
Errors int `xml:"errors,attr"`
Failures int `xml:"failures,attr"`
Time string `xml:"time,attr"`
TestSuite TestSuite `xml:"testsuite"`
}

type JUnitCounter struct {
Total int
Skipped int
Failures int
Pass int
}

type JUnitXMLParser struct {
XMLFile string
Parsed *TestSuite
Counters *JUnitCounter
Failures []string
Cases []*TestCase
}

func NewJUnitXMLParser(xmlFile string) (*JUnitXMLParser, error) {
p := &JUnitXMLParser{
XMLFile: xmlFile,
Parsed: &TestSuite{},
Counters: &JUnitCounter{},
Cases: []*TestCase{},
}
xmlData, err := os.ReadFile(xmlFile)
if err != nil {
return nil, fmt.Errorf("error reading XML file: %w", err)
}
if err := xml.Unmarshal(xmlData, p.Parsed); err != nil {
ts := &TestSuites{}
if err.Error() == "expected element type <testsuite> but have <testsuites>" {
log.Warnf("Found errors while processing default JUnit format, attempting new JUnit format for e2e...")
if err := xml.Unmarshal(xmlData, ts); err != nil {
return nil, fmt.Errorf("error parsing XML data with testsuites: %w", err)
}
p.Parsed = &ts.TestSuite
} else {
return nil, fmt.Errorf("error parsing XML data: %w", err)
}
}
// Iterate over the test cases
for _, testcase := range p.Parsed.TestCases {
// Access the properties of each test case
tc := &testcase
p.Counters.Total += 1
if len(testcase.Skipped.Message) > 0 {
p.Counters.Skipped += 1
tc.Status = TestStatusSkipped
p.Cases = append(p.Cases, tc)
continue
}
if len(testcase.Failure) > 0 {
p.Counters.Failures += 1
p.Failures = append(p.Failures, fmt.Sprintf("\"%s\"", testcase.Name))
tc.Status = TestStatusFail
p.Cases = append(p.Cases, tc)
continue
}
tc.Status = TestStatusPass
p.Cases = append(p.Cases, tc)
}
p.Counters.Pass = p.Counters.Total - (p.Counters.Skipped + p.Counters.Failures)

return p, nil
}
96 changes: 96 additions & 0 deletions pkg/api/junit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package api

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
)

func TestNewJUnitXMLParser(t *testing.T) {
// Create a fake JUnit XML file
xmlFile := createFakeJUnitXMLFileForOpenShiftTests()
defer removeFakeJUnitXMLFile(xmlFile)

parser, err := NewJUnitXMLParser(xmlFile)
assert.NoError(t, err)
assert.NotNil(t, parser)

// Assert the parsed test suite
assert.Equal(t, "openshift-tests", parser.Parsed.Name)
assert.Equal(t, 3, parser.Parsed.Tests)
assert.Equal(t, 1, parser.Parsed.Skipped)
assert.Equal(t, 1, parser.Parsed.Failures)
assert.Equal(t, "TestVersion", parser.Parsed.Property.Name)
assert.Equal(t, "4.14.0-202310201027.p0.g948001a.assembly.stream-948001a", parser.Parsed.Property.Value)

// Assert the parsed test cases
assert.Len(t, parser.Cases, 3)

// Assert the pass test case
assert.Equal(t, "test_case_name_1", parser.Cases[0].Name)
assert.Equal(t, "test_case_time_1", parser.Cases[0].Time)
assert.Equal(t, "test_case_system_out_1", parser.Cases[0].SystemOut)
assert.Equal(t, TestStatusPass, parser.Cases[0].Status)

// Assert the skipped test case
assert.Equal(t, "test_case_name_2", parser.Cases[1].Name)
assert.Equal(t, "test_case_time_2", parser.Cases[1].Time)
assert.Equal(t, "test_case_system_out_2", parser.Cases[1].SystemOut)
assert.Equal(t, TestStatusSkipped, parser.Cases[1].Status)
assert.Equal(t, "test_case_skipped_message_2", parser.Cases[1].Skipped.Message)

// Assert the failed test case
assert.Equal(t, "test_case_name_3", parser.Cases[2].Name)
assert.Equal(t, "test_case_time_3", parser.Cases[2].Time)
assert.Equal(t, "test_case_system_out_3", parser.Cases[2].SystemOut)
assert.Equal(t, TestStatusFail, parser.Cases[2].Status)
assert.Equal(t, "test_case_failure_3", parser.Cases[2].Failure)

// Assert the counters
assert.Equal(t, 3, parser.Counters.Total)
assert.Equal(t, 1, parser.Counters.Skipped)
assert.Equal(t, 1, parser.Counters.Failures)
assert.Equal(t, 1, parser.Counters.Pass)
}

func createFakeJUnitXMLFileForOpenShiftTests() string {
// Create a temporary file
file, err := os.CreateTemp("", "opct-e2e.xml")
if err != nil {
panic(err)
}
defer file.Close()

// Case 1: Write the fake JUnit XML content to the file
content := `<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="openshift-tests" tests="3" skipped="1" failures="1" time="2568">
<property name="TestVersion" value="4.14.0-202310201027.p0.g948001a.assembly.stream-948001a"></property>
<testcase name="test_case_name_1" time="test_case_time_1">
<system-out>test_case_system_out_1</system-out>
</testcase>
<testcase name="test_case_name_2" time="test_case_time_2">
<system-out>test_case_system_out_2</system-out>
<skipped message="test_case_skipped_message_2"/>
</testcase>
<testcase name="test_case_name_3" time="test_case_time_3">
<system-out>test_case_system_out_3</system-out>
<failure>test_case_failure_3</failure>
</testcase>
</testsuite>`
_, err = file.WriteString(content)
if err != nil {
panic(err)
}

// Return the file path
return file.Name()
}

func removeFakeJUnitXMLFile(file string) {
// Remove the temporary file
err := os.Remove(file)
if err != nil {
panic(err)
}
}
81 changes: 81 additions & 0 deletions pkg/cmd/adm/parsejunit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package adm

import (
"fmt"
"strings"

opctapi "github.com/redhat-openshift-ecosystem/provider-certification-tool/pkg/api"
"github.com/spf13/cobra"
)

type parseJUnitInput struct {
skipFailed bool
skipPassed bool
skipSkipped bool
}

var parseJUnitArgs parseJUnitInput
var parseJUnitCmd = &cobra.Command{
Use: "parse-junit",
Example: "opct adm parse-junit",
Short: "Parse JUnit file.",
RunE: parseJUnitRun,
}

func init() {
parseJUnitCmd.Flags().BoolVar(&parseJUnitArgs.skipFailed, "skip-failed", false, "Skip printing on stdout the failed test names.")
parseJUnitCmd.Flags().BoolVar(&parseJUnitArgs.skipPassed, "skip-passed", false, "Skip printing on stdout the passed test names.")
parseJUnitCmd.Flags().BoolVar(&parseJUnitArgs.skipSkipped, "skip-skipped", false, "Skip printing on stdout the skipped test names.")
}

func parseJUnitRun(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return fmt.Errorf("please provide the path to the JUnit file")
}

junitFile := args[0]
parser, err := opctapi.NewJUnitXMLParser(junitFile)
if err != nil {
return fmt.Errorf("error parsing JUnit file: %v", err)
}

// Printing summary
fmt.Println("Summary:")
fmt.Printf("- File: %s\n", parser.XMLFile)
fmt.Printf("- Total: %d\n", parser.Counters.Total)
fmt.Printf("- Pass: %d\n", parser.Counters.Pass)
fmt.Printf("- Skipped: %d\n", parser.Counters.Skipped)
fmt.Printf("- Failures: %d\n", parser.Counters.Failures)
fmt.Println()
fmt.Println("JUnit Attributes:")
fmt.Printf("- XMLName: %v\n", parser.Parsed.XMLName)
fmt.Printf("- Name: %s\n", parser.Parsed.Name)
fmt.Printf("- Tests: %d\n", parser.Parsed.Tests)
fmt.Printf("- Skipped: %d\n", parser.Parsed.Skipped)
fmt.Printf("- Failures: %d\n", parser.Parsed.Failures)
fmt.Printf("- Time: %s\n", parser.Parsed.Time)
fmt.Printf("- Property: %v\n", parser.Parsed.Property)

// Passed tests
passed := []string{}
skipped := []string{}
for _, testcase := range parser.Cases {
if testcase.Status == opctapi.TestStatusPass {
passed = append(passed, testcase.Name)
}
if testcase.Status == opctapi.TestStatusSkipped {
skipped = append(skipped, testcase.Name)
}
}

if !parseJUnitArgs.skipPassed {
fmt.Printf("\n#> Passed tests (%d): \n%s\n", len(passed), strings.Join(passed, "\n"))
}
if !parseJUnitArgs.skipFailed {
fmt.Printf("\n#> Failed tests (%d): \n%s\n", len(parser.Failures), strings.Join(parser.Failures, "\n"))
}
if !parseJUnitArgs.skipSkipped {
fmt.Printf("\n#> Skipped tests (%d): \n%s\n", len(skipped), strings.Join(skipped, "\n"))
}
return nil
}
1 change: 1 addition & 0 deletions pkg/cmd/adm/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ var admCmd = &cobra.Command{
func init() {
admCmd.AddCommand(parseMetricsCmd)
admCmd.AddCommand(parseEtcdLogsCmd)
admCmd.AddCommand(parseJUnitCmd)
admCmd.AddCommand(baseline.NewCmdBaseline())
admCmd.AddCommand(setupNodeCmd)
}
Expand Down

0 comments on commit 8f78fc4

Please sign in to comment.