Coder Social home page Coder Social logo

cucumber / cucumber-expressions Goto Github PK

View Code? Open in Web Editor NEW
139.0 71.0 48.0 10.02 MB

Human friendly alternative to Regular Expressions

License: MIT License

Makefile 0.04% Go 16.15% Ruby 12.64% Java 30.10% TypeScript 13.39% C# 14.17% Python 13.50%
dotnet go java javascript python ruby polyglot-release tidelift

cucumber-expressions's Issues

LICENSE file is empty

The LICENSE file is currently empty; differing from other repositories in the Cucumber organisation.

πŸ‘“ What did you see?

Empty LICENSE file.

βœ… What did you expect to see?

MIT LICENSE specified, similar to other Cucumber repositories.

ChainAlert: npm package release (15.0.0) has no matching tag in this repo

Dear @cucumber/cucumber-expressions maintainers,
Thank you for your contribution to the open-source community.

This issue was automatically created to inform you a new version (15.0.0) of @cucumber/cucumber-expressions was published without a matching tag in this repo.

Our service monitors the open-source ecosystem and informs popular packages' owners in case of potentially harmful activity.
If you find this behavior legitimate, kindly close and ignore this issue. Read more

Define default parameter types for a different platform

The @cucumber/language-service module is able to extract Cucumber Expressions from source code (currently Java and TypeScript, but other languages can be added with relatively little effort).

However, if a user is using @cucumber/language-service (which is written in TypeScript) with Java, they may have Cucumber Expressions that uses parameter types that are only defined for Cucumber-JVM (such as {double} or {bigdecimal}).

These expressions will cause a parse error unless the registry is instantiated with those parameter types.

One way to support this is to add a defineDefaultParameterTypes(registry, platform) function where the value of platform would be used to decide what parameter types to define.

