Skip to content

Commit

Permalink
Merge pull request #2 from Moon1706/develop
Browse files Browse the repository at this point in the history
Change readme
  • Loading branch information
Moon1706 authored Feb 11, 2024
2 parents 4b0644c + 9d997ef commit ec3dbd2
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 28 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ coverage.out
bin/
report.json
allure-results/
allure-results.zip
141 changes: 139 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,139 @@
# 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.

## Usage

### TestCaseID issue

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 `<name><separator><value>`. 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=<uuid>`,`suite=<suite-name>`, 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
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

You can also use the converter exactly in your Ginkgo code.

```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
```
6 changes: 6 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const (
FlagLabelSeparator = "label_separator"
FlagMandatoryLabels = "mandatory_labels"
FlagAnalyzeErrors = "analyze_errors"
FlagAutoGenID = "auto_gen_id"
FlagLogLevel = "log_level"
)

Expand Down Expand Up @@ -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)
},
}
Expand All @@ -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")
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/convert/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
32 changes: 21 additions & 11 deletions pkg/convert/report/labels.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package report

import (
"errors"
"fmt"
"strings"

Expand All @@ -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)
)
Expand All @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down
28 changes: 20 additions & 8 deletions pkg/convert/report/labels_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import (
"github.com/stretchr/testify/assert"
)

const (
testName = "test"
)

func TestLabelScraperGetAllTestCaseLabels(t *testing.T) {
var tests = []struct {
name string
Expand Down Expand Up @@ -60,25 +64,25 @@ 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)
}
}

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{{
Expand All @@ -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
Expand All @@ -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)
}
}
Expand All @@ -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)
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/convert/report/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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
}
Expand Down
5 changes: 3 additions & 2 deletions pkg/convert/transform/transform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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))
Expand Down

0 comments on commit ec3dbd2

Please sign in to comment.