Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get JavaScript to parity with Java #62

Merged
merged 20 commits into from
Nov 14, 2024
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
2 changes: 1 addition & 1 deletion .github/workflows/release-npm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '16'
node-version: '22'
cache: 'npm'
cache-dependency-path: javascript/package-lock.json
- run: npm install-test
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/test-javascript.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ jobs:
matrix:
os:
- ubuntu-latest
node-version: ["16.x", "17.x", "18.x"]
node-version: ["18.x", "20.x", "22.x"]
include:
- os: windows-latest
node-version: "18.x"
node-version: "22.x"
- os: macos-latest
node-version: "18.x"
node-version: "22.x"

steps:
- uses: actions/checkout@v4
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]

### Added
- New methods in JavaScript implementation to match Java ([#62](https://github.com/cucumber/query/pull/62))
- Update dependency @cucumber/messages to v26 ((#52)[https://github.com/cucumber/query/pull/52])
- Update dependency io.cucumber:messages up to v26 ((#53)[https://github.com/cucumber/query/pull/53])

### Changed
- BREAKING CHANGE: `countMostSevereTestStepResultStatus` now returns `EnumMap` with all statuses regardless of count ([#62](https://github.com/cucumber/query/pull/62))
- BREAKING CHANGE: `findAllTestCaseStarted` now omits `TestCaseStarted` messages where there is or will be another attempt ([#62](https://github.com/cucumber/query/pull/62))
- BREAKING CHANGE: Rename `findMostSevereTestStepResulBy` to `findMostSevereTestStepResultBy` ([#62](https://github.com/cucumber/query/pull/62))

### Removed
- BREAKING CHANGE: Remove support for Node.js 16.x and 17.x ([#62](https://github.com/cucumber/query/pull/62))

## [12.2.0] - 2024-06-22
### Changed
- Include pickle name if parameterized ((#44)[https://github.com/cucumber/query/pull/44])
Expand Down
33 changes: 33 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Contributing

For general guidance on contributing to Cucumber, see https://github.com/cucumber/.github/blob/main/CONTRIBUTING.md

## Adding or changing query methods

This is a polyglot repo with several languages adhering to a common suite of acceptance tests. A change should be made consistently across all languages. Currently, the list is:

- Java (reference)
- JavaScript

Java is the reference implementation in the sense that it is responsible for generating the fixtures that are used in the acceptance tests to verify all implementations.

So your playbook for adding a method would be something like:

1. Add the method in `Query.Java` with test(s) in `QueryTest.java`
2. Extend `QueryAcceptanceTest.java` to include verifications for the new method
3. Run `QueryAcceptanceTest::updateExpectedQueryResultFiles` to regenerate the fixtures
4. Implement other languages

## Types

Choosing which type to use in another language based on what we did in Java is an inexact science. This table defines all the decisions we've made so far:

| Java | JavaScript |
|---------------------|-------------------------|
| `Optional<T>` | `T \| undefined`[^1] |
| `List<T>` | `ReadonlyArray<T>` |
| `Map<K, V>` | `Map<K, V>` |
| `EnumMap<K, V>` | `Record<K, V>` |
| `List<Entry<T, V>>` | `ReadonlyArray<[T, V]>` |

[^1]: See <https://github.com/sindresorhus/meta/discussions/7>
29 changes: 16 additions & 13 deletions java/src/main/java/io/cucumber/query/Query.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,14 @@
import io.cucumber.messages.types.Timestamp;

import java.time.Duration;
import java.util.*;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static java.util.Collections.emptyList;
import static java.util.Comparator.comparing;
Expand All @@ -61,6 +56,8 @@
* @see <a href="https://github.com/cucumber/messages?tab=readme-ov-file#message-overview">Cucumber Messages - Message Overview</a>
*/
public final class Query {
private static final Map<TestStepResultStatus, Long> ZEROES_BY_TEST_STEP_RESULT_STATUSES = Arrays.stream(TestStepResultStatus.values())
.collect(Collectors.toMap(identity(), (s) -> 0L));
private final Comparator<TestStepResult> testStepResultComparator = nullsFirst(comparing(o -> o.getStatus().ordinal()));
private final Deque<TestCaseStarted> testCaseStarted = new ConcurrentLinkedDeque<>();
private final Map<String, TestCaseFinished> testCaseFinishedByTestCaseStartedId = new ConcurrentHashMap<>();
Expand All @@ -74,13 +71,15 @@ public final class Query {
private TestRunStarted testRunStarted;
private TestRunFinished testRunFinished;

public Map<TestStepResultStatus, Long> countMostSevereTestStepResultStatus() {
return findAllTestCaseStarted().stream()
.map(this::findMostSevereTestStepResulBy)
public EnumMap<TestStepResultStatus, Long> countMostSevereTestStepResultStatus() {
final EnumMap<TestStepResultStatus, Long> results = new EnumMap<>(ZEROES_BY_TEST_STEP_RESULT_STATUSES);
results.putAll(findAllTestCaseStarted().stream()
.map(this::findMostSevereTestStepResultBy)
.filter(Optional::isPresent)
.map(Optional::get)
.map(TestStepResult::getStatus)
.collect(groupingBy(identity(), LinkedHashMap::new, counting()));
.collect(groupingBy(identity(), LinkedHashMap::new, counting())));
return results;
}

public int countTestCasesStarted() {
Expand All @@ -100,7 +99,11 @@ public List<PickleStep> findAllPickleSteps() {
}

public List<TestCaseStarted> findAllTestCaseStarted() {
return new ArrayList<>(testCaseStarted);
return this.testCaseStarted.stream()
.filter(testCaseStarted1 -> !findTestCaseFinishedBy(testCaseStarted1)
.filter(TestCaseFinished::getWillBeRetried)
.isPresent())
.collect(toList());
}

public Map<Optional<Feature>, List<TestCaseStarted>> findAllTestCaseStartedGroupedByFeature() {
Expand Down Expand Up @@ -136,7 +139,7 @@ public Optional<Feature> findFeatureBy(TestCaseStarted testCaseStarted) {
return findLineageBy(testCaseStarted).flatMap(Lineage::feature);
}

public Optional<TestStepResult> findMostSevereTestStepResulBy(TestCaseStarted testCaseStarted) {
public Optional<TestStepResult> findMostSevereTestStepResultBy(TestCaseStarted testCaseStarted) {
requireNonNull(testCaseStarted);
return findTestStepsFinishedBy(testCaseStarted)
.stream()
Expand Down
4 changes: 2 additions & 2 deletions java/src/test/java/io/cucumber/query/QueryAcceptanceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ private static Map<String, Object> createQueryResults(Query query) {
.map(query::findFeatureBy)
.map(feature -> feature.map(Feature::getName))
.collect(toList()));
results.put("findMostSevereTestStepResulBy", query.findAllTestCaseStarted().stream()
.map(query::findMostSevereTestStepResulBy)
results.put("findMostSevereTestStepResultBy", query.findAllTestCaseStarted().stream()
.map(query::findMostSevereTestStepResultBy)
.map(testStepResult -> testStepResult.map(TestStepResult::getStatus))
.collect(toList()));

Expand Down
18 changes: 18 additions & 0 deletions java/src/test/java/io/cucumber/query/QueryTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.cucumber.query;

import io.cucumber.messages.types.Envelope;
import io.cucumber.messages.types.TestCaseFinished;
import io.cucumber.messages.types.TestCaseStarted;
import io.cucumber.messages.types.Timestamp;
import org.junit.jupiter.api.Test;
Expand All @@ -27,6 +28,23 @@ void retainsInsertionOrderForTestCaseStarted() {
assertThat(query.findAllTestCaseStarted()).containsExactly(a, b, c);
}

@Test
void omitsTestCaseStartedIfFinishedAndWillBeRetried() {
TestCaseStarted a = new TestCaseStarted(0L, randomId(), randomId(), "main", new Timestamp(0L, 0L));
TestCaseFinished b = new TestCaseFinished(a.getId(), new Timestamp(0L, 0L), true);
TestCaseStarted c = new TestCaseStarted(0L, randomId(), randomId(), "main", new Timestamp(0L, 0L));
TestCaseFinished d = new TestCaseFinished(c.getId(), new Timestamp(0L, 0L), false);

Stream.of(a, c)
.map(Envelope::of)
.forEach(query::update);
Stream.of(b, d)
.map(Envelope::of)
.forEach(query::update);

assertThat(query.findAllTestCaseStarted()).containsExactly(c);
}

private static String randomId() {
return UUID.randomUUID().toString();
}
Expand Down
Loading