In order to implement this consistently on all platforms, we could add new acceptance tests in testdata/cucumber-expression/matching/*.yml with an additional platform property to indicate what platform to use.

Also see #42

RegEx "expressions" and defineParameterType feature file examples ?

πŸ€” What's the problem you're trying to solve?

I have been looking over the cucumber git repo for regex "expressions" (javascript)
defineParameterType seems interesting but I do not see any simple usage examples only unit test cases.
https://github.com/cucumber/cucumber-expressions/tree/5af7076298a9c2b5455e63f1c0de90ddc9f1c1a4#javascript--typescript

✨ What's your proposed solution?

The entire ParameterTypeRegistry + defineParameterType seems a bit complex when you explore it.
Showing off how to use it in a feature file would help adoption/integration.

public defineParameterType(parameterType: ParameterType<unknown>) {

⛏ Have you considered any alternatives or workarounds?

I am currently using Regex strings directly in the feature file step definitions. I was looking here to find better ways of doing things.

πŸ“š Any additional context?

None

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

`end` as a method name / prop can cause some issues

πŸ€” What's the problem you've observed?

In many of the AST sub-classes, such as Node there are a variety of attributes passed in. One of these is called end or derivations thereof. This name "end" can be problematic in some languages (Such as ruby). A few examples are below
Java - https://github.com/cucumber/cucumber-expressions/blob/main/java/src/main/java/io/cucumber/cucumberexpressions/Ast.java#L64
Ruby - https://github.com/cucumber/cucumber-expressions/blob/main/ruby/lib/cucumber/cucumber_expressions/ast.rb#L38
JS - https://github.com/cucumber/cucumber-expressions/blob/main/javascript/src/Ast.ts#L77

✨ Do you have a proposal for making it better?

It seems as though we often use the hash representation methods, and not directly using the getters. Therefore I propose removing all getters and just rely on using the hash methods. Failing this maybe altering start/end to starting and ending

πŸ“š Any additional context?


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

Allow free-form text in optionals

πŸ€” What's the problem you're trying to solve?

I'd like to have optional free-form text in my steps:

For instance:

Then error 123 is reported (because reasons)

The "because reasons" string isn't fixed, the content of the parenthesis varies depending on the scenario.

✨ What's your proposed solution?

It would be nice to either:

  • have a way to use some kind of "catch-all" structure (here .*) in optionals:
    error {int} is reported( \\(.*)
  • or allow parameters in optionals
    error {int} is reported( \\({})

⛏ Have you considered any alternatives or workarounds?

I tried using an anonymous parameter:

error {int} is reported( \\(){}

and ignore the related parameter in the step definition.

but this is not possible in my case, since I also have a step for:

error {int} is reported {int} times( \\(){}

which causes an ambiguous step definition.

This is why I'd need to put the anonymous part within the optional:

error {int} is reported( \\({})

But then it is an illegal expression (parameters are not allowed in optionals).

I also tried using a comment at the end of the expression:

Then error 123 is reported #(because reasons)

but comments are only allowed as a start of a new line.

The only resort is to use a regex. But the expressions then get quite ugly and unreadable (the examples above are simplified, my real expressions are more complex).
Or move the free-form text to its own line, but it is definitely less concise and sort of breaks the scenario flow.

These two workarounds work, but are suboptimal as they unfortunately make for less readable code.

πŸ“š Any additional context?

I recently used cucumber v4.5.4 (cucumber-java), and the following expressions just worked for my use case:

error {int} is reported( \\(.*)`
error {int} is reported {int} time(s)( \\(.*)

This stopped working when migrating to cucumber v7.8.0!

Using alternates within escaped parentheses doesn't work

πŸ‘“ What did you see?

In this expression:

I run cucumber-js \(installed locally/globally\)

The phrase I run cucumber-js installed locally doesn't match.

The generated regular expression is shown as:

/^I run cucumber-js \(installed (?:locally|globally\))$/

βœ… What did you expect to see?

Instead of the generated regular expression

/^I run cucumber-js \(installed (?:locally|globally\))$/

I think it should be

/^I run cucumber-js \(installed (?:locally|globally)\)$/

I guess we're gobbling up the trailing \) as part of the word globally

πŸ“¦ Which version are you using?

cucumber-expressions javascript 15.0.1

πŸ”¬ How could we reproduce it?

https://cucumber.github.io/cucumber-expressions/?advanced=1&expression=I%20run%20cucumber-js%20%5C%28installed%20locally%2Fglobally%5C%29&step=I%20run%20cucumber-js%20installed%20locally

πŸ€” Anything else?

Discovered in cucumber/cucumber-js#1926

Unable to create step definition with | operator with latest version

πŸ‘“ What did you see?

When I try to create step as below

Then Verify "user" exist in the list
Then Verify "user" not exist in the list

with single step definition, it is not identifying. I have create stepDef as below

@Then ("Verify {string} (|not) exist in the list")
public void verifyUserExist(String username, String exist){
   if(exist.isEmpty){
     //check exist
 }
}

showing error as "Step undefined
You can implement this step using the snippet(s) below:"

βœ… What did you expect to see?

This was working with older cucumber version I hope when i check in this Issue

πŸ“¦ Which tool/library version are you using?

Cucumber version: 7.15.0

πŸ”¬ How could we reproduce it?

No response

πŸ“š Any additional context?

No response

Nested capture groups are not passed to custom param definition transformer

πŸ‘“ What did you see?

When creating a custom param definition, the transformer funcion does not received nested capturing groups.

βœ… What did you expect to see?

As javascript developer, I expect regex capturing groups to conform Regex Ecamscript standard and capture nested groups

πŸ“¦ Which tool/library version are you using?

https://www.npmjs.com/package/@cucumber/cucumber-expressions/v/16.1.2

πŸ”¬ How could we reproduce it?

Create a new npm package with your prefered typescript setup (the bug is also recreated with plain javascript).

In the package.json, install @cucumber/[email protected] dependency

Define a custom parameter with nested captured groups:

import { defineParameterType } from '@cucumber/cucumber';

defineParameterType({
  name: 'foo',
  regexp: /((foo))/,
  transformer: function (
    firstGroup: string,
    secondGroup: string
  ): string[] {
   return [firstGroup, secondGroup]
  },
});

Then, write any step definition and feature involving this custom definition

import { Given, Then, When } from '@cucumber/cucumber';

Given('{foo}', function(foo: string[]): void { console.log(foo) });
When('bar', function(): void {});
Then('baz', function(): void {});
Given foo
When bar
Then baz

Then, create a minimal config testing the feature processing the custom parameter and the step definitions and have a look at the console. Instead of ['foo', 'foo'], ['foo', undefined] is printed instead.

πŸ“š Any additional context?

It seems the capturing groups are internally represented as a tree. Cucumber relies on TreeRegexp to create a GroupBuilder for the purpose of capturing regex groups. These groups are represented as a tree and only the root ones are passed to the custom definition transformer, ignoring any children groups.

Having a look at the Argument class:

public getValue<T>(thisObj: unknown): T | null {
  const groupValues = this.group ? this.group.values : null
  return this.parameterType.transform(thisObj, groupValues)
}

group.values are passed to the transformer:

export default class Group {
  constructor(
    public readonly value: string,
    public readonly start: number | undefined,
    public readonly end: number | undefined,
    public readonly children: readonly Group[]
  ) {}

  get values(): string[] | null {
    return (this.children.length === 0 ? [this] : this.children).map((g) => g.value)
  }
}

As you can see granchildren groups are never included. I would expect nested groups to be included as Ecmascript Regex spec states


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

Dependency Dashboard

This issue provides visibility into Renovate updates and their statuses. Learn more

This repository currently has no open or pending branches.


  • Check this box to trigger a request for Renovate to run again on this repository

Remove Python example illustrating how to define parameter types

πŸ‘“ What did you see?

Instructions about how to define a parameter type in Python: https://github.com/cucumber/cucumber-expressions#python

This is misleading, because Behave has not yet integrated this library into its code. Cucumber Expressions are not supported in Behave.

βœ… What did you expect to see?

Behave is not yetusing this library. It doesn't support Cucumber Expressions yet, nor does it have an API for defining parameter types. See behave/behave#968 for more details.

πŸ“¦ Which tool/library version are you using?

Behave v1.2.7.dev2

Action Required: Fix Renovate Configuration

There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.

Location: ^*.gemspec
Error type: Invalid regular expression: ^*.gemspec

Use YAML instead of embedded JSON in acceptance test data

Is your feature request related to a problem? Please describe.

The various testdata/**/*.yaml files have expected fields that are strings containing JSON. I find this hard to read, and it also means the tests have to parse JSON after parsing YAML.

Describe the solution you'd like

I'd like the test data to just be YAML. So instad of this:

expression: mice/rats
expected: |-
{"type": "EXPRESSION_NODE", "start": 0, "end": 9, "nodes": [
{"type": "ALTERNATION_NODE", "start": 0, "end": 9, "nodes": [
{"type": "ALTERNATIVE_NODE", "start": 0, "end": 4, "nodes": [
{"type": "TEXT_NODE", "start": 0, "end": 4, "token": "mice"}
]},
{"type": "ALTERNATIVE_NODE", "start": 5, "end": 9, "nodes": [
{"type": "TEXT_NODE", "start": 5, "end": 9, "token": "rats"}
]}
]}
]}

