From 0ac65de7efb8c5c240f6b2f79ab15b4f4c443b35 Mon Sep 17 00:00:00 2001 From: Nathan Perkins Date: Tue, 30 May 2023 17:40:48 -0700 Subject: [PATCH] fix: create parent directory before report files (#1212) --- reporters/json_report.go | 13 ++++-- reporters/json_report_test.go | 75 +++++++++++++++++++++++++++++++ reporters/junit_report.go | 7 +++ reporters/junit_report_test.go | 22 +++++++++ reporters/teamcity_report.go | 4 ++ reporters/teamcity_report_test.go | 75 +++++++++++++++++++++++++++++++ 6 files changed, 193 insertions(+), 3 deletions(-) create mode 100644 reporters/json_report_test.go create mode 100644 reporters/teamcity_report_test.go diff --git a/reporters/json_report.go b/reporters/json_report.go index 7f96c450f..be506f9b4 100644 --- a/reporters/json_report.go +++ b/reporters/json_report.go @@ -4,12 +4,16 @@ import ( "encoding/json" "fmt" "os" + "path" "github.com/onsi/ginkgo/v2/types" ) -//GenerateJSONReport produces a JSON-formatted report at the passed in destination +// GenerateJSONReport produces a JSON-formatted report at the passed in destination func GenerateJSONReport(report types.Report, destination string) error { + if err := os.MkdirAll(path.Dir(destination), 0770); err != nil { + return err + } f, err := os.Create(destination) if err != nil { return err @@ -25,8 +29,8 @@ func GenerateJSONReport(report types.Report, destination string) error { return f.Close() } -//MergeJSONReports produces a single JSON-formatted report at the passed in destination by merging the JSON-formatted reports provided in sources -//It skips over reports that fail to decode but reports on them via the returned messages []string +// MergeJSONReports produces a single JSON-formatted report at the passed in destination by merging the JSON-formatted reports provided in sources +// It skips over reports that fail to decode but reports on them via the returned messages []string func MergeAndCleanupJSONReports(sources []string, destination string) ([]string, error) { messages := []string{} allReports := []types.Report{} @@ -46,6 +50,9 @@ func MergeAndCleanupJSONReports(sources []string, destination string) ([]string, allReports = append(allReports, reports...) } + if err := os.MkdirAll(path.Dir(destination), 0770); err != nil { + return messages, err + } f, err := os.Create(destination) if err != nil { return messages, err diff --git a/reporters/json_report_test.go b/reporters/json_report_test.go new file mode 100644 index 000000000..515e572d4 --- /dev/null +++ b/reporters/json_report_test.go @@ -0,0 +1,75 @@ +package reporters_test + +import ( + "fmt" + "os" + "path/filepath" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/onsi/ginkgo/v2/reporters" + "github.com/onsi/ginkgo/v2/types" +) + +var _ = Describe("JSONReport", func() { + var report types.Report + + BeforeEach(func() { + report = types.Report{ + SuiteDescription: "My Suite", + SuitePath: "/path/to/suite", + PreRunStats: types.PreRunStats{SpecsThatWillRun: 15, TotalSpecs: 20}, + SuiteConfig: types.SuiteConfig{RandomSeed: 17, ParallelTotal: 1}, + RunTime: time.Minute, + SpecReports: types.SpecReports{ + S(types.NodeTypeIt, Label("cat", "dog"), CLabels(Label("dolphin"), Label("gorilla", "cow")), CTS("A", "B"), CLS(cl0, cl1), "C", cl2, types.SpecStateTimedout, STD("some captured stdout\n"), GW("ginkgowriter\noutput\ncleanup!"), SE(types.SpecEventByStart, "a by step", cl0), + SE(types.SpecEventNodeStart, types.NodeTypeIt, "C", cl2, TL(0)), + F("failure\nmessage", cl3, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL("ginkgowriter\n"), AF(types.SpecStatePanicked, cl4, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL("ginkgowriter\noutput\n"), ForwardedPanic("the panic!"))), + SE(types.SpecEventNodeEnd, types.NodeTypeIt, "C", cl2, TL("ginkgowriter\noutput\n"), time.Microsecond*87230), + RE("a report entry", cl1, TL("ginkgowriter\noutput\n")), + RE("a hidden report entry", cl1, TL("ginkgowriter\noutput\n"), types.ReportEntryVisibilityNever), + AF(types.SpecStateFailed, "a subsequent failure", types.FailureNodeInContainer, FailureNodeLocation(cl3), types.NodeTypeAfterEach, 0, TL("ginkgowriter\noutput\ncleanup!")), + ), + S(types.NodeTypeIt, "A", cl0, STD("some captured stdout\n"), GW("some GinkgoWriter\noutput is interspersed\nhere and there\n"), + SE(types.SpecEventNodeStart, types.NodeTypeIt, "A", cl0), + PR("my progress report", LeafNodeText("A"), TL("some GinkgoWriter\n")), + SE(types.SpecEventByStart, "My Step", cl1, TL("some GinkgoWriter\n")), + RE("my entry", cl1, types.ReportEntryVisibilityFailureOrVerbose, TL("some GinkgoWriter\noutput is interspersed\n")), + RE("my hidden entry", cl1, types.ReportEntryVisibilityNever, TL("some GinkgoWriter\noutput is interspersed\n")), + SE(types.SpecEventByEnd, "My Step", cl1, time.Millisecond*200, TL("some GinkgoWriter\noutput is interspersed\n")), + SE(types.SpecEventNodeEnd, types.NodeTypeIt, "A", cl0, time.Millisecond*300, TL("some GinkgoWriter\noutput is interspersed\nhere and there\n")), + ), + S(types.NodeTypeIt, "A", cl0, types.SpecStatePending), + S(types.NodeTypeIt, "A", cl0, types.SpecStatePanicked, STD("some captured stdout\n"), + SE(types.SpecEventNodeStart, types.NodeTypeIt, "A", cl0), + F("failure\nmessage", cl1, types.FailureNodeIsLeafNode, FailureNodeLocation(cl0), types.NodeTypeIt, ForwardedPanic("the panic")), + SE(types.SpecEventNodeEnd, types.NodeTypeIt, "A", cl0, time.Millisecond*300, TL("some GinkgoWriter\noutput is interspersed\nhere and there\n")), + ), + S(types.NodeTypeBeforeSuite, "A", cl0, types.SpecStatePassed), + }, + } + }) + + Describe("when configured to write the report inside a folder", func() { + var folderPath string + var filePath string + + BeforeEach(func() { + folderPath = filepath.Join("test_outputs") + fileName := fmt.Sprintf("report-%d", GinkgoParallelProcess()) + filePath = filepath.Join(folderPath, fileName) + + Ω(reporters.GenerateJSONReport(report, filePath)).Should(Succeed()) + DeferCleanup(os.RemoveAll, folderPath) + }) + + It("creates the folder and the report file", func() { + _, err := os.Stat(folderPath) + Ω(err).Should(Succeed(), "Parent folder should be created") + _, err = os.Stat(filePath) + Ω(err).Should(Succeed(), "Report file should be created") + }) + }) +}) diff --git a/reporters/junit_report.go b/reporters/junit_report.go index 592d7f614..816042208 100644 --- a/reporters/junit_report.go +++ b/reporters/junit_report.go @@ -14,6 +14,7 @@ import ( "encoding/xml" "fmt" "os" + "path" "strings" "github.com/onsi/ginkgo/v2/config" @@ -285,6 +286,9 @@ func GenerateJUnitReportWithConfig(report types.Report, dst string, config Junit TestSuites: []JUnitTestSuite{suite}, } + if err := os.MkdirAll(path.Dir(dst), 0770); err != nil { + return err + } f, err := os.Create(dst) if err != nil { return err @@ -322,6 +326,9 @@ func MergeAndCleanupJUnitReports(sources []string, dst string) ([]string, error) mergedReport.TestSuites = append(mergedReport.TestSuites, report.TestSuites...) } + if err := os.MkdirAll(path.Dir(dst), 0770); err != nil { + return messages, err + } f, err := os.Create(dst) if err != nil { return messages, err diff --git a/reporters/junit_report_test.go b/reporters/junit_report_test.go index cbbc87bfd..c5bc51af9 100644 --- a/reporters/junit_report_test.go +++ b/reporters/junit_report_test.go @@ -4,6 +4,7 @@ import ( "encoding/xml" "fmt" "os" + "path/filepath" "time" . "github.com/onsi/ginkgo/v2" @@ -372,4 +373,25 @@ var _ = Describe("JunitReport", func() { )) }) }) + + Describe("when configured to write the report inside a folder", func() { + var folderPath string + var filePath string + + BeforeEach(func() { + folderPath = filepath.Join("test_outputs") + fileName := fmt.Sprintf("report-%d", GinkgoParallelProcess()) + filePath = filepath.Join(folderPath, fileName) + + Ω(reporters.GenerateJUnitReport(report, filePath)).Should(Succeed()) + DeferCleanup(os.RemoveAll, folderPath) + }) + + It("creates the folder and the report file", func() { + _, err := os.Stat(folderPath) + Ω(err).Should(Succeed(), "Parent folder should be created") + _, err = os.Stat(filePath) + Ω(err).Should(Succeed(), "Report file should be created") + }) + }) }) diff --git a/reporters/teamcity_report.go b/reporters/teamcity_report.go index c1863496d..e990ad82e 100644 --- a/reporters/teamcity_report.go +++ b/reporters/teamcity_report.go @@ -11,6 +11,7 @@ package reporters import ( "fmt" "os" + "path" "strings" "github.com/onsi/ginkgo/v2/types" @@ -27,6 +28,9 @@ func tcEscape(s string) string { } func GenerateTeamcityReport(report types.Report, dst string) error { + if err := os.MkdirAll(path.Dir(dst), 0770); err != nil { + return err + } f, err := os.Create(dst) if err != nil { return err diff --git a/reporters/teamcity_report_test.go b/reporters/teamcity_report_test.go new file mode 100644 index 000000000..b768f5c4c --- /dev/null +++ b/reporters/teamcity_report_test.go @@ -0,0 +1,75 @@ +package reporters_test + +import ( + "fmt" + "os" + "path/filepath" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/onsi/ginkgo/v2/reporters" + "github.com/onsi/ginkgo/v2/types" +) + +var _ = Describe("TeamCityReport", func() { + var report types.Report + + BeforeEach(func() { + report = types.Report{ + SuiteDescription: "My Suite", + SuitePath: "/path/to/suite", + PreRunStats: types.PreRunStats{SpecsThatWillRun: 15, TotalSpecs: 20}, + SuiteConfig: types.SuiteConfig{RandomSeed: 17, ParallelTotal: 1}, + RunTime: time.Minute, + SpecReports: types.SpecReports{ + S(types.NodeTypeIt, Label("cat", "dog"), CLabels(Label("dolphin"), Label("gorilla", "cow")), CTS("A", "B"), CLS(cl0, cl1), "C", cl2, types.SpecStateTimedout, STD("some captured stdout\n"), GW("ginkgowriter\noutput\ncleanup!"), SE(types.SpecEventByStart, "a by step", cl0), + SE(types.SpecEventNodeStart, types.NodeTypeIt, "C", cl2, TL(0)), + F("failure\nmessage", cl3, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL("ginkgowriter\n"), AF(types.SpecStatePanicked, cl4, types.FailureNodeIsLeafNode, FailureNodeLocation(cl2), types.NodeTypeIt, TL("ginkgowriter\noutput\n"), ForwardedPanic("the panic!"))), + SE(types.SpecEventNodeEnd, types.NodeTypeIt, "C", cl2, TL("ginkgowriter\noutput\n"), time.Microsecond*87230), + RE("a report entry", cl1, TL("ginkgowriter\noutput\n")), + RE("a hidden report entry", cl1, TL("ginkgowriter\noutput\n"), types.ReportEntryVisibilityNever), + AF(types.SpecStateFailed, "a subsequent failure", types.FailureNodeInContainer, FailureNodeLocation(cl3), types.NodeTypeAfterEach, 0, TL("ginkgowriter\noutput\ncleanup!")), + ), + S(types.NodeTypeIt, "A", cl0, STD("some captured stdout\n"), GW("some GinkgoWriter\noutput is interspersed\nhere and there\n"), + SE(types.SpecEventNodeStart, types.NodeTypeIt, "A", cl0), + PR("my progress report", LeafNodeText("A"), TL("some GinkgoWriter\n")), + SE(types.SpecEventByStart, "My Step", cl1, TL("some GinkgoWriter\n")), + RE("my entry", cl1, types.ReportEntryVisibilityFailureOrVerbose, TL("some GinkgoWriter\noutput is interspersed\n")), + RE("my hidden entry", cl1, types.ReportEntryVisibilityNever, TL("some GinkgoWriter\noutput is interspersed\n")), + SE(types.SpecEventByEnd, "My Step", cl1, time.Millisecond*200, TL("some GinkgoWriter\noutput is interspersed\n")), + SE(types.SpecEventNodeEnd, types.NodeTypeIt, "A", cl0, time.Millisecond*300, TL("some GinkgoWriter\noutput is interspersed\nhere and there\n")), + ), + S(types.NodeTypeIt, "A", cl0, types.SpecStatePending), + S(types.NodeTypeIt, "A", cl0, types.SpecStatePanicked, STD("some captured stdout\n"), + SE(types.SpecEventNodeStart, types.NodeTypeIt, "A", cl0), + F("failure\nmessage", cl1, types.FailureNodeIsLeafNode, FailureNodeLocation(cl0), types.NodeTypeIt, ForwardedPanic("the panic")), + SE(types.SpecEventNodeEnd, types.NodeTypeIt, "A", cl0, time.Millisecond*300, TL("some GinkgoWriter\noutput is interspersed\nhere and there\n")), + ), + S(types.NodeTypeBeforeSuite, "A", cl0, types.SpecStatePassed), + }, + } + }) + + Describe("when configured to write the report inside a folder", func() { + var folderPath string + var filePath string + + BeforeEach(func() { + folderPath = filepath.Join("test_outputs") + fileName := fmt.Sprintf("report-%d", GinkgoParallelProcess()) + filePath = filepath.Join(folderPath, fileName) + + Ω(reporters.GenerateTeamcityReport(report, filePath)).Should(Succeed()) + DeferCleanup(os.RemoveAll, folderPath) + }) + + It("creates the folder and the report file", func() { + _, err := os.Stat(folderPath) + Ω(err).Should(Succeed(), "Parent folder should be created") + _, err = os.Stat(filePath) + Ω(err).Should(Succeed(), "Report file should be created") + }) + }) +})