Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .github/renovate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"github>cucumber/renovate-config",
"github>cucumber/renovate-config:messages-range-strategy-widen"
]
}
18 changes: 18 additions & 0 deletions .github/workflows/release-github.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Release GitHub

on:
push:
branches: [release/*]

jobs:
create-github-release:
name: Create GitHub Release and Git tag
runs-on: ubuntu-latest
environment: Release
permissions:
contents: write
steps:
- uses: actions/checkout@v5
- uses: cucumber/[email protected]
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
25 changes: 25 additions & 0 deletions .github/workflows/release-mvn.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Release Maven

on:
push:
branches: [release/*]

jobs:
publish-mvn:
name: Publish Maven Package
runs-on: ubuntu-latest
environment: Release
steps:
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '17'
cache: 'maven'
- uses: cucumber/[email protected]
with:
gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
gpg-passphrase: ${{ secrets.GPG_PASSPHRASE }}
nexus-username: ${{ secrets.SONATYPE_USERNAME }}
nexus-password: ${{ secrets.SONATYPE_PASSWORD }}
working-directory: java
41 changes: 41 additions & 0 deletions .github/workflows/test-java.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: test-java

on:
push:
branches:
- main
- renovate/**
paths:
- java/**
- testdata/**
- .github/**
pull_request:
branches:
- main
paths:
- java/**
- testdata/**
- .github/**
workflow_call:

jobs:
test-java:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
os:
- ubuntu-latest
java: ['17', '21']

steps:
- uses: actions/checkout@v5

- uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: ${{ matrix.java }}
cache: 'maven'

- run: mvn verify
working-directory: java
36 changes: 36 additions & 0 deletions .github/workflows/test-testdata.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: test-testdata

on:
push:
branches:
- main
paths:
- testdata/**
pull_request:
branches:
- main
paths:
- testdata/**

jobs:
test-testdata:
runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v5

- uses: actions/setup-node@v5
with:
cache: 'npm'
cache-dependency-path: testdata/package-lock.json

- run: npm ci
working-directory: testdata

- name: check repository is not dirty
run: "[[ -z $(git status --porcelain) ]]"

- name: show diff
if: ${{ failure() }}
run: git status --porcelain
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.idea/
*.iml
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Changelog

All notable changes to this project will be documented in this file.

The formatter is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- Java implementation ([#1](https://github.com/cucumber/usage-formatter/pull/1) M.P. Korstanje)

[Unreleased]: https://github.com/cucumber/usage-formatter/compare/f21831df98c5e57a53950a92b068df8321e45bce...HEAD
78 changes: 76 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,76 @@
# usage-formatter
Prints usage statatistics for step definitions
# Usage Formatter
⚠️ This is an internal package; you don't need to install it in order to use the Usage Formatter.

[![Maven Central](https://img.shields.io/maven-central/v/io.cucumber/cucumber-json-formatter.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:io.cucumber%20AND%20a:usage-formatter)

Writes usage statistics for step definitions

## Features

Can render a plain text report showing which step definitions are used by which
steps and some statistics for long each took. The error from the mean is the 95%
confidence interval assuming a normal distribution.

```
Expression/Text Duration Mean ± Error Location
an order for {string} 0.009s 0.001s ± 0.000s samples/multiple-features/multiple-features.ts:3
an order for "eggs" 0.001s samples/multiple-features/multiple-features-1.feature:3
an order for "milk" 0.001s samples/multiple-features/multiple-features-1.feature:6
an order for "bread" 0.001s samples/multiple-features/multiple-features-1.feature:9
an order for "batteries" 0.001s samples/multiple-features/multiple-features-2.feature:3
an order for "light bulbs" 0.001s samples/multiple-features/multiple-features-2.feature:6
4 more
```

The output can also be rendered as a json report.

```json
{
"stepDefinitions": [
{
"sourceReference": {
"uri": "samples/multiple-features/multiple-features.ts",
"location": {
"line": 3
}
},
"duration": {
"sum": {
"seconds": 0,
"nanos": 9000000
},
"mean": {
"seconds": 0,
"nanos": 1000000
},
"moe95": {
"seconds": 0,
"nanos": 0
}
},
"expression": {
"source": "an order for {string}",
"type": "CUCUMBER_EXPRESSION"
},
"matches": [
{
"text": "an order for \"eggs\"",
"duration": {
"seconds": 0,
"nanos": 1000000
},
"uri": "samples/multiple-features/multiple-features-1.feature",
"location": {
"line": 3,
"column": 3
}
},
...
```


## Contributing

Each language implementation validates itself against the examples in the
`testdata` folder. See the [testdata/README.md](testdata/README.md) for more
information.
4 changes: 4 additions & 0 deletions java/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.idea/
*.iml
target/
pom.xml.versionsBackup
101 changes: 101 additions & 0 deletions java/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-parent</artifactId>
<version>4.4.0</version>
</parent>

<artifactId>usage-formatter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Usage Formatter</name>
<description>Writes usage statistics for step definitions</description>
<url>https://github.com/cucumber/usage-formatter</url>

<properties>
<project.Automatic-Module-Name>io.cucumber.usageformatter</project.Automatic-Module-Name>
<project.build.outputTimestamp>1758245480</project.build.outputTimestamp>
</properties>

<scm>
<connection>scm:git:git://github.com/cucumber/usage-formatter.git</connection>
<developerConnection>scm:git:[email protected]:cucumber/usage-formatter.git</developerConnection>
<url>git://github.com/cucumber/usage-formatter.git</url>
<tag>HEAD</tag>
</scm>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>6.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>2.20.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-bom</artifactId>
<version>3.27.6</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>messages</artifactId>
<version>[29.0.1,31.0.0)</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>query</artifactId>
<version>[14.3.0,15.0.0)</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
<scope>test</scope>
</dependency>

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

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
60 changes: 60 additions & 0 deletions java/src/main/java/io/cucumber/usageformatter/Durations.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package io.cucumber.usageformatter;

import io.cucumber.messages.Convertor;
import io.cucumber.usageformatter.UsageReport.Statistics;

import java.math.BigDecimal;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.TimeUnit;

final class Durations {
static Statistics createStatistics(List<Duration> durations) {
if (durations.isEmpty()) {
return null;
}

Duration sum = durations.stream()
.reduce(Duration::plus)
// Can't happen
.orElse(Duration.ZERO);
Duration mean = sum.dividedBy(durations.size());
Duration moe95 = calculateMarginOfError95(durations, mean);
return new Statistics(
Convertor.toMessage(sum),
Convertor.toMessage(mean),
Convertor.toMessage(moe95)
);
}

/**
* Calculate the margin of error with a 0.95% confidence interval.
* <p>
* So assuming a normal distribution, the duration of a step will fall
* within {@code mean ± moe95} with 95% probability.
*
* @see <a href="https://en.wikipedia.org/wiki/Margin_of_error">Wikipedia - Margin of error</a>
*/
private static Duration calculateMarginOfError95(List<Duration> durations, Duration mean) {
BigDecimal meanSeconds = toBigDecimalSeconds(mean);
BigDecimal variance = durations.stream()
.map(Durations::toBigDecimalSeconds)
.map(durationSeconds -> durationSeconds.subtract(meanSeconds).pow(2))
.reduce(BigDecimal::add)
.orElse(BigDecimal.ZERO);
// TODO: With Java 17, use BigDecimal.sqrt and
double marginOfError = 2 * Math.sqrt(variance.doubleValue()) / durations.size();
// TODO: With Java 17, BigDecimal.divideAndRemainder for seconds and nanos
long seconds = (long) Math.floor(marginOfError);
long nanos = (long) Math.floor((marginOfError - seconds) * TimeUnit.SECONDS.toNanos(1));
return Duration.ofSeconds(seconds, nanos);
}

static BigDecimal toBigDecimalSeconds(Duration duration) {
return BigDecimal.valueOf(duration.getSeconds()).add(BigDecimal.valueOf(duration.getNano(), 9));
}

static BigDecimal toBigDecimalSeconds(io.cucumber.messages.types.Duration duration) {
return BigDecimal.valueOf(duration.getSeconds()).add(BigDecimal.valueOf(duration.getNanos(), 9));
}
}
Loading