Skip to content
Open
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
10 changes: 10 additions & 0 deletions java/.mvn/jvm.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
--add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
26 changes: 22 additions & 4 deletions java/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
<parent>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-parent</artifactId>
<version>4.5.0</version>
<version>5.0.0-SNAPSHOT</version>
</parent>

<artifactId>usage-formatter</artifactId>
<version>0.1.1-SNAPSHOT</version>
<version>0.2.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Usage Formatter</name>
<description>Writes usage statistics for step definitions</description>
Expand Down Expand Up @@ -60,12 +60,12 @@
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>messages</artifactId>
<version>[29.0.1,31.0.0)</version>
<version>[32.0.0-SNAPSHOT,33.0.0)</version>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>query</artifactId>
<version>[14.3.0,15.0.0)</version>
<version>[15.0.0-SNAPSHOT,16.0.0)</version>
</dependency>

<dependency>
Expand Down Expand Up @@ -98,4 +98,22 @@
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<executions>
<execution>
<id>validate</id>
<phase>validate</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
15 changes: 11 additions & 4 deletions java/src/main/java/io/cucumber/usageformatter/Durations.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,34 @@

import io.cucumber.messages.Convertor;
import io.cucumber.usageformatter.UsageReport.Statistics;
import org.jspecify.annotations.Nullable;

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

