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

OPCT-304: cmd: adm parser-junit #123

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Crating the api package as this lib can be used by plugins too.
Should we need to place under api/junit directory creating a dedicated package?


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
Loading