We'd have this instead:

expression: mice/rats
expectation:
  type: EXPRESSION_NODE
  start: 0
  end: 9
  nodes:
  - type: ALTERNATION_NODE
    start: 0
    end: 9
    nodes:
    - type: ALTERNATIVE_NODE
      start: 0
      end: 4
      nodes:
      - type: TEXT_NODE
        start: 0
        end: 4
        token: mice
    - type: ALTERNATIVE_NODE
      start: 5
      end: 9
      nodes:
      - type: TEXT_NODE
        start: 5
        end: 9
        token: rats

Any objections if I convert the test data? (I'd also change JSON.parse(expectation.expected) to just expectation.expected in the tests.

My main reason for doing this is to just tidy up a bit before people start implementing the Python / .NET ports...

WDYT @mpkorstanje?

Typo in error message for use of alternation inside an optional

Repeated 'the' within the error message for use of an alternation inside an optional.

πŸ‘“ What did you see?

Through an issue raised on the Visual Studio Code extension (cucumber/vscode#142), it was observed that the error message for use of an alternation inside an optional includes a repeated 'the' in its second line:

An alternation can not be used inside an optional.
You can use '\/' to escape the the '/'

βœ… What did you expect to see?

Single 'the':

An alternation can not be used inside an optional.
You can use '\/' to escape the '/'

πŸ“¦ Which tool/library version are you using?

Cucumber Expressions: 16.1.2
Cucumber Visual Studio Code extension: 1.8.0

πŸ”¬ How could we reproduce it?

  1. Install the Visual Studio Code extension
  2. Create a step definition with an alternation inside an optional
import { When } from 'cypress-cucumber-preprocessor/steps'
const { wait } = cy

When('entro no detalhe d(a/o) {string}', () => {
  wait('@apisRequests')
})
  1. Check the logs of the Cucumber Language Server in the Output window

Add the ability to ignore a cucumber expression parameter just like a non capturing regex

πŸ€” What's the problem you're trying to solve?

Currently, there is no way to ignore a cucumber expression parameter, even though it is possible to do so in a normal regex. Thus, I think it would be nice to be able to have access to that feature in cucmber expressions as well.

✨ What's your proposed solution?

My proposed solution is simply to add the same mechanism as with a non-capturing regex group (for example (?:.*?)) like this, for example:

@Given( "{string} Database Entry with {?string} {string} has {string} = {string}" )
public void databaseEntryWithIdHasString( String tableName, String id, String columnName, String value) {
    ...
}

As you can see in my snippet, the cucumber expression {?string} is ignored, as it is just a string that will be used to define the name of the id. It's just a way to add more clarity to my features without the need of creating a new step definition for each and every way we can describe our column id.

Currently, I need to add it to my method signature like the snippet below and get warnings that my parameter idName is not used, which requires me to suppress the warning (we are using SonarQube and this kind of warning decreases our code quality):

@SuppressWarnings( "unused" )
@Given( "{string} Database Entry with {string} {string} has {string} = {string}" )
public void databaseEntryWithIdHasString( String tableName, String idName, String id, String columnName, String value) {
    ...
}

I think that in the parser that you are using to convert cucumber expressions to their actual parameter value, you can simply filter out the ones that start with a question mark, as simple as that.

⛏ Have you considered any alternatives or workarounds?

Yes, I have considered using a regex instead, but I feel like that adding the feature would be pretty low-effort, and I'd rather keep using cucumber expressions, since the reason why they exist is to make the step definitions more readable (I want to keep my step definitions readable).

πŸ“š Any additional context?

Expression for an argument list

πŸ€” What's the problem you're trying to solve?

In some cases it might be helpful to have a way of declaring several instances of one type as an expression, e.g.
Given the customer selects product(s) {product}+
which could match something like:

Given the customer selects product bike
Given the customer selects products bike, car
Given the customer selects products bike, car, skateboard

and for a better flow maybe also with a filler:

Given the customer selects products bike and car
Given the customer selects products bike, car or skateboard

What already works is something like this:

  val separators = arrayOf(",", ";", "and", "or")

  @ParameterType("^(?:(?:,|;|and|or)?\s?\w+)+$")
  fun products(product: String): List<Product> {
    return product.split(*separators)
      .map { it.trim() }
      .map { Product(it) }
  }

This has the disadvantage, that the regex (if specific things still should be checked) might get complex, since it also has to include all separators and the possibility of them being there or not. Also, the complete string (incl. separators) is highlighted in the feature files. Hence, having that as a feature of cucumber expressions would be nice, since it might make the used regex easier. And might also help with the highlighting in the feature files, since the separators could be displayed in another color.

✨ What's your proposed solution?

I could imagine two different ways. For both ways the definition of the parameter type might be kind of the same like it is now (define how the transformation is done for one single element), but it could define separators:

  @ParameterType(regex = "\\w+", separators=[",", ";", "and", "or"])
  fun product(product: String): List<Product> {
    return Product(it)
  }

Option one: a special notation for marking an expression as "vararg", e.g. {name}+

If the + is provided, the method signature must contain a collection type:

 @Given("the customer selects product(s) {product}+")
  fun givenCustomerSelectsProduct(product: List<Product>) { \* ... *\ }

Option two: no special expression syntax - instead it depends on the method signature if it is a single value or a collection of values is provided.

So both would be possible:

 @Given("the customer selects product(s) {product}")
  fun givenCustomerSelectsProduct(product: Product) { \* ... *\ }

and

 @Given("the customer selects product(s) {product}")
  fun givenCustomerSelectsProduct(product: List<Product>) { \* ... *\ }

Python implementation of Cucumber Expressions

The Behave team (led by @jenisys) would like to add support for Cucumber Expressions in Behave.

The first step would be to implement Cucumber Expressions in Python.
There are four existing implementations in this repository (Go, Ruby, TypeScript and Java). The Python implementation should follow the structure of the existing implementations as closely as possible. This will make maintenance easier in the future.

Every implementation uses a shared set of test data, which are YAML files with input and expected output for different parts of the parser.

The parser consists of multiple components, and it may be easier to implement the library in the following order:

  • CucumberExpressionTokenizer
  • CucumberExpressionParser
  • TreeRegexp
  • CucumberExpression
  • CucumberExpressionGenerator

I would also recommend using TDD to guide the implementation. That is - start by writing the test for a component (reading in thest data from the relevant YAML file). Make the tests pass one by one, by making incremental improvements to the code.

If contributors are working in a team, it may be easier to collaborate if all contributors are committing to the same feature branch in the same repo (as opposed to using individual forks). Please add comment here if you want commit access so you can work on a branch in this repo.

Terraform Compliance v1.3.31, Alternative text bug?

πŸ‘“ What did you see?

Alternative text fails in this scenario:

Feature: vm compliance

    Scenario: vm Sku compliance
        Given I have azurerm_virtual_machine defined
        Then it must have "vm_size"
        And its value must be Standard_DS1_v2/Standard_DS2_v1

Terminal Output:

Failure: vm_size property in azurerm_virtual_machine.vm resource does not match with ^Standard_DS1_v2/Standard_DS2_v1$ case insensitive regex. It is set to Standard_DS1_v2.
        And its value must be Standard_DS1_v2/Standard_DS2_v1
          Failure:

βœ… What did you expect to see?

Scenario to pass.

πŸ“¦ Which version are you using?

Terraform v1.1.7
Terraform Compliance v1.3.31
PowerShell 7.2.1
Win 11 OS, up to date.

πŸ”¬ How could we reproduce it?

Steps to reproduce the behavior:

  1. Install Terraform Compliance v1.3.31

  2. Extract plan.zip to folder, contains plan file and plan.json and z.feature
    plan.zip

  3. Run command 'terraform-compliance -p ./plan -f ./'
    5.. See error in terminal output.

πŸ€” Anything else?

Deprecate Java-specific types (biginteger, bigdecimal, byte, short, long, double)

Is your feature request related to a problem? Please describe.

While working on #43 it struck me that all the parameter types that are java-specific (biginteger, bigdecimal, byte, short, long, double) don't need an explicit parameter type:

An {int} parameter can be transformed to any of the following classes:

  • java.lang.Byte
  • java.lang.Short
  • java.lang.Integer
  • java.lang.Long
  • java.math.BigInteger

A {float} parameter can be transformed to any of the following classes:

  • java.lang.Float
  • java.lang.Doublet
  • java.math.BigDecimal

Deciding what class to transform to would first use the class from the corresponding method parameter's Java type. If the Java type isn't available (e.g. for dynamically typed languages), the appropriate type would be chosen based on the string value - we'd try to fit it into the "biggest" type. E.g. "127" becomes java.lang.Byte, "128" becomes java.lang.Short etc.

A similar algorithm could be used for float.

The main reason I want to do this is to solve the "different parameter types for different platforms" problem described in #43, but with a cleaner (IMO) solution.

Another reason I want to do this is to avoid having different parameter types for different platforms, which makes it harder to implement cross-platform tools (such as the Cucumber Language Server).

Describe the solution you'd like

The gist of the solution is described above, but I'd also like this to be backwards compatible. Using any of the deprecated types will print a warning, encouraging users to switch to {int} or {float}. Implementation-wise, the deprecated types would just be aliases for the {int} or {float} types.

Describe alternatives you've considered
#43 is an alternative I have considered.

Additional context
Add any other context or screenshots about the feature request here.

[python] Package "click" has a new version which breaks linting in CI

πŸ‘“ What did you see?

python CI broke on using pyflakes, I think, and in doing so, reported a traceback.

ImportError: cannot import name '_unicodefun' from 'click'

This issue in another Python-using project describes the problem:
dask/distributed#6013

black....................................................................Failed
- hook id: black
- exit code: 1

Traceback (most recent call last):
  File "/home/runner/.cache/pre-commit/repoa_4yao8n/py_env-python3.10/bin/black", line 8, in <module>
    sys.exit(patched_main())
  File "/home/runner/.cache/pre-commit/repoa_4yao8n/py_env-python3.10/lib/python3.10/site-packages/black/__init__.py", line 1372, in patched_main
    patch_click()
  File "/home/runner/.cache/pre-commit/repoa_4yao8n/py_env-python3.10/lib/python3.10/site-packages/black/__init__.py", line 1358, in patch_click
    from click import _unicodefun
ImportError: cannot import name '_unicodefun' from 'click' (/home/runner/.cache/pre-commit/repoa_4yao8n/py_env-python3.10/lib/python3.10/site-packages/click/__init__.py)

flake8...................................................................Passed
Error: Process completed with exit code 1.

βœ… What did you expect to see?

A passing CI for Python.

πŸ“¦ Which tool/library version are you using?

Well, this is here, in this repo, in CI.

πŸ”¬ How could we reproduce it?

See
https://github.com/cucumber/cucumber-expressions/runs/5733063101?check_suite_focus=true

πŸ“š Any additional context?

Released Ruby gem is missing LICENSE file.

I am working on updating a few packages in the Ruby cucumber stack in Fedora and I noticed the license file is missing from this library now, that wasn't the case with v14.

From what I understand the file should be present in the released gem as it's a copy of the software.

Not 100% sure how a proper fix should look like.

AFAICT, to build and then unpack/install the gem properly, the license has to be either on the same level with the .gemspec file in the Ruby gem or some level below. A file cannot be referenced a level up from gemspec, so doing something like s.files << "../LICENSE" is not going to work for unpacking the gem. Perhaps a Rakefile target that copies the LICENSE first and then builds the gem so that something like s.files << "./LICENSE" would then be OK.

Improve Expression creation performance

πŸ‘“ What did you see?

I have a project tested with Cucumber 7.9.0:

  • about 400 test scenarios
  • about 150 step defs
  • about 30 parameter types

The Cucumber tests run in about 10.2 seconds on average : that's not so bad (we have plain Java, no Spring, no Mockito). But we wanted to have an even faster feedback loop for the developer.

Thus, we made some profiling and found that ExpressionFactory.createExpression(String) was eating a lot of CPU (this method is called more than 64'000 times for our project).

βœ… What did you expect to see?

We improved the code with two different variants and made some benchmark using JMH microbenchmark framework :

  • creationExpression0 is the original method
  • creationExpression1 is a small update in which we replaced some regexp conditions by startsWith/endsWith conditions
  • creationExpression2 is a full rewrite with regexp conditions replaced by character testing conditions

The benchmark results are the following :

Benchmark Mode Cnt Score Error Units
ExpressionFactoryBenchmark.createExpression0 thrpt 25 234230,747 Β± 4693,108 ops/s
ExpressionFactoryBenchmark.createExpression1 thrpt 25 347290,522 Β± 4297,255 ops/s
ExpressionFactoryBenchmark.createExpression2 thrpt 25 356142,244 Β± 9833,928 ops/s

When using the createExpression2 variant on our project, the cucumber tests run in 9.0 seconds on average. That's a 1.2 second improvement (12%).

Thus, we suggest to replace current ExpressionFactory.createExpression(String) implementation by the one from our createExpression2.

You can find in annex the three different variants, unit testing to ensure that all three variants behave the same and the JMH benchmark code.

cucumberexpressions.zip

πŸ“¦ Which tool/library version are you using?

I'm using Cucumber 7.9.0 with the following Maven dependencies:

    <dependency>
        <groupId>io.cucumber</groupId>
        <artifactId>cucumber-java</artifactId>
        <version>${cucumber.version}</version>
        <scope>test</scope>
        <exclusions>
            <exclusion><!-- version 1.1.2 from cucumber conflicts with version 1.1.0 from junit -->
                <groupId>org.apiguardian</groupId>
                <artifactId>apiguardian-api</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>io.cucumber</groupId>
        <artifactId>cucumber-junit-platform-engine</artifactId>
        <version>${cucumber.version}</version>
        <scope>test</scope>
        <exclusions>
            <exclusion><!-- conflicts with the version from junit-platform-suite -->
                <groupId>org.junit.platform</groupId>
                <artifactId>junit-platform-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>io.cucumber</groupId>
        <artifactId>cucumber-picocontainer</artifactId>
        <version>${cucumber.version}</version>
        <scope>test</scope>
    </dependency>

πŸ”¬ How could we reproduce it?

The JMH micro benchmark is provided in ExpressionFactoryBenchmark.

Steps to reproduce the behavior:

  1. Create a blank Maven project with the pom.xml above and JUnit5
  2. Run the micro benchmark

Typescript step definition not matched

πŸ‘“ What did you see?

Creating step definitions like below are not matched to the feature.
It works when making it a regular expression (instead of cucumber expression) as mentioned in Docs.

βœ… What did you expect to see?

Step definitions should be matched correctly to the feature without having to use a regular expression.

πŸ“¦ Which tool/library version are you using?

WebdriverIO 7.16.13
Cucumber 7.3.2
Cucumber-expressions 15.0.2
TypeScript 4.5.5

πŸ”¬ How could we reproduce it?

Not working:

Given("The user selects {string} from the (country|language) dropdown", function(toBeSelectedValue: string, dropdownType: string){
    console.log(toBeSelectedValue)
    console.log(dropdownType)
})

Working:

Given(/^The user selects (.*) from the (country|hospital) dropdown$/, function(toBeSelectedValue: string, dropdownType: string){
    console.log(toBeSelectedValue)
    console.log(dropdownType)
})

Feature file:

Feature: Select dropdown value

Scenario: A value is selected
    Given The user selects "Norway" from the country dropdown

more powerful optional text

πŸ€” What's the problem you're trying to solve?

Currently optional text is only good for a plural s and the like.
So I have {int} cucumber(s) in my belly matches I have 1 cucumber in my belly.

I want to implement even less variants of identical test steps by having a more powerful optional text.

✨ What's your proposed solution?

  • I also wish I should not see the population (anymore) to match I should not see the population.
    (no extra space at the end of the text)
  • And I wish I should be (back) on the start page to match I should be on the start page.
    (no double space in the text)
  • And I wish sehe ich (wieder) den/die Breadcrumb(s) {string} to match sehe ich den Breadcrumb "Start".
    (still mixable with alternative text)

⛏ Have you considered any alternatives or workarounds?

Ugly workarounds:

  • cripple the test specs by adding extra spaces, e.g. I am on the start page (space at the end)
  • cripple the expressions by putting spaces in brackets, e.g. I am on the start page( again)
  • restricting test spec writer's language to be less natural (without β€œagain”, etc.)
  • additional test step calling the other variant
  • workaround to convert such (more powerful) expressions to classic cucumber expressions (code here)

πŸ“š Any additional context?

To be honest, I am somewhat surprised this doesn't work out of the box. See also the original discussion in another project.


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

Golang big.Float is not a big decimal

πŸ€” What's the problem you've observed?

I am considering using the golang implementation of cucumber-expressions in https://github.com/regen-network/gocuke and noticed that bigdecimal is mapped to https://pkg.go.dev/math/big#Float.

The documentation for big.Float states that this is structured as sign Γ— mantissa Γ— 2**exponent. This is not a decimal number! It is a base-2 floating point number, not base-10 as required for decimals.

Java does provide a proper BigDecimal where the documentation clearly states that the structure is unscaledValue Γ— 10-scale.

✨ Do you have a proposal for making it better?

The mapping of bigdecimal to big.Float should be removed in the golang package and consumers should be allowed to register a correct decimal implementation (none is provided by the standard library).

Also in the golang implementation, it seems like it's an error to override a built-in type mapping. It might be worth reconsidering this in case users want to swap out other type mappings.

πŸ“š Any additional context?

Two correct golang decimal implementations are: https://pkg.go.dev/github.com/cockroachdb/apd/v3 and https://pkg.go.dev/github.com/ericlagergren/decimal/v3.


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

Move "try" into a dedicated project

πŸ€” What's the problem you've observed?

The "try" as part of cucumber-expression javascript implementation oversize it and add a lot of dependency to cucumber-expression itself. It could live on its own, being inside its own repo, or within a dedicated folder within this repo

Refs. #104 (comment)

✨ Do you have a proposal for making it better?

I would put it its in own repo actually, thus it would not interfere with cucumber-expression at all


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

Return optionals as part of a Cucumber Expression match

πŸ€” What's the problem you're trying to solve?

I want to provide syntax highlighting (LSP semantic tokens) for the parts of a Gherkin step that is optional in the corresponding Cucumber Expression.

✨ What's your proposed solution?

Use named capture groups for optionals, as explained in cucumber/language-service#57

The return type of match would change from Argument[] | null to Match | null:

export type Match = {
  arguments: readonly Argument[]
  optionals: readonly Optional[]
}

export type Optional = {
  group: Group
}

⛏ Have you considered any alternatives or workarounds?

If we want to support this, I can't think of any other option.

Unable to parse negative number in Norwegian

Attempting to use a negative number in a step that expects a{double}, in a test case written in Norwegian, doesn't work.

Minimal example:

# language: no

  Egenskap: Betalingseksempel

  Scenario: Negativ rest

    Gitt at du har 10,04 kr
    NΓ₯r du betaler 12,15 kr
    SΓ₯ skal du ha -2,11 kr igjen
package com.example.stepdefs;

import io.cucumber.java.no.Gitt;
import io.cucumber.java.no.NΓ₯r;
import io.cucumber.java.no.SΓ₯;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class PaymentStepDefs {

    private double balance;

    @Gitt("at du har {double} kr")
    public void initialBalance(double balance) {
        this.balance = balance;
    }

    @NΓ₯r("du betaler {double} kr")
    public void makePayment(double payment) {
        this.balance = this.balance - payment;
    }

    @SΓ₯("skal du ha {double} kr igjen")
    public void assertRemainingBalance(double expected) {
        assertEquals(expected, this.balance);
    }
}

πŸ‘“ What did you see?

Cucumber is unable to parse the number:

io.cucumber.core.exception.CucumberException: Could not convert arguments for step [skal du ha {double} kr igjen] defined at 'com.example.stepdefx.PaymentStepDefs.assertRemainingBalance(double)'.

	at io.cucumber.core.runner.PickleStepDefinitionMatch.couldNotConvertArguments(PickleStepDefinitionMatch.java:112)
	at io.cucumber.core.runner.PickleStepDefinitionMatch.runStep(PickleStepDefinitionMatch.java:56)
	at io.cucumber.core.runner.ExecutionMode$1.execute(ExecutionMode.java:10)
	at io.cucumber.core.runner.TestStep.executeStep(TestStep.java:84)
	at io.cucumber.core.runner.TestStep.run(TestStep.java:56)
	at io.cucumber.core.runner.PickleStepTestStep.run(PickleStepTestStep.java:51)
	at io.cucumber.core.runner.TestCase.run(TestCase.java:84)
	at io.cucumber.core.runner.Runner.runPickle(Runner.java:75)
	at io.cucumber.junit.platform.engine.CucumberEngineExecutionContext.lambda$runTestCase$4(CucumberEngineExecutionContext.java:112)
	at io.cucumber.core.runtime.CucumberExecutionContext.lambda$runTestCase$5(CucumberExecutionContext.java:137)
	at io.cucumber.core.runtime.RethrowingThrowableCollector.executeAndThrow(RethrowingThrowableCollector.java:23)
	at io.cucumber.core.runtime.CucumberExecutionContext.runTestCase(CucumberExecutionContext.java:137)
	at io.cucumber.junit.platform.engine.CucumberEngineExecutionContext.runTestCase(CucumberEngineExecutionContext.java:109)
	at io.cucumber.junit.platform.engine.NodeDescriptor$PickleDescriptor.execute(NodeDescriptor.java:168)
	at io.cucumber.junit.platform.engine.NodeDescriptor$PickleDescriptor.execute(NodeDescriptor.java:90)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Caused by: io.cucumber.cucumberexpressions.CucumberExpressionException: Failed to parse number
	at io.cucumber.cucumberexpressions.NumberParser.parse(NumberParser.java:41)
	at io.cucumber.cucumberexpressions.NumberParser.parseDouble(NumberParser.java:21)
	at io.cucumber.cucumberexpressions.BuiltInParameterTransformer.doTransform(BuiltInParameterTransformer.java:80)
	at io.cucumber.cucumberexpressions.BuiltInParameterTransformer.transform(BuiltInParameterTransformer.java:22)
	at io.cucumber.cucumberexpressions.ParameterTypeRegistry$8.transform(ParameterTypeRegistry.java:114)
	at io.cucumber.cucumberexpressions.ParameterTypeRegistry$8.transform(ParameterTypeRegistry.java:111)
	at io.cucumber.cucumberexpressions.ParameterType$TransformerAdaptor.transform(ParameterType.java:299)
	at io.cucumber.cucumberexpressions.ParameterType.transform(ParameterType.java:259)
	at io.cucumber.cucumberexpressions.Argument.getValue(Argument.java:42)
	at io.cucumber.core.stepexpression.ExpressionArgument.getValue(ExpressionArgument.java:17)
	at io.cucumber.core.runner.PickleStepDefinitionMatch.runStep(PickleStepDefinitionMatch.java:47)
	... 15 more
Caused by: java.text.ParseException: Unparseable number: "-2,11"
	at java.base/java.text.NumberFormat.parse(NumberFormat.java:434)
	at io.cucumber.cucumberexpressions.NumberParser.parse(NumberParser.java:39)
	... 25 more

βœ… What did you expect to see?

The text should be parsed to double value -2.11.

πŸ“¦ Which tool/library version are you using?

Cucumber-java 12.15

Feature: named capture groups

I've been looking at how cucumber-expressions could be adopted in Behat.

One feature of our existing two pattern syntaxes (regex and turnip) is that they can name the arguments.

e.g.

@When /^I eat (?<count>[0-9]+) (?<fruit>.*)$/
@When I eat :count :fruit

This allows Behat to match arguments based on name as well as position, letting users transpose the argument order if necessary.

That can be useful if multiple patterns are attached to one step, something allowed in Behat:

/**
 * @When I eat :count :fruit
 * @When :count of the :fruit are eaten
 * @When I don't eat any :fruit
 */
public function myStepDef(string $fruit, int $count=0): void

The new feature would be for Cucumber Expressions to capture argument names:

  1. Define a syntax for adding names to expressions (e.g. {fruit:string})
  2. Retain that name in the generated regex as a named group
  3. Attach the name to the matched Argument value objects that the parser outputs (with an accessor method)

Then it would be up to the Cucumber implementation to either use the names for matching to the step definition, or to ignore them and use the order directly as currently happens.

Update Grammar in Architecture

I'm implementing Cucumber Expressions parser in Rust: WIP. And grammar described in the ARCHITECTURE.md really bothers me for couple of reasons.

  1. Lookbehind and lookahead in alternation definition
    EBNF describes context-free grammars and (correct me if I'm wrong) shouldn't have lookaheads and lookbehinds. This also means that described grammar may have non-linear complexity.
  2. Provided grammar describes superset of Cucumber Expressions and I don't really see reasons to do it.

So implementing Cucumber Expression parser according to the docs leads to unnecessary performance overhead for no obvious reasons.

Can't we just provide exact grammar for Cucumber Expressions without Note: section?


If I didn't miss anything provided grammar should be exact Cucumber Expressions

cucumber-expression     = (alternation
                           | optional
                           | parameter
                           | text)*
text                    = (- text-to-escape) | ('\', text-to-escape)
text-to-escape          = '(' | '{' | '/' | '\' 

alternation             = single-alternation, ('/', single_alternation)+
single-alternation      = ((text-in-alternative+, optional*) 
                            | (optional+, text-in-alternative+))+
text-in-alternative     = (- alternative-to-escape) | ('\', alternative-to-escape)
alternative-to-escape   = ' ' | '(' | '{' | '/' | '\'

optional                = '(', text-in-optional+, ')'
text-in-optional        = (- optional-to-escape) | ('\', optional-to-escape)
optional-to-escape      = '(' | ')' | '{' | '/' | '\'

parameter               = '{', name*, '}'
name                    = (- name_to_escape) | ('\', name-to-escape)
name-to-escape          = '{' | '}' | '(' | '/' | '\'

java: Not all tests are executed

πŸ€” What's the problem you've observed?

private static List<Path> acceptance_tests_pass() throws IOException {
List<Path> paths = new ArrayList<>();
newDirectoryStream(Paths.get("..", "testdata", "cucumber-expression", "matching")).forEach(paths::add);
paths.sort(Comparator.naturalOrder());
return singletonList(Paths.get("..", "testdata", "cucumber-expression", "matching", "matches-byte.yaml"));
}

Because of this not all acceptance tests are executed. This pattern may be repeated elsewhere.

Bad CucumberExpression creation performance

πŸ‘“ What did you see?

On my project with about 150 stepdefs and about 400 test scenarios, the IntelliJ profiler says the CucumberExpression.<init> method takes 25.9% of the total CPU time. This is because the method is called for all step defs and for all test scenarios. I think the performance could be better.

βœ… What did you expect to see?

I expect CucumberExpression.<init> to avoid unnecessary processing (contributes to #2035).

I understand that cucumber-java8 can introduce dynamic behavior which requires parsing the expressions for each test scenario. However, I think we can safely cache everything that is constant and does not depend on cucumber-java8. I identitifed the following performance improvement points in CucumberExpression:

  • TreeRegex creation: in CucumberExpression constructor, this object serves to get some "metadata" about a regular expression itself (i.e. not depending on context). Thus, two identical regular expressions will lead to the same TreeRegp, so the creation is cacheable.

    The original code:

    this.treeRegexp = new TreeRegexp(pattern);
    

    could be replaced by (treeRegexps is a static Map<String, TreeRegexp>):

    this.treeRegexp = treeRegexps.computeIfAbsent(pattern, TreeRegexp::new);
    
  • calls to escapeRegex in the rewriteToRegex method are done on the Node.text() content: two identical Node.text() will lead to the same escaped result, independently of the context. Thus, the result of escapeRegex is cacheable.

    The original code:

    return escapeRegex(node.text());
    

    can be replaced by (escapedTexts is a static Map<String, String>):

    return escapedTexts.computeIfAbsent(node.text(), CucumberExpression::escapeRegex);
    

These two optimization points lead to four combinations to be benchmarked (original version is createExpression0). The benchmark consists in creating 400 times five different expressions:

Benchmark cached calls to escapeRegex cached TreeRegex creation ops/s
CucumberExpressionBenchmark.createExpression0 no no 153,024 Β± 13,800
CucumberExpressionBenchmark.createExpression1 yes no 181,960 Β± 12,133
CucumberExpressionBenchmark.createExpression2 no yes 186,236 Β± 11,232
CucumberExpressionBenchmark.createExpression3 yes yes 219,890 Β± 12,365

Caching the TreeRegex creation lead to 22% performance improvement and using both methods lead to 44% performance improvement.

On a real project with about 150 stepdefs and 400 test scenarios, the IntelliJ Profiler runs is about 7700 ms and says that CucumberExpression.<init> is:

  • 25.9% of the total CPU time with the original version (1994 ms)
  • 15.7% of the total CPU time with both optimizations enabled (1209 ms, i.e. that's a 785 ms improvement on total time, or 10%)

I suggest to use the variant createExpression3 and I would be happy to propose a PR.

πŸ“¦ Which tool/library version are you using?

Cucumber 7.10.1

πŸ”¬ How could we reproduce it?

The benchmark with the four variants is in
cucumberexpressions.zip

Steps to reproduce the behavior:

  1. Create a Maven project with the following dependencies:

     <dependency>
         <groupId>io.cucumber</groupId>
         <artifactId>cucumber-java</artifactId>
         <version>${cucumber.version}</version>
         <scope>test</scope>
     </dependency>
     <dependency>
         <groupId>io.cucumber</groupId>
         <artifactId>cucumber-junit-platform-engine</artifactId>
         <version>${cucumber.version}</version>
         <scope>test</scope>
     </dependency>
     <dependency>
         <groupId>io.cucumber</groupId>
         <artifactId>cucumber-picocontainer</artifactId>
         <version>${cucumber.version}</version>
         <scope>test</scope>
     </dependency>
     <dependency>
         <groupId>org.openjdk.jmh</groupId>
         <artifactId>jmh-generator-annprocess</artifactId>
         <version>1.36</version>
         <scope>test</scope>
     </dependency>
    
  2. Run the benchmark

Request for non-capturing groups in Cucumber Expressions

Hello,

It would be nice if Cucumber Expressions support non-capturing groups.
My intension is to have a mapping for the following step:

When I order 2 apples

and the step definitions mapping to be something like that

@When("I order {int} [fruit]")
public void orderFruits(int fruitNumber){
........
}

Where the square brackets will denote non-capturing group and the expression [{context_description}] would match any text, but wont pass it as a parameter to the step deifinition method

Missing source for source map

πŸ‘“ What did you see?

The source map reference the TypeScript source code but they are not included in the package.

e.g. in ./dist/esm/src/index.js.map:

{...,"sources":["../../../src/index.ts"],...}

But since the source are not included in the package, when bundling with webpack, it fails with:

WARNING in ../../node_modules/.pnpm/@[email protected]/node_modules/@cucumber/cucumber-expressions/dist/esm/src/index.js
Module Warning (from ../../node_modules/.pnpm/[email protected][email protected]/node_modules/source-map-loader/dist/cjs.js):
Failed to parse source map from '...\node_modules\.pnpm\@[email protected]\node_modules\@cucumber\cucumber-expressions\src\index.ts' file: Error: ENOENT: no such file or directory, open '...\node_modules\.pnpm\@[email protected]\node_modules\@cucumber\cucumber-expressions\src\index.ts'

βœ… What did you expect to see?

Include the TypeScript source code in the package. i.e.:

// package.json
{
  "files": [
    ...,
    "src"  // <-- add this
  ]
}

πŸ“¦ Which tool/library version are you using?

@cucumber/cucumber-expressions: 16.1.2

πŸ”¬ How could we reproduce it?

This involves webpack, will need to find time to create a repro.
But the issue itself should be simple enough to understand directly.

Tidy up contributor guide / local development environment

We want to make sure this project is convenient to hack on for new contributors.

We currently have the Makefiles imported from the monorepo, which can build all language versions at once. Do we still need those?

Should we provide a Docker environment (or multiple per-language environments) to help contributors build the projects?

The CONTRIBUTING.md file is empty.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.