From 7badcbba160de40efce8882903bd7386665ed431 Mon Sep 17 00:00:00 2001 From: MalteHerrmann <42640438+MalteHerrmann@users.noreply.github.com> Date: Wed, 8 May 2024 23:53:55 +0200 Subject: [PATCH] imp(report): Refactor nested report map (#7) * refactor the nested map for markdown and HTML reports * add changelog entry * add test CI workflow * address linters --- .github/workflows/test.yml | 27 ++++++++++++++ CHANGELOG.md | 1 + report/markdown.go | 50 +++----------------------- report/nested_map.go | 73 ++++++++++++++++++++++++++++++++++++++ report/nested_map_test.go | 55 ++++++++++++++++++++++++++++ 5 files changed, 161 insertions(+), 45 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 report/nested_map.go create mode 100644 report/nested_map_test.go diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..5b4711f --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,27 @@ +name: Tests +on: + pull_request: + push: + branches: + - main +jobs: + tests: + name: Run tests + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + # Required: setup-go, for all versions v3.0.0+ of golangci-lint + - uses: actions/setup-go@v4 + with: + go-version: '1.22' + check-latest: true + - uses: actions/checkout@v4 + - uses: technote-space/get-diff-action@v6.1.2 + with: + PATTERNS: | + **/**.go + go.mod + go.sum + - run: make test + # Check only if there are differences in the source code + if: "env.GIT_DIFF" diff --git a/CHANGELOG.md b/CHANGELOG.md index 07a796e..b05c1e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements +- (report) [#7](https://github.com/MalteHerrmann/ginkgo-parser/7) Refactor nested contents for Markdown and HTML reports. - (cli) [#6](https://github.com/MalteHerrmann/ginkgo-parser/6) Use Cobra for CLI implementation. - (ci) [#5](https://github.com/MalteHerrmann/ginkgo-parser/5) Add linter to CI. diff --git a/report/markdown.go b/report/markdown.go index 9c0ac1f..970bd9e 100644 --- a/report/markdown.go +++ b/report/markdown.go @@ -3,8 +3,6 @@ package report import ( "fmt" "os" - - ginkgotypes "github.com/onsi/ginkgo/v2/types" ) // spacesPerIndentation defines the whitespace to be used per indentation level. @@ -18,7 +16,7 @@ func ConvertGinkgoReportToMarkdown(jsonFile, markdownFile string) error { return fmt.Errorf("error parsing JSON: %w", err) } - spec, err := buildSpecMap(reports) + spec, err := BuildNestedContents(reports) if err != nil { return fmt.Errorf("error building spec map: %w", err) } @@ -33,51 +31,13 @@ func ConvertGinkgoReportToMarkdown(jsonFile, markdownFile string) error { return nil } -// buildSpecMap recursively builds a map of nested maps from the given Ginkgo reports. -// This is used to convert the typed structure of the Ginkgo reports -// to a nested Markdown structure. -func buildSpecMap(reports []ginkgotypes.Report) (map[string]interface{}, error) { - spec := make(map[string]interface{}) - - for _, report := range reports { - for _, specReport := range report.SpecReports { - containerHierarchy := specReport.ContainerHierarchyTexts - - var leafNodeTypeStr string - leafNodeType := specReport.LeafNodeType - switch leafNodeType { - case ginkgotypes.NodeTypeIt: - leafNodeTypeStr = "it" - default: - return nil, fmt.Errorf("unknown leaf node type: %d", leafNodeType) - } - - leafNodeText := specReport.LeafNodeText - cleanLeafNode := leafNodeTypeStr + " " + leafNodeText - containerHierarchy = append(containerHierarchy, cleanLeafNode) - - currentSpec := spec - for _, item := range containerHierarchy { - // If the item is not yet in the map, create a new map for the underlying levels - // and add the item to the map. - if currentSpec[item] == nil { - currentSpec[item] = make(map[string]interface{}) - } - currentSpec = currentSpec[item].(map[string]interface{}) - } - } - } - - return spec, nil -} - // buildMarkdown is a recursive function to build a markdown string from the given map of nested maps. -func buildMarkdown(contents map[string]interface{}, indentationLevel int) string { +func buildMarkdown(spec NestedContents, indentationLevel int) string { markdown := "" - for key, value := range contents { - if value != nil { + for key, value := range spec.Contents { + if len(value.Contents) != 0 { markdown += fmt.Sprintf("%s- %s\n", spaces(indentationLevel), key) - markdown += buildMarkdown(value.(map[string]interface{}), indentationLevel+1) + markdown += buildMarkdown(value, indentationLevel+1) } else { markdown += fmt.Sprintf("%s- %s\n", spaces(indentationLevel), key) } diff --git a/report/nested_map.go b/report/nested_map.go new file mode 100644 index 0000000..ba33753 --- /dev/null +++ b/report/nested_map.go @@ -0,0 +1,73 @@ +package report + +import ( + "fmt" + + ginkgotypes "github.com/onsi/ginkgo/v2/types" +) + +type NestedContents struct { + HasSkipped bool + HasFailed bool + Contents map[string]NestedContents +} + +func newNestedContents() NestedContents { + return NestedContents{ + HasSkipped: false, + HasFailed: false, + Contents: make(map[string]NestedContents), + } +} + +// BuildNestedContents recursively builds a map of nested maps from the given Ginkgo reports. +// +// This is used to convert the typed structure of the Ginkgo reports +// to a nested Markdown or HTML structure. +func BuildNestedContents(reports []ginkgotypes.Report) (NestedContents, error) { + spec := newNestedContents() + + for _, report := range reports { + for _, specReport := range report.SpecReports { + containerHierarchy := specReport.ContainerHierarchyTexts + + cleanLeafNode := getCleanLeafNodeText(specReport) + containerHierarchy = append(containerHierarchy, cleanLeafNode) + + currentNestingLevel := spec + for _, item := range containerHierarchy { + // If the item is not yet in the map, create a new map for the underlying levels + // and add the item to the map. + content, found := currentNestingLevel.Contents[item] + if !found { + content = newNestedContents() + currentNestingLevel.Contents[item] = content + } + currentNestingLevel = content + } + } + } + + return spec, nil +} + +// getCleanLeafNodeText returns the clean leaf node text for the given Ginkgo spec report. +// +// Example: An It block with the text "should return the correct value" will be converted +// to "it should return the correct value". +func getCleanLeafNodeText(specReport ginkgotypes.SpecReport) string { + var leafNodeTypeStr string + + leafNodeType := specReport.LeafNodeType + switch leafNodeType { + case ginkgotypes.NodeTypeIt: + leafNodeTypeStr = "it" + default: + panic(fmt.Sprintf("unknown leaf node type: %d", leafNodeType)) + } + + leafNodeText := specReport.LeafNodeText + cleanLeafNode := leafNodeTypeStr + " " + leafNodeText + + return cleanLeafNode +} diff --git a/report/nested_map_test.go b/report/nested_map_test.go new file mode 100644 index 0000000..7540673 --- /dev/null +++ b/report/nested_map_test.go @@ -0,0 +1,55 @@ +package report_test + +import ( + "github.com/MalteHerrmann/ginkgo-parser/report" + ginkgotypes "github.com/onsi/ginkgo/v2/types" + + //nolint:revive // dot imports are okay for ginkgo + . "github.com/onsi/ginkgo/v2" + //nolint:revive // dot imports are okay for ginkgo + . "github.com/onsi/gomega" +) + +var _ = Describe("Building the nested map", Ordered, func() { + var ( + reports []ginkgotypes.Report + spec report.NestedContents + ) + + BeforeAll(func() { + // Load the example report from the testdata directory + var err error + reports, err = report.GetReportsFromJSON("testdata/example_report.json") + Expect(err).To(BeNil(), "expected no error parsing the report") + }) + + It("should build the nested map without error", func() { + var err error + spec, err = report.BuildNestedContents(reports) + Expect(err).To(BeNil(), "expected no error building the spec map") + Expect(spec).ToNot(BeNil(), "expected a non-nil spec map") + Expect(spec.HasFailed).To(BeFalse(), "expected no failed specs") + Expect(spec.HasSkipped).To(BeFalse(), "expected no skipped specs") + Expect(spec.Contents).To(HaveKey("Example"), "expected a different top-level key") + }) + + It("should have the correct hierarchy", func() { + Expect(spec.Contents["Example"].Contents).To(HaveKey("it should be true"), "expected key to be present") + Expect(spec.Contents["Example"].Contents).To(HaveKey("it should be false"), "expected key to be present") + + Expect(spec.Contents["Example"].Contents).To(HaveKey("Nested table"), "expected key to be present") + Expect(spec.Contents["Example"].Contents["Nested table"].Contents).To( + HaveKey("it 1 is 1"), + "expected key to be present", + ) + + Expect(spec.Contents["Example"].Contents).To( + HaveKey("skipped tests"), + "expected key to be present", + ) + Expect(spec.Contents["Example"].Contents["skipped tests"].Contents).To( + HaveKey("it should be skipped"), + "expected key to be present", + ) + }) +})