Skip to content

Implement retry flaky steps #684

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open

Implement retry flaky steps #684

wants to merge 4 commits into from

Conversation

nhv96
Copy link

@nhv96 nhv96 commented Mar 29, 2025

🤔 What's changed?

  • Added support for retrying flaky steps by returning ErrRetry
  • Added CLI option for MaxRetries attempts
  • Added example of the feature

image

⚡️ What's your motivation?

Discussed here.

🏷️ What kind of change is this?

  • ⚡ New feature (non-breaking change which adds new behaviour)

♻️ Anything particular you want feedback on?

📋 Checklist:

  • I agree to respect and uphold the Cucumber Community Code of Conduct
  • I've changed the behaviour of the code
    • I have added/updated tests to cover my changes.
  • My change requires a change to the documentation.
    • I have updated the documentation accordingly.
  • Users should know about my change
    • I have added an entry to the "Unreleased" section of the CHANGELOG, linking to this pull request.

This text was originally generated from a template, then edited by hand. You can modify the template here.

nhv96 added 2 commits March 29, 2025 20:49
* retry failed pickles

* revert some changes

* use example steps from cck

* update example

* implement retry with godog.ErrRetry

* unit test for retry flaky

* Update flaky feature description

* add --retry to CLI option

* update test

* update test for --retry cli option

* update test

* handle err without printing retry steps

* storage methods to upsert step result

* rename wording

* revert change

* update feature test (will remove this feature)

* revert unused files

* revert unused files

* improve readability

* remove unused code

* cleanup
* retry failed pickles

* revert some changes

* use example steps from cck

* update example

* implement retry with godog.ErrRetry

* unit test for retry flaky

* Update flaky feature description

* add --retry to CLI option

* update test

* update test for --retry cli option

* update test

* handle err without printing retry steps

* storage methods to upsert step result

* rename wording

* revert change

* update feature test (will remove this feature)

* revert unused files

* revert unused files

* improve readability

* remove unused code

* cleanup

* update changelog
Copy link
Contributor

@luke-hill luke-hill left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor fixes suggested. However a larger question is, are you seeking conformance with standard cucumber execution / cck conformance (I know you might not be there yet).

The reason why, is that a lot of your implementation details surrounding the specific status is unique and not what all standard implementors do