final class Durations {

private Durations() {
// utility class
}

@Nullable
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(sum),
Convertor.toMessage(mean),
Convertor.toMessage(moe95)
);
}
Expand All @@ -31,7 +38,7 @@ static Statistics createStatistics(List<Duration> durations) {
* 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.
* within {@code mean ± moe95} with 95% probability.
*
* @see <a href="https://en.wikipedia.org/wiki/Margin_of_error">Wikipedia - Margin of error</a>
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,6 @@ public static Builder builder(Serializer serializer) {
return new Builder(serializer);
}

public static final class Builder {
private final Serializer serializer;

private Builder(Serializer serializer) {
this.serializer = requireNonNull(serializer);
}

public MessagesToUsageWriter build(OutputStream out) {
requireNonNull(out);
return new MessagesToUsageWriter(out, serializer);
}
}

@Override
public void close() throws IOException {
if (streamClosed) {
Expand All @@ -79,6 +66,19 @@ public void close() throws IOException {
}
}

public static final class Builder {
private final Serializer serializer;

private Builder(Serializer serializer) {
this.serializer = requireNonNull(serializer);
}

public MessagesToUsageWriter build(OutputStream out) {
requireNonNull(out);
return new MessagesToUsageWriter(out, serializer);
}
}

@FunctionalInterface
public interface Serializer {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

final class TableFormatter {

private TableFormatter(){
// utility class
}

static String format(Table table, boolean[] leftAlignColumn) {
StringJoiner joiner = new StringJoiner(lineSeparator(), lineSeparator(), lineSeparator());
int[] longestCellLengthInColumn = findLongestCellLengthInColumn(table);
Expand Down
11 changes: 6 additions & 5 deletions java/src/main/java/io/cucumber/usageformatter/UsageReport.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.cucumber.messages.types.Location;
import io.cucumber.messages.types.SourceReference;
import io.cucumber.messages.types.StepDefinitionPattern;
import org.jspecify.annotations.Nullable;

import java.util.List;
import java.util.Optional;
Expand All @@ -28,11 +29,11 @@ public static final class StepDefinitionUsage {

private final StepDefinitionPattern pattern;
private final SourceReference sourceReference;
private final Statistics duration;
private final @Nullable Statistics duration;
private final List<StepUsage> matches;

StepDefinitionUsage(
StepDefinitionPattern pattern, SourceReference sourceReference, Statistics duration, List<StepUsage> matches
StepDefinitionPattern pattern, SourceReference sourceReference, @Nullable Statistics duration, List<StepUsage> matches
) {
this.pattern = requireNonNull(pattern);
this.sourceReference = requireNonNull(sourceReference);
Expand All @@ -44,7 +45,7 @@ public StepDefinitionPattern getExpression() {
return pattern;
}

public Statistics getDuration() {
public @Nullable Statistics getDuration() {
return duration;
}

Expand Down Expand Up @@ -94,9 +95,9 @@ public static final class StepUsage {
private final String text;
private final Duration duration;
private final String uri;
private final Location location;
private final @Nullable Location location;

StepUsage(String text, Duration duration, String uri, Location location) {
StepUsage(String text, Duration duration, String uri, @Nullable Location location) {
this.text = requireNonNull(text);
this.duration = requireNonNull(duration);
this.uri = requireNonNull(uri);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
import io.cucumber.messages.types.TestStepFinished;
import io.cucumber.query.Query;
import io.cucumber.usageformatter.UsageReport.StepUsage;
import org.jspecify.annotations.Nullable;

import java.time.Duration;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;

import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.groupingBy;
Expand All @@ -31,7 +31,7 @@ UsageReport build() {
Map<Optional<StepDefinition>, List<Optional<StepUsage>>> testStepsFinishedByStepDefinition = query
.findAllTestStepFinished()
.stream()
.collect(groupingBy(findUnambiguousStepDefinitionBy(), LinkedHashMap::new, mapping(createStepUsage(), toList())));
.collect(groupingBy(this::findUnambiguousStepDefinitionBy, LinkedHashMap::new, mapping(this::createStepUsage, toList())));

// Add unused step definitions
query.findAllStepDefinitions().stream()
Expand All @@ -58,16 +58,16 @@ private UsageReport.StepDefinitionUsage createStepDefinitionUsage(StepDefinition
);
}

private UsageReport.Statistics createStatistics(List<StepUsage> stepUsages) {
private UsageReport.@Nullable Statistics createStatistics(List<StepUsage> stepUsages) {
List<Duration> durations = stepUsages.stream()
.map(StepUsage::getDuration)
.map(Convertor::toDuration)
.collect(toList());
return Durations.createStatistics(durations);
}

private Function<TestStepFinished, Optional<StepUsage>> createStepUsage() {
return testStepFinished -> query.findTestStepBy(testStepFinished)
private Optional<StepUsage> createStepUsage(TestStepFinished testStepFinished) {
return query.findTestStepBy(testStepFinished)
.flatMap(query::findPickleStepBy)
.flatMap(pickleStep -> query
.findPickleBy(testStepFinished)
Expand All @@ -80,8 +80,8 @@ private Function<TestStepFinished, Optional<StepUsage>> createStepUsage() {
));
}

private Function<TestStepFinished, Optional<StepDefinition>> findUnambiguousStepDefinitionBy() {
return testStepFinished -> query.findTestStepBy(testStepFinished)
private Optional<StepDefinition> findUnambiguousStepDefinitionBy(TestStepFinished testStepFinished) {
return query.findTestStepBy(testStepFinished)
.flatMap(query::findUnambiguousStepDefinitionBy);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,6 @@ public enum PlainTextFeature {
/**
* Include steps using a step definition.
*/
INCLUDE_STEPS,
INCLUDE_STEPS
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@NullMarked
package io.cucumber.usageformatter;

import org.jspecify.annotations.NullMarked;
22 changes: 10 additions & 12 deletions java/src/test/java/io/cucumber/usageformatter/DurationsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ public class DurationsTest {
void testToBigDecimalSeconds() {
assertThat(toBigDecimalSeconds(Duration.ofMillis(0))).isEqualTo(BigDecimal.valueOf(0, 9));
assertThat(toBigDecimalSeconds(Duration.ofMillis(100))).isEqualTo(BigDecimal.valueOf(100_000_000, 9));
assertThat(toBigDecimalSeconds(Duration.ofMillis(1000))).isEqualTo(BigDecimal.valueOf(1_000_000_000, 9));
assertThat(toBigDecimalSeconds(createDuration(0L, 0L))).isEqualTo(BigDecimal.valueOf(0, 9));
assertThat(toBigDecimalSeconds(createDuration(0L, 100_000_000L))).isEqualTo(BigDecimal.valueOf(100_000_000, 9));
assertThat(toBigDecimalSeconds(Duration.ofSeconds(1))).isEqualTo(BigDecimal.valueOf(1_000_000_000, 9));
assertThat(toBigDecimalSeconds(createDuration(0L, 0))).isEqualTo(BigDecimal.valueOf(0, 9));
assertThat(toBigDecimalSeconds(createDuration(0L, 100_000_000))).isEqualTo(BigDecimal.valueOf(100_000_000, 9));
assertThat(toBigDecimalSeconds(createDuration(1L, 0))).isEqualTo(BigDecimal.valueOf(1_000_000_000, 9));
}

Expand All @@ -38,10 +38,9 @@ void createStatistics_with_even_number_of_values() {
Duration.ofSeconds(4)
));

assertThat(statistics).isNotNull();
assertThat(statistics.getSum()).isEqualTo(createDuration(8L, 0L));
assertThat(statistics.getMean()).isEqualTo(createDuration(2L, 0L));
assertThat(statistics.getMoe95()).isEqualTo(createDuration(1L, 224744871L));
assertThat(statistics).extracting(Statistics::getSum).isEqualTo(createDuration(8L, 0));
assertThat(statistics).extracting(Statistics::getMean).isEqualTo(createDuration(2L, 0));
assertThat(statistics).extracting(Statistics::getMoe95).isEqualTo(createDuration(1L, 224744871));
}

@Test
Expand All @@ -52,13 +51,12 @@ void createStatistics_with_odd_number_of_values() {
Duration.ofSeconds(4)
));

assertThat(statistics).isNotNull();
assertThat(statistics.getSum()).isEqualTo(createDuration(7L, 0L));
assertThat(statistics.getMean()).isEqualTo(createDuration(2L, 333333333L));
assertThat(statistics.getMoe95()).isEqualTo(createDuration(1L, 440164599L));
assertThat(statistics).extracting(Statistics::getSum).isEqualTo(createDuration(7L, 0));
assertThat(statistics).extracting(Statistics::getMean).isEqualTo(createDuration(2L, 333333333));
assertThat(statistics).extracting(Statistics::getMoe95).isEqualTo(createDuration(1L, 440164599));
}

private static io.cucumber.messages.types.Duration createDuration(long seconds, long nanos) {
private static io.cucumber.messages.types.Duration createDuration(long seconds, int nanos) {
return new io.cucumber.messages.types.Duration(seconds, nanos);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import io.cucumber.messages.NdjsonToMessageIterable;
import io.cucumber.messages.types.Envelope;
import io.cucumber.usageformatter.MessagesToUsageWriter.Builder;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
Expand All @@ -22,20 +21,20 @@

import static io.cucumber.usageformatter.Jackson.OBJECT_MAPPER;
import static io.cucumber.usageformatter.Jackson.PRETTY_PRINTER;
import static io.cucumber.usageformatter.MessagesToUsageWriter.builder;
import static java.nio.file.Files.readAllBytes;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Objects.requireNonNull;
import static org.assertj.core.api.Assertions.assertThat;

class MessagesToUsageWriterAcceptanceTest {
private static final NdjsonToMessageIterable.Deserializer deserializer = (json) -> OBJECT_MAPPER.readValue(json, Envelope.class);
private static final NdjsonToMessageIterable.Deserializer deserializer = json -> OBJECT_MAPPER.readValue(json, Envelope.class);
private static final MessagesToUsageWriter.Serializer jsonSerializer = OBJECT_MAPPER.writer(PRETTY_PRINTER)::writeValue;

static List<TestCase> acceptance() {
Map<String, Builder> formats = new LinkedHashMap<>();
formats.put("json", builder(jsonSerializer));
formats.put("unused.txt", builder(new UnusedReportSerializer()));
formats.put("step-definitions.txt", builder(UsageReportSerializer.builder().build()));
formats.put("with-steps.txt", builder(UsageReportSerializer.builder()
Map<String, MessagesToUsageWriter.Builder> formats = new LinkedHashMap<>();
formats.put("json", MessagesToUsageWriter.builder(jsonSerializer));
formats.put("unused.txt", MessagesToUsageWriter.builder(new UnusedReportSerializer()));
formats.put("step-definitions.txt", MessagesToUsageWriter.builder(UsageReportSerializer.builder().build()));
formats.put("with-steps.txt", MessagesToUsageWriter.builder(UsageReportSerializer.builder()
.feature(UsageReportSerializer.PlainTextFeature.INCLUDE_STEPS, true)
.maxStepsPerStepDefinition(5)
.build()));
Expand All @@ -59,7 +58,7 @@ private static List<Path> getSources() {
);
}

private static <T extends OutputStream> T writeUsageReport(TestCase testCase, T out, Builder builder) throws IOException {
private static <T extends OutputStream> T writeUsageReport(TestCase testCase, T out, MessagesToUsageWriter.Builder builder) throws IOException {
try (InputStream in = Files.newInputStream(testCase.source)) {
try (NdjsonToMessageIterable envelopes = new NdjsonToMessageIterable(in, deserializer)) {
try (MessagesToUsageWriter writer = builder.build(out)) {
Expand All @@ -76,7 +75,7 @@ private static <T extends OutputStream> T writeUsageReport(TestCase testCase, T
@MethodSource("acceptance")
void test(TestCase testCase) throws IOException {
ByteArrayOutputStream bytes = writeUsageReport(testCase, new ByteArrayOutputStream(), testCase.builder);
assertThat(bytes.toString()).isEqualToIgnoringNewLines(new String(readAllBytes(testCase.expected)));
assertThat(bytes.toString(UTF_8)).isEqualToIgnoringNewLines(Files.readString(testCase.expected));
}

@ParameterizedTest
Expand All @@ -95,18 +94,18 @@ void updateExpectedFiles(TestCase testCase) throws IOException {
static class TestCase {
private final Path source;
private final String format;
private final Builder builder;
private final MessagesToUsageWriter.Builder builder;
private final Path expected;

private final String name;

TestCase(Path source, String format, Builder builder) {
TestCase(Path source, String format, MessagesToUsageWriter.Builder builder) {
this.source = source;
this.format = format;
this.builder = builder;
String fileName = source.getFileName().toString();
this.name = fileName.substring(0, fileName.lastIndexOf(".ndjson"));
this.expected = source.getParent().resolve(name + "." + format);
this.expected = requireNonNull(source.getParent()).resolve(name + "." + format);
}

@Override
Expand Down
Loading
Loading