Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e4b53c3
Use a message based usage formatter
mpkorstanje Oct 2, 2025
e335d86
Sort by duration
mpkorstanje Oct 2, 2025
f6aef1b
Extract UsageReportWriter
mpkorstanje Oct 2, 2025
62a860b
Use Duration instead of double
mpkorstanje Oct 2, 2025
fce6962
Use JavaTimeModule for deserialisation
mpkorstanje Oct 3, 2025
0f01c1a
More fiddling and tests
mpkorstanje Oct 3, 2025
421aeeb
Format uris to be shorter
mpkorstanje Oct 8, 2025
9bdfbac
Reuse for unused summary printer
mpkorstanje Oct 8, 2025
3035715
Merge remote-tracking branch 'origin/main' into message-based-usage-f…
mpkorstanje Oct 8, 2025
14be287
Bump query version
mpkorstanje Oct 8, 2025
48c5ed1
Spotless
mpkorstanje Oct 8, 2025
cbd04cd
Exclude steps without or too many step definitions
mpkorstanje Oct 8, 2025
29f0dc0
Revapi
mpkorstanje Oct 8, 2025
70625c2
Clean up
mpkorstanje Oct 9, 2025
374325a
Fix
mpkorstanje Oct 9, 2025
9c06754
Merge remote-tracking branch 'origin/main' into message-based-usage-f…
mpkorstanje Oct 9, 2025
ccc9d18
Calculate margin of error 95% instead
mpkorstanje Oct 9, 2025
bed98b2
Spotless
mpkorstanje Oct 9, 2025
bcc3b07
Naming
mpkorstanje Oct 9, 2025
d537b3a
WIP: Format as plain text
mpkorstanje Oct 9, 2025
1597e70
Finish
mpkorstanje Oct 10, 2025
f06f7ac
Add seconds
mpkorstanje Oct 10, 2025
ea4108c
Include authority in feature path
mpkorstanje Oct 10, 2025
5cdfa8a
Extract to repo
mpkorstanje Oct 10, 2025
26a88c7
Extract to repo
mpkorstanje Oct 10, 2025
8724818
Spotless
mpkorstanje Oct 10, 2025
e0fb9c2
Update documentation
mpkorstanje Oct 12, 2025
480c750
Merge remote-tracking branch 'origin/main' into message-based-usage-f…
mpkorstanje Oct 13, 2025
e436d04
Add usage json formatter
mpkorstanje Oct 14, 2025
f22408f
Use non snapshot
mpkorstanje Oct 15, 2025
687ed5b
Merge remote-tracking branch 'origin/main' into message-based-usage-f…
mpkorstanje Oct 15, 2025
f5fe9b3
Reduce diff
mpkorstanje Oct 15, 2025
b2e9d81
Fix
mpkorstanje Oct 15, 2025
7685310
Update CHANGELOG
mpkorstanje Oct 15, 2025
47de6f1
Merge remote-tracking branch 'origin/main' into message-based-usage-f…
mpkorstanje Oct 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
~~# Changelog
# Changelog