sc.Step(`^a step that passes the second time`, func(ctx context.Context) (context.Context, error) {
secondTimePass++
if secondTimePass < 2 {
return ctx, fmt.Errorf("unexpected network connection, %w", godog.ErrRetry)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For stuff that will inevitably become part of living documentation I would make this more informative than obfuscating.

See https://github.com/cucumber/compatibility-kit/blob/main/devkit/samples/retry/retry.feature.ts (Which in itself probably could be a bit better)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I think I should just return godog.ErrRetry instead.

return ctx, nil
})

fifthTimePass := 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be removed as we want this always to fail. The conditional below should also be removed. It should always return the formatted error

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I agree, this folder is for examples, I should not add this case here (this case is supposed to be in unit test over here only).

@@ -46,4 +46,6 @@ built-in formatters are:
specify SEED to reproduce the shuffling from a previous run
--random=5738`)
flagSet.Lookup(prefix + "random").NoOptDefVal = "-1"

flagSet.IntVar(&opts.MaxRetries, prefix+"retry", opts.MaxRetries, "retry n times when a step encounter error")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
flagSet.IntVar(&opts.MaxRetries, prefix+"retry", opts.MaxRetries, "retry n times when a step encounter error")
flagSet.IntVar(&opts.MaxRetries, prefix+"retry", opts.MaxRetries, "Specify the number of times to retry failing tests (default: 0)")

Make sure this is defaulting to 0

If you want to see where the original config parser is set with all of the messages check here: https://github.com/cucumber/cucumber-ruby/blob/main/lib/cucumber/cli/options.rb

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it will be default 0. I'll update the message.

@@ -80,6 +80,9 @@ type Options struct {

// ShowHelp enables suite to show CLI flags usage help and exit.
ShowHelp bool

// MaxRetries is used to retry the number of time when a step is failed. Default is 0 retry.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// MaxRetries is used to retry the number of time when a step is failed. Default is 0 retry.
// MaxRetries is the number of times you can retry failing tests. Default is 0.


fifthTimePass := 0
ctx.Step(`^a step that always fails`, func(ctx context.Context) (context.Context, error) {
fifthTimePass++
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like duplicate info from above. Is there a way to avoid defining this twice.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I forgot, this file is unit tests for the framework itself. So this case here is to cover the case step retried all attempts but still fail.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So based on what I've seen earlier, this should ideally live in a separate folder called compatibility and you should look towards CCK conformance imo.

Maybe come and discuss this on discord. See what the desired avenue is in the go community

@nhv96
Copy link
Author

nhv96 commented Apr 12, 2025

Some minor fixes suggested. However a larger question is, are you seeking conformance with standard cucumber execution / cck conformance (I know you might not be there yet).

The reason why, is that a lot of your implementation details surrounding the specific status is unique and not what all standard implementors do

@luke-hill I agree that this implementation works for the specific status godog.ErrRetry, in our discussion thread we also proposed other approaches as well such as using tags @flaky in the feature files.

Do you mean that other standard implementors will also implement such approaches as well? If it is the case, then this PR is to implement the approach using RetryErr, then the following PRs can be to add other approaches.

So I do want to conform the cucumber standard for implementations, but I don't want to make so many different changes in one PR 😄

@nhv96
Copy link
Author

nhv96 commented May 17, 2025

hi @vearutop @luke-hill if you have time, please help me review this PR.

Copy link
Contributor

@luke-hill luke-hill left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done another pass at a review but at this point I think the "goal" needs to be understood better with regards to conformance / non-conformance

Feature: Godog should be able to retry flaky tests
In order to help Go developers deal with flaky tests
As a test suite
I need to be able to return godog.Err to mark which steps should be retry
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more a question, but usually in feature files if it's a technical term you're referencing you'd use backticks or something else.

But maybe you don't do that here 🤷‍♂️

@@ -0,0 +1,19 @@
Feature: Godog should be able to retry flaky tests
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whilst the principle here is good, I'd lean more on you implementing the CCK for things like this, so that you can use the conformance element there

@@ -80,6 +80,9 @@ type Options struct {

// ShowHelp enables suite to show CLI flags usage help and exit.
ShowHelp bool

// MaxRetries is the number of times you can retry failing tests. Default is 0 retry.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// MaxRetries is the number of times you can retry failing tests. Default is 0 retry.
// MaxRetries is the number of times you can retry failing tests. Default is 0 retries.

@@ -37,6 +37,7 @@ var (
undefined = models.Undefined
pending = models.Pending
ambiguous = models.Ambiguous
retry = models.Retry
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whilst this is godog, and you are allowed to do your own thing, if your ultimate goal is cucumber conformance, this would need to go.

We have a concept called "flaky" which is something that passed after retries, maybe this can be renamed or adapted I don't know

@@ -74,6 +79,8 @@ const (
Pending
// Ambiguous ...
Ambiguous
// Retry ...
Retry
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might / should be altered too

@@ -105,6 +112,8 @@ func (st StepResultStatus) String() string {
return "pending"
case Ambiguous:
return "ambiguous"
case Retry:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here also

@@ -336,3 +345,15 @@ func (s *Storage) mustGet(table, index string, args ...interface{}) memdb.Result

return it
}

func (s *Storage) first(table, index string, args ...interface{}) interface{} {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am writing this comment to let you know I don't understand this bit. But I'm assuming it does something important 😅


fifthTimePass := 0
ctx.Step(`^a step that always fails`, func(ctx context.Context) (context.Context, error) {
fifthTimePass++
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So based on what I've seen earlier, this should ideally live in a separate folder called compatibility and you should look towards CCK conformance imo.

Maybe come and discuss this on discord. See what the desired avenue is in the go community

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants