From 938c32896b62a86969535e4e2989ad9c2bc58646 Mon Sep 17 00:00:00 2001 From: Kuniwak Date: Wed, 17 Apr 2024 14:59:48 +0900 Subject: [PATCH] Add file attributes for JUnit Test Report --- go.mod | 1 - tool/gh-action/runner/runner.go | 5 +- tool/unity-meta-check-junit/cmd/cmd.go | 6 +- tool/unity-meta-check-junit/junit/junit.go | 161 +++++++++++++----- .../junit/junit_stub.go | 11 +- .../junit/junit_test.go | 77 +++++++-- 6 files changed, 188 insertions(+), 73 deletions(-) diff --git a/go.mod b/go.mod index 866896b..e827533 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.18 require ( github.com/google/go-cmp v0.5.5 - github.com/jstemmer/go-junit-report v0.9.1 github.com/pkg/errors v0.9.1 github.com/scylladb/go-set v1.0.2 ) diff --git a/tool/gh-action/runner/runner.go b/tool/gh-action/runner/runner.go index cb26061..bfd3931 100644 --- a/tool/gh-action/runner/runner.go +++ b/tool/gh-action/runner/runner.go @@ -10,7 +10,6 @@ import ( "github.com/DeNA/unity-meta-check/unity/checker" "github.com/DeNA/unity-meta-check/util/logging" "io" - "time" ) type Runner func(opts *Options) (bool, error) @@ -25,8 +24,6 @@ func NewRunner( logger logging.Logger, ) Runner { return func(opts *Options) (bool, error) { - startTime := time.Now() - logger.Debug(fmt.Sprintf("check: %#v", opts.CheckerOpts)) resultNotFiltered, err := check(opts.RootDirAbs, opts.CheckerOpts) if err != nil { @@ -57,7 +54,7 @@ func NewRunner( if opts.EnableJUnit { logger.Debug(fmt.Sprintf("write junit report: %q", opts.JUnitOutPath)) - if err := writeJunitXML(resultFiltered, startTime, opts.JUnitOutPath); err != nil { + if err := writeJunitXML(resultFiltered, opts.JUnitOutPath); err != nil { return false, err } } else { diff --git a/tool/unity-meta-check-junit/cmd/cmd.go b/tool/unity-meta-check-junit/cmd/cmd.go index ced83bd..06db9ba 100644 --- a/tool/unity-meta-check-junit/cmd/cmd.go +++ b/tool/unity-meta-check-junit/cmd/cmd.go @@ -9,13 +9,10 @@ import ( "github.com/DeNA/unity-meta-check/util/cli" "github.com/DeNA/unity-meta-check/version" "io" - "time" ) func NewMain() cli.Command { return func(args []string, procInout cli.ProcessInout, env cli.Env) cli.ExitStatus { - startTime := time.Now() - opts, err := options.BuildOptions(args, procInout) if err != nil { if err != flag.ErrHelp { @@ -32,7 +29,7 @@ func NewMain() cli.Command { parse := report.NewParser() result := parse(io.TeeReader(procInout.Stdin, procInout.Stdout)) - if err := junit.WriteToFile(result, startTime, opts.OutPath); err != nil { + if err := junit.WriteToFile(result, opts.OutPath); err != nil { _, _ = fmt.Fprintln(procInout.Stderr, err.Error()) return cli.ExitAbnormal } @@ -43,4 +40,3 @@ func NewMain() cli.Command { return cli.ExitNormal } } - diff --git a/tool/unity-meta-check-junit/junit/junit.go b/tool/unity-meta-check-junit/junit/junit.go index 0547670..63b78a3 100644 --- a/tool/unity-meta-check-junit/junit/junit.go +++ b/tool/unity-meta-check-junit/junit/junit.go @@ -1,21 +1,62 @@ package junit import ( + "encoding/xml" "fmt" "github.com/DeNA/unity-meta-check/unity" "github.com/DeNA/unity-meta-check/unity/checker" "github.com/DeNA/unity-meta-check/util/typedpath" - "github.com/jstemmer/go-junit-report/formatter" - "github.com/jstemmer/go-junit-report/parser" "io" "os" "runtime" - "time" ) -type WriteToFileFunc func(result *checker.CheckResult, startTime time.Time, outPath typedpath.RawPath) error +type TestSuites struct { + XMLName xml.Name `xml:"testsuites"` + TestSuites []TestSuite `xml:"testsuite"` +} + +type TestSuite struct { + XMLName xml.Name `xml:"testsuite"` + Tests int `xml:"tests,attr"` + Failures int `xml:"failures,attr"` + Time string `xml:"time,attr"` + Name string `xml:"name,attr"` + File *string `xml:"file,attr,omitempty"` + Properties Properties `xml:"properties"` + TestCases []TestCase `xml:"testcase"` +} + +type TestCase struct { + XMLName xml.Name `xml:"testcase"` + ClassName string `xml:"classname,attr"` + Name string `xml:"name,attr"` + Time string `xml:"time,attr"` + File *string `xml:"file,attr,omitempty"` + Failure *Failure `xml:"failure,omitempty"` +} + +type Failure struct { + XMLName xml.Name `xml:"failure"` + Message string `xml:"message,attr"` + Type string `xml:"type,attr"` + Contents string `xml:",chardata"` +} -func WriteToFile(result *checker.CheckResult, startTime time.Time, outPath typedpath.RawPath) error { +type Properties struct { + XMLName xml.Name `xml:"properties"` + Properties []Property `xml:"property"` +} + +type Property struct { + XMLName xml.Name `xml:"property"` + Name string `xml:"name,attr"` + Value string `xml:"value,attr"` +} + +type WriteToFileFunc func(result *checker.CheckResult, outPath typedpath.RawPath) error + +func WriteToFile(result *checker.CheckResult, outPath typedpath.RawPath) error { if err := os.MkdirAll(string(outPath.Dir()), 0755); err != nil { return err } @@ -24,72 +65,108 @@ func WriteToFile(result *checker.CheckResult, startTime time.Time, outPath typed if err != nil { return err } - defer func(){ _ = file.Close() }() + defer func() { _ = file.Close() }() - endTime := time.Now() - return Write(result, endTime.Sub(startTime), file) + return Write(result, file) } -func Write(result *checker.CheckResult, duration time.Duration, writer io.Writer) error { +func Write(result *checker.CheckResult, writer io.Writer) error { maxLen := result.Len() - var packages []parser.Package - if maxLen == 0 { - packages = []parser.Package{ + props := &Properties{ + Properties: []Property{ { - Name: "unity-meta-check", - Tests: []*parser.Test{ - { - Name: "OK", - Result: parser.PASS, - Output: []string{"No missing or dangling .meta exist. Perfect!"}, - Duration: duration, + Name: "go.version", + Value: runtime.Version(), + }, + }, + } + var testSuites *TestSuites + if maxLen == 0 { + testSuites = &TestSuites{ + TestSuites: []TestSuite{ + { + Name: "unity-meta-check", + Tests: 1, + Failures: 0, + Time: "0.000", + Properties: *props, + TestCases: []TestCase{ + { + ClassName: "unity-meta-check", + Name: "OK", + Time: "0.000", + }, }, }, }, } } else { - durationAvg := time.Duration(int(duration) / maxLen) - packages = make([]parser.Package, maxLen) + suites := make([]TestSuite, maxLen) i := 0 for _, missingMeta := range result.MissingMeta { - packages[i] = parser.Package{ - Name: string(unity.TrimMetaFromSlash(missingMeta)), - Tests: []*parser.Test{ + file := string(unity.TrimMetaFromSlash(missingMeta)) + suites[i] = TestSuite{ + Name: file, + Tests: 1, + Time: "0.000", + Failures: 1, + File: &file, + Properties: *props, + TestCases: []TestCase{ { - Name: "meta", - Result: parser.FAIL, - Output: []string{ - fmt.Sprintf("File or directory exists: %s", unity.TrimMetaFromSlash(missingMeta)), - fmt.Sprintf("But .meta is missing: %s", missingMeta), + ClassName: "missing", + Name: "meta", + Time: "0.000", + File: &file, + Failure: &Failure{ + Message: "Failed", + Contents: fmt.Sprintf("File or directory exists: %s\nBut .meta is missing: %s", file, missingMeta), }, - Duration: durationAvg, }, }, } i++ } for _, danglingMeta := range result.DanglingMeta { - packages[i] = parser.Package{ - Name: string(unity.TrimMetaFromSlash(danglingMeta)), - Tests: []*parser.Test{ + file := string(unity.TrimMetaFromSlash(danglingMeta)) + suites[i] = TestSuite{ + Name: file, + Tests: 1, + Time: "0.000", + Failures: 1, + File: &file, + Properties: *props, + TestCases: []TestCase{ { - Name: "meta", - Result: parser.FAIL, - Output: []string{ - fmt.Sprintf("File or directory does not exist: %s", unity.TrimMetaFromSlash(danglingMeta)), - fmt.Sprintf("But .meta is present: %s", danglingMeta), + ClassName: "dangling", + Name: "meta", + Time: "0.000", + File: &file, + Failure: &Failure{ + Message: "Failed", + Contents: fmt.Sprintf("File or directory does not exist: %s\nBut .meta is present: %s", file, danglingMeta), }, - Duration: durationAvg, }, }, } i++ } + testSuites = &TestSuites{ + TestSuites: suites, + } } - junitReport := &parser.Report{Packages: packages} - - if err := formatter.JUnitReportXML(junitReport, false, runtime.Version(), writer); err != nil { + bs, err := xml.MarshalIndent(testSuites, "", "\t") + if err != nil { + return err + } + if _, err := io.WriteString(writer, xml.Header); err != nil { + return err + } + if _, err := writer.Write(bs); err != nil { + return err + } + if _, err := writer.Write([]byte{'\n'}); err != nil { return err } return nil diff --git a/tool/unity-meta-check-junit/junit/junit_stub.go b/tool/unity-meta-check-junit/junit/junit_stub.go index 37c0469..0dabbd3 100644 --- a/tool/unity-meta-check-junit/junit/junit_stub.go +++ b/tool/unity-meta-check-junit/junit/junit_stub.go @@ -4,18 +4,16 @@ import ( "errors" "github.com/DeNA/unity-meta-check/unity/checker" "github.com/DeNA/unity-meta-check/util/typedpath" - "time" ) func StubWriteToFileFunc(err error) WriteToFileFunc { - return func(_ *checker.CheckResult, _ time.Time, _ typedpath.RawPath) error { + return func(_ *checker.CheckResult, _ typedpath.RawPath) error { return err } } type WriteToFileCallArgs struct { CheckResult *checker.CheckResult - StartTime time.Time OutPath typedpath.RawPath } @@ -23,12 +21,11 @@ func SpyWriteToFileFunc(inherited WriteToFileFunc, callArgs *[]WriteToFileCallAr if inherited == nil { inherited = StubWriteToFileFunc(errors.New("SPY_WRITE_TO_FILE_FUNC")) } - return func(result *checker.CheckResult, startTime time.Time, outPath typedpath.RawPath) error { + return func(result *checker.CheckResult, outPath typedpath.RawPath) error { *callArgs = append(*callArgs, WriteToFileCallArgs{ CheckResult: result, - StartTime: startTime, OutPath: outPath, }) - return inherited(result, startTime, outPath) + return inherited(result, outPath) } -} \ No newline at end of file +} diff --git a/tool/unity-meta-check-junit/junit/junit_test.go b/tool/unity-meta-check-junit/junit/junit_test.go index 682f0a0..14a48d8 100644 --- a/tool/unity-meta-check-junit/junit/junit_test.go +++ b/tool/unity-meta-check-junit/junit/junit_test.go @@ -2,29 +2,78 @@ package junit import ( "bytes" + "fmt" "github.com/DeNA/unity-meta-check/unity/checker" "github.com/DeNA/unity-meta-check/util/typedpath" + "github.com/google/go-cmp/cmp" + "runtime" "testing" ) func TestWrite(t *testing.T) { - result := checker.NewCheckResult( - []typedpath.SlashPath{ - typedpath.NewSlashPathUnsafe("path/to/missing.meta"), + cases := map[string]struct { + Result *checker.CheckResult + Expected string + }{ + "empty (boundary)": { + Result: checker.NewCheckResult([]typedpath.SlashPath{}, []typedpath.SlashPath{}), + Expected: fmt.Sprintf(` + + + + + + + + +`, runtime.Version()), }, - []typedpath.SlashPath{ - typedpath.NewSlashPathUnsafe("path/to/dangling.meta"), + "both missing and dangling (easy to test)": { + Result: checker.NewCheckResult( + []typedpath.SlashPath{ + typedpath.NewSlashPathUnsafe("path/to/missing.meta"), + }, + []typedpath.SlashPath{ + typedpath.NewSlashPathUnsafe("path/to/dangling.meta"), + }, + ), + Expected: fmt.Sprintf(` + + + + + + + File or directory exists: path/to/missing But .meta is missing: path/to/missing.meta + + + + + + + + File or directory does not exist: path/to/dangling But .meta is present: path/to/dangling.meta + + + +`, runtime.Version(), runtime.Version()), }, - ) - - buf := &bytes.Buffer{} - if err := Write(result, 0, buf); err != nil { - t.Errorf("want nil, got %#v", err) - return } - if len(buf.String()) == 0 { - t.Error("want not empty string, got empty string") - return + for name, c := range cases { + t.Run(name, func(t *testing.T) { + + buf := &bytes.Buffer{} + if err := Write(c.Result, buf); err != nil { + t.Errorf("want nil, got %#v", err) + return + } + + actual := buf.String() + if actual != c.Expected { + t.Error(cmp.Diff(c.Expected, actual)) + return + } + }) } }