diff --git a/internal/gojunitreport/go-junit-report.go b/internal/gojunitreport/go-junit-report.go new file mode 100644 index 0000000..11c6ce2 --- /dev/null +++ b/internal/gojunitreport/go-junit-report.go @@ -0,0 +1,98 @@ +package gojunitreport + +import ( + "encoding/json" + "encoding/xml" + "fmt" + "io" + "os" + "time" + + "github.com/jstemmer/go-junit-report/v2/gtr" + "github.com/jstemmer/go-junit-report/v2/junit" + "github.com/jstemmer/go-junit-report/v2/parser/gotest" +) + +type parser interface { + Parse(r io.Reader) (gtr.Report, error) + Events() []gotest.Event +} + +// Config contains the go-junit-report command configuration. +type Config struct { + Parser string + Hostname string + PackageName string + SkipXMLHeader bool + Properties map[string]string + TimestampFunc func() time.Time + + // For debugging + PrintEvents bool +} + +// Run runs the go-junit-report command and returns the generated report. +func (c Config) Run(input io.Reader, output io.Writer) (*gtr.Report, error) { + var p parser + switch c.Parser { + case "gotest": + p = gotest.NewParser( + gotest.PackageName(c.PackageName), + gotest.TimestampFunc(c.TimestampFunc), + ) + case "gojson": + p = gotest.NewJSONParser( + gotest.PackageName(c.PackageName), + gotest.TimestampFunc(c.TimestampFunc), + ) + default: + return nil, fmt.Errorf("invalid parser: %s", c.Parser) + } + + report, err := p.Parse(input) + if err != nil { + return nil, fmt.Errorf("error parsing input: %w", err) + } + + if c.PrintEvents { + enc := json.NewEncoder(os.Stderr) + for _, event := range p.Events() { + if err := enc.Encode(event); err != nil { + return nil, err + } + } + } + + for i := range report.Packages { + for k, v := range c.Properties { + report.Packages[i].SetProperty(k, v) + } + } + + if err = c.writeXML(output, report); err != nil { + return nil, err + } + return &report, nil +} + +func (c Config) writeXML(w io.Writer, report gtr.Report) error { + testsuites := junit.CreateFromReport(report, c.Hostname) + + if !c.SkipXMLHeader { + _, err := fmt.Fprintf(w, xml.Header) + if err != nil { + return err + } + } + + enc := xml.NewEncoder(w) + enc.Indent("", "\t") + if err := enc.Encode(testsuites); err != nil { + return err + } + if err := enc.Flush(); err != nil { + return err + } + _, err := fmt.Fprintf(w, "\n") + return err +} diff --git a/go-junit-report_test.go b/internal/gojunitreport/go-junit-report_test.go similarity index 57% rename from go-junit-report_test.go rename to internal/gojunitreport/go-junit-report_test.go index cd8065c..bbe1edd 100644 --- a/go-junit-report_test.go +++ b/internal/gojunitreport/go-junit-report_test.go @@ -1,4 +1,4 @@ -package main +package gojunitreport import ( "bytes" @@ -12,25 +12,17 @@ import ( "testing" "time" - "github.com/jstemmer/go-junit-report/v2/junit" - "github.com/jstemmer/go-junit-report/v2/parser/gotest" - "github.com/google/go-cmp/cmp" ) -const testDataDir = "testdata/" +const testDataDir = "../../testdata/" var matchTest = flag.String("match", "", "only test testdata matching this pattern") -type TestConfig struct { - noXMLHeader bool - packageName string -} - -var testConfigs = map[int]TestConfig{ - 5: {noXMLHeader: true}, - 6: {noXMLHeader: true}, - 7: {packageName: "test/package"}, +var testConfigs = map[int]Config{ + 5: {SkipXMLHeader: true}, + 6: {SkipXMLHeader: true}, + 7: {PackageName: "test/package"}, } func TestRun(t *testing.T) { @@ -58,7 +50,7 @@ func TestRun(t *testing.T) { } } -func testRun(inputFile, reportFile string, config TestConfig, t *testing.T) { +func testRun(inputFile, reportFile string, config Config, t *testing.T) { input, err := os.Open(inputFile) if err != nil { t.Fatalf("error opening input file: %v", err) @@ -72,33 +64,19 @@ func testRun(inputFile, reportFile string, config TestConfig, t *testing.T) { t.Fatalf("error loading report file: %v", err) } - options := []gotest.Option{ - gotest.PackageName(config.packageName), - gotest.TimestampFunc(func() time.Time { - return time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) - }), - } - - var parser Parser + config.Parser = "gotest" if strings.HasSuffix(inputFile, ".gojson.txt") { - parser = gotest.NewJSONParser(options...) - } else { - parser = gotest.NewParser(options...) + config.Parser = "gojson" } - - report, err := parser.Parse(input) - if err != nil { - t.Fatal(err) + config.Hostname = "hostname" + config.Properties = map[string]string{"go.version": "1.0"} + config.TimestampFunc = func() time.Time { + return time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) } - for i := range report.Packages { - report.Packages[i].SetProperty("go.version", "1.0") - } - testsuites := junit.CreateFromReport(report, "hostname") - var output bytes.Buffer - if err := writeXML(&output, testsuites, config.noXMLHeader); err != nil { - t.Fatalf("error writing XML: %v", err) + if _, err := config.Run(input, &output); err != nil { + t.Fatal(err) } if diff := cmp.Diff(output.String(), string(wantReport)); diff != "" { @@ -106,16 +84,16 @@ func testRun(inputFile, reportFile string, config TestConfig, t *testing.T) { } } -func testFileConfig(filename string) (conf TestConfig, reportFile string, err error) { +func testFileConfig(filename string) (config Config, reportFile string, err error) { var prefix string if idx := strings.IndexByte(filename, '-'); idx < 0 { - return conf, "", fmt.Errorf("testdata file does not contain a dash (-); expected name `{id}-{name}.txt` got `%s`", filename) + return config, "", fmt.Errorf("testdata file does not contain a dash (-); expected name `{id}-{name}.txt` got `%s`", filename) } else { prefix = filename[:idx] } id, err := strconv.Atoi(prefix) if err != nil { - return conf, "", fmt.Errorf("testdata file did not start with a valid number: %w", err) + return config, "", fmt.Errorf("testdata file did not start with a valid number: %w", err) } return testConfigs[id], fmt.Sprintf("%s-report.xml", prefix), nil } diff --git a/go-junit-report.go b/main.go similarity index 61% rename from go-junit-report.go rename to main.go index 23dc1a5..3080a30 100644 --- a/go-junit-report.go +++ b/main.go @@ -1,17 +1,13 @@ package main import ( - "encoding/json" - "encoding/xml" "flag" "fmt" "io" "os" "strings" - "github.com/jstemmer/go-junit-report/v2/gtr" - "github.com/jstemmer/go-junit-report/v2/junit" - "github.com/jstemmer/go-junit-report/v2/parser/gotest" + "github.com/jstemmer/go-junit-report/v2/internal/gojunitreport" ) var ( @@ -29,7 +25,7 @@ var ( output = flag.String("out", "", "write XML report to `file`") iocopy = flag.Bool("iocopy", false, "copy input to stdout; can only be used in conjunction with -out") properties = make(keyValueFlag) - inputParser = flag.String("parser", "gotest", "set input parser: gotest, gojson") + parser = flag.String("parser", "gotest", "set input parser: gotest, gojson") // debug flags printEvents = flag.Bool("debug.print-events", false, "print events generated by the go test parser") @@ -39,8 +35,9 @@ var ( ) func main() { - flag.Var(&properties, "prop", "add property to generated report; properties should be specified as `key=value`") + flag.Var(&properties, "p", "add `key=value` property to generated report; repeat this flag to add multiple properties.") flag.Parse() + if *iocopy && *output == "" { exitf("you must specify an output file with -out when using -iocopy") } @@ -51,7 +48,7 @@ func main() { } if *goVersionFlag != "" { - fmt.Fprintf(os.Stderr, "the -go-version flag is deprecated and will be removed in the future.\n") + fmt.Fprintf(os.Stderr, "the -go-version flag is deprecated and will be removed in the future, use the -p flag instead.\n") properties["go.version"] = *goVersionFlag } @@ -62,7 +59,6 @@ func main() { exitf("") } - // Read input var in io.Reader = os.Stdin if *input != "" { f, err := os.Open(*input) @@ -73,44 +69,6 @@ func main() { in = f } - if *iocopy { - in = io.TeeReader(in, os.Stdout) - } - - var parser Parser - switch *inputParser { - case "gotest": - parser = gotest.NewParser(gotest.PackageName(*packageName)) - case "gojson": - parser = gotest.NewJSONParser(gotest.PackageName(*packageName)) - default: - fmt.Fprintf(os.Stderr, "invalid parser: %s\n", *inputParser) - flag.Usage() - os.Exit(1) - } - - report, err := parser.Parse(in) - if err != nil { - exitf("error parsing input: %s\n", err) - } - - if *printEvents { - enc := json.NewEncoder(os.Stderr) - for _, event := range parser.Events() { - if err := enc.Encode(event); err != nil { - exitf("error printing events: %v\n", err) - } - } - } - for i := range report.Packages { - for k, v := range properties { - report.Packages[i].SetProperty(k, v) - } - } - - hostname, _ := os.Hostname() // ignore error - testsuites := junit.CreateFromReport(report, hostname) - var out io.Writer = os.Stdout if *output != "" { f, err := os.Create(*output) @@ -121,8 +79,23 @@ func main() { out = f } - if err := writeXML(out, testsuites, *noXMLHeader); err != nil { - exitf("error writing XML: %v", err) + if *iocopy { + in = io.TeeReader(in, os.Stdout) + } + + hostname, _ := os.Hostname() // ignore error + + config := gojunitreport.Config{ + Parser: *parser, + Hostname: hostname, + PackageName: *packageName, + SkipXMLHeader: *noXMLHeader, + Properties: properties, + PrintEvents: *printEvents, + } + report, err := config.Run(in, out) + if err != nil { + exitf("error: %v\n", err) } if *setExitCode && !report.IsSuccessful() { @@ -130,25 +103,6 @@ func main() { } } -func writeXML(w io.Writer, testsuites junit.Testsuites, skipHeader bool) error { - if !skipHeader { - _, err := fmt.Fprintf(w, xml.Header) - if err != nil { - return err - } - } - enc := xml.NewEncoder(w) - enc.Indent("", "\t") - if err := enc.Encode(testsuites); err != nil { - return err - } - if err := enc.Flush(); err != nil { - return err - } - _, err := fmt.Fprintf(w, "\n") - return err -} - func exitf(msg string, args ...interface{}) { if msg != "" { fmt.Fprintf(os.Stderr, msg+"\n", args...) @@ -178,8 +132,3 @@ func (f *keyValueFlag) Set(value string) error { (*f)[k] = v return nil } - -type Parser interface { - Parse(r io.Reader) (gtr.Report, error) - Events() []gotest.Event -} diff --git a/testdata/generate-golden.go b/testdata/generate-golden-reports.go similarity index 53% rename from testdata/generate-golden.go rename to testdata/generate-golden-reports.go index 95f1cae..68d7411 100644 --- a/testdata/generate-golden.go +++ b/testdata/generate-golden-reports.go @@ -1,9 +1,8 @@ -//go:generate go run generate-golden.go -w +//go:generate go run generate-golden-reports.go -w package main import ( - "encoding/xml" "flag" "fmt" "io" @@ -12,22 +11,15 @@ import ( "strings" "time" - "github.com/jstemmer/go-junit-report/v2/gtr" - "github.com/jstemmer/go-junit-report/v2/junit" - "github.com/jstemmer/go-junit-report/v2/parser/gotest" + "github.com/jstemmer/go-junit-report/v2/internal/gojunitreport" ) var verbose bool -type Settings struct { - skipXMLHeader bool - packageName string -} - -var fileSettings = map[string]Settings{ - "005-no_xml_header.txt": {skipXMLHeader: true}, - "006-mixed.txt": {skipXMLHeader: true}, - "007-compiled_test.txt": {packageName: "test/package"}, +var configs = map[string]gojunitreport.Config{ + "005-no_xml_header.txt": {SkipXMLHeader: true}, + "006-mixed.txt": {SkipXMLHeader: true}, + "007-compiled_test.txt": {PackageName: "test/package"}, } func main() { @@ -102,53 +94,18 @@ func createReportFromInput(inputFile, outputFile string, write bool) error { out = f } - settings := fileSettings[inputFile] - - options := []gotest.Option{ - gotest.PackageName(settings.packageName), - gotest.TimestampFunc(func() time.Time { - return time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) - }), - } - - var parser Parser + config := configs[inputFile] + config.Parser = "gotest" if strings.HasSuffix(inputFile, ".gojson.txt") { - parser = gotest.NewJSONParser(options...) - } else { - parser = gotest.NewParser(options...) + config.Parser = "gojson" } - report, err := parser.Parse(in) - if err != nil { - return err + config.Hostname = "hostname" + config.TimestampFunc = func() time.Time { + return time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) } - return writeReport(report, out, settings) -} + config.Properties = map[string]string{"go.version": "1.0"} -type Parser interface { - Parse(r io.Reader) (gtr.Report, error) -} - -func writeReport(report gtr.Report, out io.Writer, settings Settings) error { - for i := range report.Packages { - report.Packages[i].SetProperty("go.version", "1.0") - } - testsuites := junit.CreateFromReport(report, "hostname") - - if !settings.skipXMLHeader { - if _, err := fmt.Fprintf(out, xml.Header); err != nil { - return err - } - } - - enc := xml.NewEncoder(out) - enc.Indent("", "\t") - if err := enc.Encode(testsuites); err != nil { - return err - } - if err := enc.Flush(); err != nil { - return err - } - _, err := fmt.Fprintf(out, "\n") + _, err = config.Run(in, out) return err }