From 5bf4a80774e31eabb5d4d5bda41f7cf5131479b9 Mon Sep 17 00:00:00 2001 From: Nikita Sharaev Date: Sun, 11 Feb 2024 19:30:47 +0300 Subject: [PATCH 1/3] init: modify readme --- .gitignore | 1 + README.md | 70 ++++++++++++++++++++++++- cmd/root.go | 6 +++ pkg/convert/parser/parser.go | 2 +- pkg/convert/report/labels.go | 32 +++++++---- pkg/convert/report/labels_test.go | 28 +++++++--- pkg/convert/report/report.go | 8 +-- pkg/convert/transform/transform_test.go | 5 +- 8 files changed, 124 insertions(+), 28 deletions(-) diff --git a/.gitignore b/.gitignore index a46f34a..a3af155 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ coverage.out bin/ report.json allure-results/ +allure-results.zip diff --git a/README.md b/README.md index 2c46137..a978ea7 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,68 @@ -# ginkgo2allure -Convert Ginkgo report to Allure report +# Ginkgo2Allure + +CLI and library that are used to convert Ginkgo JSON reports into Allure JSON reports. Globally used for E2E and integration testing. + +## Dependencies + +There is no need to install additional dependencies on the user's part. The project itself relies heavily on the [OzonTech library](github.com/ozontech/allure-go). + +## Installation + +Simply download the binary file and use it. + +## Usage + +### Basic + +#### CLI + +```sh +# Get Ginkgo JSON report +ginkgo -r --keep-going -p --json-report=report.json ./tests/e2e/ +# Create folder for Allure results +mkdir -p ./allure-results/ +# See help +ginkgo2allure -h +# Convert Ginkgo report to Allure reports +ginkgo2allure ./report.json ./allure-results/ +# Archive Allure results +zip -r allure-results.zip ./allure-results/ +# Send zip archive to Allure server +``` + +#### Lib + +```go +import ( + . "github.com/onsi/ginkgo/v2" + "github.com/onsi/ginkgo/v2/types" + "github.com/Moon1706/ginkgo2allure/pkg/convert" + fmngr "github.com/Moon1706/ginkgo2allure/pkg/convert/file_manager" + "github.com/Moon1706/ginkgo2allure/pkg/convert/parser" +) + +var _ = ReportAfterSuite("allure report", func(report types.Report) { + allureReports, err := convert.GinkgoToAllureReport(report, parser.NewDefaultParser, parser.Config{}) + if err != nil { + panic(err) + } + + fileManager := fmngr.NewFileManager("./allure-results") + errs = convert.PrintAllureReports(allureReports, fileManager) + if err != nil { + panic(err) + } +}) +``` + +## Build + +```sh +make bin +``` + +## Test + +```sh +make coverage +``` diff --git a/cmd/root.go b/cmd/root.go index 04938e2..b3b8df2 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -22,6 +22,7 @@ const ( FlagLabelSeparator = "label_separator" FlagMandatoryLabels = "mandatory_labels" FlagAnalyzeErrors = "analyze_errors" + FlagAutoGenID = "auto_gen_id" FlagLogLevel = "log_level" ) @@ -58,6 +59,10 @@ in a separate folder allure-results.`, if err == nil { config.TransformOpts = append(config.TransformOpts, transform.WillAnalyzeErrors(analyzeErrors, analyzeErrors)) } + autoGenID, err := cmd.Flags().GetBool(FlagAutoGenID) + if err == nil { + config.LabelsScraperOpts = append(config.LabelsScraperOpts, report.WillAutoGenerateID(autoGenID)) + } app.StartConvertion(args[0], args[1], config, logger) }, } @@ -74,6 +79,7 @@ func init() { rootCmd.Flags().String(FlagLabelSeparator, report.DefaultLabelSpliter, "labels separator") rootCmd.Flags().StringSlice(FlagMandatoryLabels, []string{report.IDLabelName}, "allure mandatory labels") rootCmd.Flags().Bool(FlagAnalyzeErrors, true, "will analyze test fails in Ginkgo report or not") + rootCmd.Flags().Bool(FlagAutoGenID, report.DefaultAutoGenerateID, "will auto generate UUID for Ginkgo test or not") rootCmd.PersistentFlags().StringVarP(&logLevel, FlagLogLevel, "l", "info", "log level") } diff --git a/pkg/convert/parser/parser.go b/pkg/convert/parser/parser.go index 8b31bba..bb6855a 100644 --- a/pkg/convert/parser/parser.go +++ b/pkg/convert/parser/parser.go @@ -43,7 +43,7 @@ func NewParser(specReport types.SpecReport, transformer Transformer, func NewDefaultParser(specReport types.SpecReport, config Config) (*Parser, error) { t := transform.NewTransform(config.TransformOpts...) - ls := report.NewLabelScraper(specReport.LeafNodeLabels, config.LabelsScraperOpts...) + ls := report.NewLabelScraper(specReport.LeafNodeText, specReport.LeafNodeLabels, config.LabelsScraperOpts...) r := report.NewReport(specReport, config.ReportOpts...) return NewParser(specReport, t, ls, r), nil } diff --git a/pkg/convert/report/labels.go b/pkg/convert/report/labels.go index d6a6076..c2b3b9d 100644 --- a/pkg/convert/report/labels.go +++ b/pkg/convert/report/labels.go @@ -1,7 +1,6 @@ package report import ( - "errors" "fmt" "strings" @@ -16,23 +15,22 @@ const ( IDLabelName = "id" DescriptionLabelName = "description" - DefaultLabelSpliter = "=" - DefaultEpic = "base" - DefaultSuiteName = "" + DefaultLabelSpliter = "=" + DefaultEpic = "base" + DefaultSuiteName = "" + DefaultAutoGenerateID = false CorrectCountLabelsParts = 2 ) -var ( - ErrNoID = errors.New("test doesn't contain allure id in labels") -) - type ( DefaultLabelsScraper struct { + testName string epic string suiteName string testCaseLabels map[string]string labelSpliter string + autogenID bool } LabelsScraperOpt func(o *DefaultLabelsScraper) ) @@ -55,11 +53,20 @@ func WithSuiteName(suiteName string) LabelsScraperOpt { } } -func NewLabelScraper(leafNodeLabels []string, scraperOptions ...LabelsScraperOpt) *DefaultLabelsScraper { +func WillAutoGenerateID(autogen bool) LabelsScraperOpt { + return func(o *DefaultLabelsScraper) { + o.autogenID = autogen + } +} + +func NewLabelScraper(leafNodeText string, leafNodeLabels []string, + scraperOptions ...LabelsScraperOpt) *DefaultLabelsScraper { scraper := &DefaultLabelsScraper{ + testName: leafNodeText, epic: DefaultEpic, suiteName: DefaultSuiteName, labelSpliter: DefaultLabelSpliter, + autogenID: DefaultAutoGenerateID, } for _, o := range scraperOptions { o(scraper) @@ -110,10 +117,13 @@ func (ls *DefaultLabelsScraper) CreateAllureLabels() (labels []*allure.Label) { return labels } -func (ls *DefaultLabelsScraper) GetID() (uuid.UUID, error) { +func (ls *DefaultLabelsScraper) GetID(defaultID uuid.UUID) (uuid.UUID, error) { id, ok := ls.testCaseLabels[IDLabelName] if !ok { - return uuid.UUID{}, ErrNoID + if ls.autogenID { + return defaultID, nil + } + return uuid.UUID{}, fmt.Errorf("test with name `%s` doesn't contain UUID", ls.testName) } allureUUID, err := uuid.Parse(id) if err != nil { diff --git a/pkg/convert/report/labels_test.go b/pkg/convert/report/labels_test.go index deee68e..d7e1aac 100644 --- a/pkg/convert/report/labels_test.go +++ b/pkg/convert/report/labels_test.go @@ -10,6 +10,10 @@ import ( "github.com/stretchr/testify/assert" ) +const ( + testName = "test" +) + func TestLabelScraperGetAllTestCaseLabels(t *testing.T) { var tests = []struct { name string @@ -60,7 +64,7 @@ func TestLabelScraperGetAllTestCaseLabels(t *testing.T) { }} for _, tt := range tests { - lb := report.NewLabelScraper(tt.leafNodeLabels, tt.scraperOpt...) + lb := report.NewLabelScraper(testName, tt.leafNodeLabels, tt.scraperOpt...) tc := lb.GetTestCaseLabels() assert.Equal(t, tc, tt.testCaseLabels, tt.name) } @@ -68,17 +72,17 @@ func TestLabelScraperGetAllTestCaseLabels(t *testing.T) { func TestLabelScraperCheckMandatoryLabels(t *testing.T) { mandatoryLabelsLabels := []string{"correct"} - lb := report.NewLabelScraper([]string{fmt.Sprintf("correct%slabel", report.DefaultLabelSpliter)}) + lb := report.NewLabelScraper(testName, []string{fmt.Sprintf("correct%slabel", report.DefaultLabelSpliter)}) err := lb.CheckMandatoryLabels(mandatoryLabelsLabels) assert.Empty(t, err, "correct label was found in madatory labels") - lb = report.NewLabelScraper([]string{fmt.Sprintf("incorrect%slabel", report.DefaultLabelSpliter)}) + lb = report.NewLabelScraper(testName, []string{fmt.Sprintf("incorrect%slabel", report.DefaultLabelSpliter)}) err = lb.CheckMandatoryLabels(mandatoryLabelsLabels) assert.Error(t, err, "lables wasn't found in madatory labels") } func TestLabelScraperCreateAllureLabels(t *testing.T) { - lb := report.NewLabelScraper([]string{fmt.Sprintf("correct%slabel", report.DefaultLabelSpliter)}, + lb := report.NewLabelScraper(testName, []string{fmt.Sprintf("correct%slabel", report.DefaultLabelSpliter)}, report.WithEpic("")) allureLabels := lb.CreateAllureLabels() assert.Equal(t, []*allure.Label{{ @@ -88,8 +92,10 @@ func TestLabelScraperCreateAllureLabels(t *testing.T) { } func TestLabelGetID(t *testing.T) { + defaultID := uuid.MustParse("f67b2057-fc82-4dd7-bbd5-9d178aab9901") var tests = []struct { name string + autoGen bool labelName string labelValue string id uuid.UUID @@ -108,12 +114,18 @@ func TestLabelGetID(t *testing.T) { labelName: report.IDLabelName, labelValue: "ad7583dc-0e3d-4640-a020-567452d84886", id: uuid.MustParse("ad7583dc-0e3d-4640-a020-567452d84886"), + }, { + name: "autogen label value", + labelName: "", + labelValue: "", + autoGen: true, + id: defaultID, }} for _, tt := range tests { - lb := report.NewLabelScraper([]string{fmt.Sprintf("%s%s%s", tt.labelName, - report.DefaultLabelSpliter, tt.labelValue)}) - id, _ := lb.GetID() + lb := report.NewLabelScraper(testName, []string{fmt.Sprintf("%s%s%s", tt.labelName, + report.DefaultLabelSpliter, tt.labelValue)}, report.WillAutoGenerateID(tt.autoGen)) + id, _ := lb.GetID(defaultID) assert.Equal(t, tt.id, id, tt.name) } } @@ -138,7 +150,7 @@ func TestLabelGetDescription(t *testing.T) { }} for _, tt := range tests { - lb := report.NewLabelScraper([]string{tt.descriptionLabel}) + lb := report.NewLabelScraper(testName, []string{tt.descriptionLabel}) description := lb.GetDescription(tt.defaultDescription) assert.Equal(t, tt.description, description, tt.name) } diff --git a/pkg/convert/report/report.go b/pkg/convert/report/report.go index 22305b1..67e46ec 100644 --- a/pkg/convert/report/report.go +++ b/pkg/convert/report/report.go @@ -20,7 +20,7 @@ type ( LabelScraper interface { CheckMandatoryLabels([]string) error CreateAllureLabels() []*allure.Label - GetID() (uuid.UUID, error) + GetID(uuid.UUID) (uuid.UUID, error) GetDescription(string) string } Opt func(o *DefaultReport) @@ -37,7 +37,7 @@ func NewReport(specReport types.SpecReport, opts ...Opt) *DefaultReport { mandatoryLabels: []string{}, specReport: specReport, } - ls := NewLabelScraper(specReport.LeafNodeLabels) + ls := NewLabelScraper(specReport.LeafNodeText, specReport.LeafNodeLabels) r.SetLabelsScraper(ls) for _, o := range opts { o(r) @@ -51,11 +51,11 @@ func (r *DefaultReport) SetLabelsScraper(ls LabelScraper) { func (r *DefaultReport) GenerateAllureReport(steps []*allure.Step) (allure.Result, error) { emptyReport := allure.Result{} - err := r.labelScraper.CheckMandatoryLabels(r.mandatoryLabels) + id, err := r.labelScraper.GetID(uuid.New()) if err != nil { return emptyReport, err } - id, err := r.labelScraper.GetID() + err = r.labelScraper.CheckMandatoryLabels(r.mandatoryLabels) if err != nil { return emptyReport, err } diff --git a/pkg/convert/transform/transform_test.go b/pkg/convert/transform/transform_test.go index 85d1eed..2ee4d60 100644 --- a/pkg/convert/transform/transform_test.go +++ b/pkg/convert/transform/transform_test.go @@ -9,6 +9,7 @@ import ( "github.com/Moon1706/ginkgo2allure/pkg/convert/report" "github.com/Moon1706/ginkgo2allure/pkg/convert/transform" + "github.com/google/uuid" "github.com/onsi/ginkgo/v2/types" "github.com/ozontech/allure-go/pkg/allure" "github.com/stretchr/testify/assert" @@ -118,8 +119,8 @@ func TestTransformAnalyzeEvents(t *testing.T) { } steps := tr.GetAllureSteps() - ls := report.NewLabelScraper(ginkgoSpecReport.LeafNodeLabels) - id, err := ls.GetID() + ls := report.NewLabelScraper(ginkgoSpecReport.LeafNodeText, ginkgoSpecReport.LeafNodeLabels) + id, err := ls.GetID(uuid.New()) assert.Empty(t, err, "id got correctly") allureReportPath := filepath.Join(allureReportFolderPath, tt.fileName, fmt.Sprintf("%s-result.json", id)) From df4ac3073d6fbeff391168f9f54fd8752cfcbf40 Mon Sep 17 00:00:00 2001 From: Nikita Sharaev Date: Sun, 11 Feb 2024 20:52:14 +0300 Subject: [PATCH 2/3] init: modify readme 2 --- README.md | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a978ea7..bdfc7ab 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,86 @@ Simply download the binary file and use it. ## Usage -### Basic +### TestCaseID issue -#### CLI +For production usage, it is really important to avoid test case duplication on the Allure server. For these goals, Allure realises the `TestCaseID` mechanism. The main idea here is to **ALWAYS** use the same test cases with an identical ID. The format of `TestCaseID` is an MD5 hash. So, for implementation this behaviour, I decided to attach to each Ginkgo test `id` label with UUIDv4. This `id` will transform into `TestCaseID` and will allow us to match the current test to the previous one in Allure and also automatically sort them by start/stop runtime. + +For you, it means that you **MUST** add for each test label `id`. Example: + +```go +It("test", Label("id=b1f3572c-f1f0-4001-a4b6-97625206d9f9"), func() { + ... +}) +``` + +For test aims, you can use the flag `--auto_gen_id`, which automatically generates a UUID for tests that don't have `id` in labels. Keep in mind that it's not a consistent UUID, and it will change in the next regeneration. + +### Allure labels + +#### General + +General label format is ``. If you want, you can change separator used flag `--label_separator`. + +You **HAVE TO** understand that all labels that were added in `It` labels will be checked in a loop, and if theirs can be split by a separator on **2** parts, they will be added to the final Allure labels. That feature allows you to add your own labels, like `owner`, `feature`, `story`, etc. Example `e2e_test.go`: + +```go +It("test", Label("id=b1f3572c-f1f0-4001-a4b6-97625206d9f9", "test=test"), func() { + ... +}) +``` + +Result `b1f3572c-f1f0-4001-a4b6-97625206d9f9-result.json` +```json +{ + ... + "labels": [ + { + "name": "id", + "value": "b1f3572c-f1f0-4001-a4b6-97625206d9f9" + }, + { + "name": "test", + "value": "test" + } + ], + ... +} +``` + +#### Default labels + +By default, each Allure test adds the labels `id=`,`suite=`, and `epic=base`. Label `epic` you can change with 2 options: +1. Define the `It` test (high priority). +2. Add the flag `--epic` in CLI (low priority). + +#### Mandatory labels + +For your own goals, you can define a list of Ginkgo labels (flag `--mandatory_labels`), which must be in **ALL** `It` tests, like `featur`,`story`, etc. By default, it's only an `id`. + +### Analyse the error issue + +In general, Ginkgo starts like that. + +```go +import ( + "testing" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestRunner(t *testing.T) { + suiteConfig, reporterConfig := GinkgoConfiguration() + + RegisterFailHandler(Fail) + RunSpecs(t, SuiteName, suiteConfig, reporterConfig) +} +``` + +As you can see, we use the basic Ginkgo `Fail` handler, which indeed doesn't have a lot of really important information for us (for instance, expect and actual values in a Gomega assert function). However, for compatibility, it was decided to stay with this handler and parse explicit trace output. It's a bad approach, but it will close most test cases. For you, it means that if you find any problems with this functionality, please inform me in Issue and disable it with the flag `--analyze_errors`. + +### CLI + +Now, after reading [TestCaseID issue](#TestCaseID_issue) you grasp how to prepare your code for conversion. Below is a basic CLI run. ```sh # Get Ginkgo JSON report @@ -30,7 +107,9 @@ zip -r allure-results.zip ./allure-results/ # Send zip archive to Allure server ``` -#### Lib +### Lib + +You can also use the converter exactly in your Ginkgo code. ```go import ( From 9d997ef390a9d792707d2891efb9dc58f2a5c415 Mon Sep 17 00:00:00 2001 From: Nikita Sharaev Date: Sun, 11 Feb 2024 20:53:07 +0300 Subject: [PATCH 3/3] init: modify readme 3 --- README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.md b/README.md index bdfc7ab..c8e6013 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,6 @@ CLI and library that are used to convert Ginkgo JSON reports into Allure JSON reports. Globally used for E2E and integration testing. -## Dependencies - -There is no need to install additional dependencies on the user's part. The project itself relies heavily on the [OzonTech library](github.com/ozontech/allure-go). - -## Installation - -Simply download the binary file and use it. - ## Usage ### TestCaseID issue