All notable changes to the current version this project will be documented in
this file. For previous versions see the [release-notes archive](release-notes).
Expand All @@ -13,8 +13,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- [Core] Prefer URIs with authority ([#3098](https://github.com/cucumber/cucumber-jvm/pull/3098) M.P. Korstanje)

### Added
- [Core] Add a `UsageJsonFormatter`, use with `--plugin usage-json` ([#3086](https://github.com/cucumber/cucumber-jvm/pull/3086) M.P. Korstanje)

### Changed
- [Core] Use a message based `TimeLineFormatter` ([#3095](https://github.com/cucumber/cucumber-jvm/pull/3095) M.P. Korstanje)
- [Core] Use a message based `UsageFormatter` ([#3086](https://github.com/cucumber/cucumber-jvm/pull/3086) M.P. Korstanje)
- [Core] Use a message based `UnusedFormatter` ([#3086](https://github.com/cucumber/cucumber-jvm/pull/3086) M.P. Korstanje)


## [7.30.0] - 2025-10-01
### Changed
Expand Down Expand Up @@ -579,4 +585,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[7.2.0]: https://github.com/cucumber/cucumber-jvm/compare/v7.1.0...v7.2.0
[7.1.0]: https://github.com/cucumber/cucumber-jvm/compare/v7.0.0...v7.1.0
[7.0.0]: https://github.com/cucumber/cucumber-jvm/compare/v7.0.0-RC1...v7.0.0
[7.0.0-RC1]: https://github.com/cucumber/cucumber-jvm/compare/v6.11.0...v7.0.0-RC1~~
[7.0.0-RC1]: https://github.com/cucumber/cucumber-jvm/compare/v6.11.0...v7.0.0-RC1
6 changes: 6 additions & 0 deletions cucumber-bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<tag-expressions.version>8.0.0</tag-expressions.version>
<teamcity-formatter.version>0.1.1</teamcity-formatter.version>
<testng-xml-formatter.version>0.6.0</testng-xml-formatter.version>
<usage-formatter.version>0.1.0</usage-formatter.version>
</properties>

<dependencyManagement>
Expand Down Expand Up @@ -90,6 +91,11 @@
<artifactId>testng-xml-formatter</artifactId>
<version>${testng-xml-formatter.version}</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>usage-formatter</artifactId>
<version>${usage-formatter.version}</version>
</dependency>

<!-- Modules in cucumber-jvm -->
<dependency>
Expand Down
24 changes: 22 additions & 2 deletions cucumber-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ cucumber.glue= # comma separated package names.

cucumber.plugin= # comma separated plugin strings.
# example: pretty, json:path/to/report.json
# example: com.example.MyCustomPlugin:path/to/report.xml

cucumber.object-factory= # object factory class name.
# example: com.example.MyObjectFactory
Expand Down Expand Up @@ -104,9 +105,28 @@ The performance gain on real projects depends on the feature size.

When not specified, the `RandomUuidGenerator` is used.

## Plugin ##
## Built in plugins ##

By implementing the Plugin interface, classes can listen to execution events
Cucumber comes with several built-in plugins. See the configuration options or the CLIs `--help` command for guidance on
activating them.

* [html](https://github.com/cucumber/html-formatter): Renders a html report of the text execution
* [json](https://github.com/cucumber/cucumber-json-formatter): Renders a json report of the text execution. In maintenance mode.
* [junit](https://github.com/cucumber/junit-xml-formatter): Renders a JUnit xml report of the text execution.
* [progress](https://github.com/cucumber/pretty-formatter): Renders sequence of dots indicating test execution progress.
* [message](https://github.com/cucumber/messages): Logs cucumbers execution as a stream of json messages.
* rerun: Creates a file with scenarios that should be rerun.
* [summary](https://github.com/cucumber/pretty-formatter): Renders summary report of the test execution.
* [testng](https://github.com/cucumber/testng-xml-formatter): Renders a TestNG xml report of the text execution.
* timeline: Renders a timeline of the test execution.
* [unused](https://github.com/cucumber/usage-formatter): Renders a plain text report of unused step definitions.
* [usage](https://github.com/cucumber/usage-formatter): Renders a plain text report of step definition usage statistics.
* [usage-json](https://github.com/cucumber/usage-formatter): Renders a json text report of step definition usage statistics.
* [teamcity](https://github.com/cucumber/teamcity-formatter/): Interspaces Cucumbers output with TeamCity Service Messages

## Custom Plugins ##

By implementing the `Plugin` interface, classes can listen to execution events
inside Cucumber JVM. Consider using a Plugin when creating test execution reports.

## FileSystem ##
Expand Down
25 changes: 25 additions & 0 deletions cucumber-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<properties>
<project.Automatic-Module-Name>io.cucumber.core</project.Automatic-Module-Name>
<apiguardian-api.version>1.1.2</apiguardian-api.version>
<assertj.version>3.27.6</assertj.version>
<jackson.version>2.20.0</jackson.version>
<jsoup.version>1.21.2</jsoup.version>
<junit-jupiter.version>5.14.0</junit-jupiter.version>
Expand Down Expand Up @@ -48,6 +49,13 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-bom</artifactId>
<version>${assertj.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

Expand Down Expand Up @@ -76,6 +84,10 @@
<groupId>io.cucumber</groupId>
<artifactId>testng-xml-formatter</artifactId>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>usage-formatter</artifactId>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>tag-expressions</artifactId>
Expand Down Expand Up @@ -188,12 +200,25 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<version>${hamcrest.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.skyscreamer</groupId>
<artifactId>jsonassert</artifactId>
<version>1.5.3</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import io.cucumber.core.plugin.TimelineFormatter;
import io.cucumber.core.plugin.UnusedStepsSummaryPrinter;
import io.cucumber.core.plugin.UsageFormatter;
import io.cucumber.core.plugin.UsageJsonFormatter;
import io.cucumber.plugin.ConcurrentEventListener;
import io.cucumber.plugin.EventListener;
import io.cucumber.plugin.Plugin;
Expand Down Expand Up @@ -55,6 +56,7 @@ public class PluginOption implements Options.Plugin {
plugins.put("timeline", TimelineFormatter.class);
plugins.put("unused", UnusedStepsSummaryPrinter.class);
plugins.put("usage", UsageFormatter.class);
plugins.put("usage-json", UsageJsonFormatter.class);
plugins.put("teamcity", TeamCityPlugin.class);
PLUGIN_CLASSES = unmodifiableMap(plugins);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,80 +1,54 @@
package io.cucumber.core.plugin;

import io.cucumber.messages.types.Envelope;
import io.cucumber.plugin.ColorAware;
import io.cucumber.plugin.ConcurrentEventListener;
import io.cucumber.plugin.event.EventPublisher;
import io.cucumber.plugin.event.Status;
import io.cucumber.plugin.event.StepDefinedEvent;
import io.cucumber.plugin.event.TestRunFinished;
import io.cucumber.plugin.event.TestStepFinished;
import io.cucumber.usageformatter.MessagesToUsageWriter;
import io.cucumber.usageformatter.UnusedReportSerializer;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import static io.cucumber.core.plugin.Formats.ansi;
import static io.cucumber.core.plugin.Formats.monochrome;
import static java.util.Locale.ROOT;

/**
* Formatter to measure performance of steps. Includes average and median step
* duration.
*/
public final class UnusedStepsSummaryPrinter implements ColorAware, ConcurrentEventListener {

private final Map<String, String> registeredSteps = new TreeMap<>();
private final Set<String> usedSteps = new TreeSet<>();
private final UTF8PrintWriter out;
private Formats formats = ansi();
private final MessagesToUsageWriter writer;

@SuppressWarnings("WeakerAccess") // Used by PluginFactory
public UnusedStepsSummaryPrinter(OutputStream out) {
this.out = new UTF8PrintWriter(out);
this.writer = MessagesToUsageWriter.builder(new UnusedReportSerializer())
.build(out);
}

@Override
public void setEventPublisher(EventPublisher publisher) {
// Record any steps registered
publisher.registerHandlerFor(StepDefinedEvent.class, this::handleStepDefinedEvent);
// Remove any steps that run
publisher.registerHandlerFor(TestStepFinished.class, this::handleTestStepFinished);
// Print summary when done
publisher.registerHandlerFor(TestRunFinished.class, event -> finishReport());
}

private void handleStepDefinedEvent(StepDefinedEvent event) {
registeredSteps.put(event.getStepDefinition().getLocation(), event.getStepDefinition().getPattern());
}

private void handleTestStepFinished(TestStepFinished event) {
String codeLocation = event.getTestStep().getCodeLocation();
if (codeLocation != null) {
usedSteps.add(codeLocation);
}
publisher.registerHandlerFor(Envelope.class, this::write);
}

private void finishReport() {
// Remove all used steps
usedSteps.forEach(registeredSteps::remove);

if (registeredSteps.isEmpty()) {
return;
private void write(Envelope event) {
try {
writer.write(event);
} catch (IOException e) {
throw new IllegalStateException(e);
}

Format format = formats.get(Status.UNUSED.name().toLowerCase(ROOT));
out.println(format.text(registeredSteps.size() + " Unused steps:"));

// Output results when done
for (Entry<String, String> entry : registeredSteps.entrySet()) {
String location = entry.getKey();
String pattern = entry.getValue();
out.println(format.text(location) + " # " + pattern);
// TODO: Plugins should implement the closable interface
// and be closed by Cucumber
if (event.getTestRunFinished().isPresent()) {
try {
writer.close();
} catch (IOException e) {
throw new IllegalStateException(e);
}
}

out.close();
}

@Override
public void setMonochrome(boolean monochrome) {
formats = monochrome ? monochrome() : ansi();
// no-op, no colors printed
}

}
Loading