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
2 changes: 1 addition & 1 deletion java-reporter-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<groupId>io.testomat</groupId>
<artifactId>java-reporter-core</artifactId>
<version>0.9.2</version>
<version>0.9.3</version>
<packaging>jar</packaging>

<name>Testomat.io Reporter Core</name>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.testomat.core.constants;

public class CommonConstants {
public static final String REPORTER_VERSION = "0.9.2";
public static final String REPORTER_VERSION = "0.9.3";

public static final String TESTS_STRING = "tests";
public static final String API_KEY_STRING = "api_key";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,25 @@ public class ReportedTestStorage {
/**
* Stores test execution metadata in the internal storage.
* <p>
* The method adds a new entry containing {@code test_id} and {@code rid}
* only if an entry with the same values does not already exist.
* The check and insertion are performed atomically using synchronization
* to prevent duplicates in concurrent environments.
* The method extracts {@code test_id} and {@code rid} from the provided body
* and stores them as a new entry if an identical pair does not already exist.
* If {@code rid} is {@code null}, the entry is ignored and nothing is stored.
* <p>
* The existence check and insertion are performed inside a synchronized block
* to guarantee atomicity and prevent duplicate entries in concurrent environments.
*
* @param body a map containing test execution data; expected to include
* {@code "test_id"} and {@code "rid"} keys
* @param body a map containing test execution data. Expected to contain
* {@code "rid"} and optionally {@code "test_id"}.
*/
public static void store(Map<String, Object> body) {

Object testId = body.get("test_id");
Object rid = body.get("rid");

if (rid == null) {
return;
}

synchronized (STORAGE) {

boolean exists = STORAGE.stream().anyMatch(m ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.Arrays;
Expand Down Expand Up @@ -177,20 +176,33 @@ void testEmptyArtifactLinkList() {
}

@Test
@DisplayName("Should handle null values in test data")
void testNullValuesInTestData() {
@DisplayName("Should not store duplicate entries with same test_id and rid")
void testNoDuplicateEntries() {
Map<String, Object> body = new HashMap<>();
body.put("test_id", null);
body.put("rid", null);
body.put("test_id", "test-1");
body.put("rid", "rid-1");

ReportedTestStorage.store(body);
ReportedTestStorage.store(body);

List<Map<String, Object>> storage = ReportedTestStorage.getStorage();
Map<String, Object> stored = storage.get(storage.size() - 1);

assertTrue(stored.containsKey("test_id"));
assertTrue(stored.containsKey("rid"));
assertNull(stored.get("test_id"));
assertNull(stored.get("rid"));
assertEquals(1, storage.size());
}

@Test
@DisplayName("Should not store entry with null rid")
void testRidNullNotStored() {

Map<String, Object> body = new HashMap<>();
body.put("test_id", "test-1");
body.put("rid", null);

ReportedTestStorage.store(body);

List<Map<String, Object>> storage =
ReportedTestStorage.getStorage();

assertEquals(0, storage.size());
}
}
2 changes: 1 addition & 1 deletion java-reporter-cucumber/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
<dependency>
<groupId>io.testomat</groupId>
<artifactId>java-reporter-core</artifactId>
<version>0.9.2</version>
<version>0.9.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
Expand Down
2 changes: 1 addition & 1 deletion java-reporter-junit/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
<dependency>
<groupId>io.testomat</groupId>
<artifactId>java-reporter-core</artifactId>
<version>0.9.2</version>
<version>0.9.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
Expand Down
4 changes: 2 additions & 2 deletions java-reporter-karate/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.testomat</groupId>
<artifactId>java-reporter-karate</artifactId>
<version>0.2.4</version>
<version>0.2.5</version>
<packaging>jar</packaging>

<name>Testomat.io Java Reporter Karate</name>
Expand Down Expand Up @@ -52,7 +52,7 @@
<dependency>
<groupId>io.testomat</groupId>
<artifactId>java-reporter-core</artifactId>
<version>0.9.2</version>
<version>0.9.3</version>
</dependency>
<dependency>
<groupId>io.karatelabs</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import io.testomat.core.model.ExceptionDetails;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Objects;
import java.util.UUID;
Expand Down Expand Up @@ -135,27 +136,25 @@ public String getNormalizedStatus(ScenarioRuntime sr) {
}

/**
* Generates a stable runtime identifier (rId) for a Karate test scenario execution.
* <p>
* The identifier is deterministically derived from the scenario location
* (feature file path and scenario line number). This ensures that the same
* scenario always produces the same rId across multiple test runs.
* <p>
* The rId is generated using a name-based UUID calculated from the string:
* <pre>
* &lt;feature-relative-path&gt;:&lt;scenario-line-number&gt;
* </pre>
* Generates a report identifier (rId) for a Karate scenario.
* The identifier is based on the feature path, scenario line, and example index
* (if the scenario is a Scenario Outline).
*
* @param sr the {@link ScenarioRuntime} representing the executed Karate test scenario
* @return a deterministic UUID string uniquely identifying the scenario execution
* @param sr scenario runtime
* @return stable UUID identifying this scenario execution
*/
public String getRid(ScenarioRuntime sr) {
String raw = String.format("%s:%s",
sr.scenario.getFeature().getResource().getRelativePath(),
sr.scenario.getLine()
);
StringBuilder raw = new StringBuilder()
.append(sr.scenario.getFeature().getResource().getRelativePath())
.append(":")
.append(sr.scenario.getLine());

int exampleIndex = sr.scenario.getExampleIndex();
if (exampleIndex >= 0) {
raw.append(":").append(exampleIndex);
}

return UUID.nameUUIDFromBytes(raw.getBytes()).toString();
return UUID.nameUUIDFromBytes(raw.toString().getBytes(StandardCharsets.UTF_8)).toString();
}

private ExceptionDetails createExceptionDetails(Throwable throwable) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
import com.intuit.karate.core.Tags;
import com.intuit.karate.resource.Resource;
import io.testomat.core.model.ExceptionDetails;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.UUID;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -143,13 +145,107 @@ void getRidGeneratesStableUuid() {
ScenarioRuntime sr = mockScenarioRuntime();

when(sr.scenario.getLine()).thenReturn(4);
when(sr.scenario.getExampleIndex()).thenReturn(-1);

String rid1 = extractor.getRid(sr);
String rid2 = extractor.getRid(sr);

assertThat(rid1)
.isNotNull()
.isEqualTo(rid2);
}

@Test
void getRidSameWithoutExampleIndex() {
ScenarioRuntime sr1 = mockScenarioRuntime();
ScenarioRuntime sr2 = mockScenarioRuntime();

when(sr1.scenario.getLine()).thenReturn(5);
when(sr2.scenario.getLine()).thenReturn(5);

when(sr1.scenario.getExampleIndex()).thenReturn(-1);
when(sr2.scenario.getExampleIndex()).thenReturn(-1);

String rid1 = extractor.getRid(sr1);
String rid2 = extractor.getRid(sr2);

assertThat(rid1).isEqualTo(rid2);
}

@Test
void getRidDifferentLinesProduceDifferentRid() {
ScenarioRuntime sr1 = mockScenarioRuntime();
ScenarioRuntime sr2 = mockScenarioRuntime();

when(sr1.scenario.getLine()).thenReturn(1);
when(sr2.scenario.getLine()).thenReturn(2);

when(sr1.scenario.getExampleIndex()).thenReturn(-1);
when(sr2.scenario.getExampleIndex()).thenReturn(-1);

String rid1 = extractor.getRid(sr1);
String rid2 = extractor.getRid(sr2);

assertThat(rid1).isNotEqualTo(rid2);
}

@Test
void getRidIncludesExampleIndex() {
ScenarioRuntime sr1 = mockScenarioRuntime();
ScenarioRuntime sr2 = mockScenarioRuntime();

when(sr1.scenario.getLine()).thenReturn(10);
when(sr2.scenario.getLine()).thenReturn(10);

when(sr1.scenario.getExampleIndex()).thenReturn(0);
when(sr2.scenario.getExampleIndex()).thenReturn(1);

String rid1 = extractor.getRid(sr1);
String rid2 = extractor.getRid(sr2);

assertThat(rid1).isNotEqualTo(rid2);
}

@Test
void getRidDifferentFeatureFilesProduceDifferentRid() {
ScenarioRuntime sr1 = mockScenarioRuntime();
ScenarioRuntime sr2 = mockScenarioRuntime();

when(sr1.scenario.getLine()).thenReturn(1);
when(sr2.scenario.getLine()).thenReturn(1);

when(sr1.scenario.getExampleIndex()).thenReturn(-1);
when(sr2.scenario.getExampleIndex()).thenReturn(-1);

when(sr2.scenario.getFeature()
.getResource()
.getRelativePath())
.thenReturn("features/Other.feature");

String rid1 = extractor.getRid(sr1);
String rid2 = extractor.getRid(sr2);

assertThat(rid1).isNotEqualTo(rid2);
}

@Test
void getRidMatchesExpectedUuid() {
ScenarioRuntime sr = mockScenarioRuntime();

when(sr.scenario.getLine()).thenReturn(4);
when(sr.scenario.getExampleIndex()).thenReturn(-1);

String expected =
UUID.nameUUIDFromBytes(
"features/KarateTest.feature:4"
.getBytes(StandardCharsets.UTF_8)
).toString();

String rid = extractor.getRid(sr);

assertThat(rid).isEqualTo(expected);
}

private ScenarioRuntime mockScenarioRuntime() {
ScenarioRuntime sr = mock(ScenarioRuntime.class);

Expand Down
2 changes: 1 addition & 1 deletion java-reporter-testng/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
<dependency>
<groupId>io.testomat</groupId>
<artifactId>java-reporter-core</artifactId>
<version>0.9.2</version>
<version>0.9.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
Expand Down
Loading