durations) {
* Calculate the margin of error with a 0.95% confidence interval.
*
* 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 Wikipedia - Margin of error
*/
diff --git a/java/src/main/java/io/cucumber/usageformatter/MessagesToUsageWriter.java b/java/src/main/java/io/cucumber/usageformatter/MessagesToUsageWriter.java
index 42e7aed..e02680c 100644
--- a/java/src/main/java/io/cucumber/usageformatter/MessagesToUsageWriter.java
+++ b/java/src/main/java/io/cucumber/usageformatter/MessagesToUsageWriter.java
@@ -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) {
@@ -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 {
diff --git a/java/src/main/java/io/cucumber/usageformatter/TableFormatter.java b/java/src/main/java/io/cucumber/usageformatter/TableFormatter.java
index 0d27c23..cf2094d 100644
--- a/java/src/main/java/io/cucumber/usageformatter/TableFormatter.java
+++ b/java/src/main/java/io/cucumber/usageformatter/TableFormatter.java
@@ -6,6 +6,10 @@
final class TableFormatter {
+ private TableFormatter(){
+ /* no-op */
+ }
+
static String format(Table table, boolean[] leftAlignColumn) {
StringJoiner joiner = new StringJoiner(lineSeparator(), lineSeparator(), lineSeparator());
int[] longestCellLengthInColumn = findLongestCellLengthInColumn(table);
diff --git a/java/src/main/java/io/cucumber/usageformatter/UsageReport.java b/java/src/main/java/io/cucumber/usageformatter/UsageReport.java
index df7beff..a4ddf8e 100644
--- a/java/src/main/java/io/cucumber/usageformatter/UsageReport.java
+++ b/java/src/main/java/io/cucumber/usageformatter/UsageReport.java
@@ -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;
@@ -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 matches;
StepDefinitionUsage(
- StepDefinitionPattern pattern, SourceReference sourceReference, Statistics duration, List matches
+ StepDefinitionPattern pattern, SourceReference sourceReference, @Nullable Statistics duration, List matches
) {
this.pattern = requireNonNull(pattern);
this.sourceReference = requireNonNull(sourceReference);
@@ -44,7 +45,7 @@ public StepDefinitionPattern getExpression() {
return pattern;
}
- public Statistics getDuration() {
+ public @Nullable Statistics getDuration() {
return duration;
}
@@ -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);
diff --git a/java/src/main/java/io/cucumber/usageformatter/UsageReportBuilder.java b/java/src/main/java/io/cucumber/usageformatter/UsageReportBuilder.java
index a1edb61..420dc07 100644
--- a/java/src/main/java/io/cucumber/usageformatter/UsageReportBuilder.java
+++ b/java/src/main/java/io/cucumber/usageformatter/UsageReportBuilder.java
@@ -5,6 +5,7 @@
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;
@@ -12,7 +13,6 @@
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;
@@ -31,7 +31,7 @@ UsageReport build() {
Map, List>> 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()
@@ -58,7 +58,7 @@ private UsageReport.StepDefinitionUsage createStepDefinitionUsage(StepDefinition
);
}
- private UsageReport.Statistics createStatistics(List stepUsages) {
+ private UsageReport.@Nullable Statistics createStatistics(List stepUsages) {
List durations = stepUsages.stream()
.map(StepUsage::getDuration)
.map(Convertor::toDuration)
@@ -66,8 +66,8 @@ private UsageReport.Statistics createStatistics(List stepUsages) {
return Durations.createStatistics(durations);
}
- private Function> createStepUsage() {
- return testStepFinished -> query.findTestStepBy(testStepFinished)
+ private Optional createStepUsage(TestStepFinished testStepFinished) {
+ return query.findTestStepBy(testStepFinished)
.flatMap(query::findPickleStepBy)
.flatMap(pickleStep -> query
.findPickleBy(testStepFinished)
@@ -80,8 +80,8 @@ private Function> createStepUsage() {
));
}
- private Function> findUnambiguousStepDefinitionBy() {
- return testStepFinished -> query.findTestStepBy(testStepFinished)
+ private Optional findUnambiguousStepDefinitionBy(TestStepFinished testStepFinished) {
+ return query.findTestStepBy(testStepFinished)
.flatMap(query::findUnambiguousStepDefinitionBy);
}
diff --git a/java/src/main/java/io/cucumber/usageformatter/UsageReportSerializer.java b/java/src/main/java/io/cucumber/usageformatter/UsageReportSerializer.java
index fd18923..a94bdfe 100644
--- a/java/src/main/java/io/cucumber/usageformatter/UsageReportSerializer.java
+++ b/java/src/main/java/io/cucumber/usageformatter/UsageReportSerializer.java
@@ -200,6 +200,6 @@ public enum PlainTextFeature {
/**
* Include steps using a step definition.
*/
- INCLUDE_STEPS,
+ INCLUDE_STEPS
}
}
diff --git a/java/src/main/java/io/cucumber/usageformatter/package-info.java b/java/src/main/java/io/cucumber/usageformatter/package-info.java
new file mode 100644
index 0000000..fd22319
--- /dev/null
+++ b/java/src/main/java/io/cucumber/usageformatter/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package io.cucumber.usageformatter;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/java/src/test/java/io/cucumber/usageformatter/DurationsTest.java b/java/src/test/java/io/cucumber/usageformatter/DurationsTest.java
index a32cb3d..50332dc 100644
--- a/java/src/test/java/io/cucumber/usageformatter/DurationsTest.java
+++ b/java/src/test/java/io/cucumber/usageformatter/DurationsTest.java
@@ -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));
}
@@ -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
@@ -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);
}
}
diff --git a/java/src/test/java/io/cucumber/usageformatter/MessagesToUsageWriterAcceptanceTest.java b/java/src/test/java/io/cucumber/usageformatter/MessagesToUsageWriterAcceptanceTest.java
index 7f7e53d..5b7fce2 100644
--- a/java/src/test/java/io/cucumber/usageformatter/MessagesToUsageWriterAcceptanceTest.java
+++ b/java/src/test/java/io/cucumber/usageformatter/MessagesToUsageWriterAcceptanceTest.java
@@ -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;
@@ -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 acceptance() {
- Map 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 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()));
@@ -59,7 +58,7 @@ private static List getSources() {
);
}
- private static T writeUsageReport(TestCase testCase, T out, Builder builder) throws IOException {
+ private static 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)) {
@@ -76,7 +75,7 @@ private static 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
@@ -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
diff --git a/java/src/test/java/io/cucumber/usageformatter/MessagesToUsageWriterTest.java b/java/src/test/java/io/cucumber/usageformatter/MessagesToUsageWriterTest.java
index 945b891..2781a42 100644
--- a/java/src/test/java/io/cucumber/usageformatter/MessagesToUsageWriterTest.java
+++ b/java/src/test/java/io/cucumber/usageformatter/MessagesToUsageWriterTest.java
@@ -3,6 +3,7 @@
import io.cucumber.messages.types.Envelope;
import io.cucumber.messages.types.TestRunFinished;
import io.cucumber.messages.types.TestRunStarted;
+import io.cucumber.messages.types.Timestamp;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
@@ -40,7 +41,9 @@ void it_throws_when_writing_after_close() throws IOException {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
MessagesToUsageWriter writer = create(bytes);
writer.close();
- assertThrows(IOException.class, () -> writer.write(null));
+ assertThrows(IOException.class, () -> writer.write(
+ Envelope.of(new TestRunStarted(new Timestamp(0L, 0), ""))
+ ));
}
@Test
@@ -58,7 +61,7 @@ private static String renderAsSummary(Envelope... messages) throws IOException {
writer.write(message);
}
}
- return new String(bytes.toByteArray(), UTF_8);
+ return bytes.toString(UTF_8);
}
private static MessagesToUsageWriter create(ByteArrayOutputStream bytes) {