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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- uses: actions/checkout@v4
Expand Down
12 changes: 6 additions & 6 deletions 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.17.0</version>
<version>0.18.0</version>
<packaging>jar</packaging>

<name>Testomat.io Reporter Core</name>
Expand All @@ -33,8 +33,8 @@
</developers>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

Expand Down Expand Up @@ -149,9 +149,9 @@
<version>1.14.1</version>

<configuration>
<complianceLevel>17</complianceLevel>
<source>17</source>
<target>17</target>
<complianceLevel>11</complianceLevel>
<source>11</source>
<target>11</target>
</configuration>

<executions>
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.17.0";
public static final String REPORTER_VERSION = "0.18.0";

public static final String TESTS_STRING = "tests";
public static final String API_KEY_STRING = "api_key";
Expand Down
4 changes: 2 additions & 2 deletions java-reporter-cucumber/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-cucumber</artifactId>
<version>0.8.3</version>
<version>0.9.0</version>
<packaging>jar</packaging>

<name>Testomat.io Java Reporter Cucumber</name>
Expand Down Expand Up @@ -51,7 +51,7 @@
<dependency>
<groupId>io.testomat</groupId>
<artifactId>java-reporter-core</artifactId>
<version>0.17.0</version>
<version>0.18.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public TestResult constructTestRunResult(TestCaseFinished event) {
.withTestId(testDataExtractor.extractTestId(event))
.withFile(fileName)
.withTitle(testDataExtractor.extractTitle(event))
.withRid(event.getTestCase().getId().toString())
.withRid(testDataExtractor.generateRid(event))
.withMessage(exceptionDetails.getMessage())
.withStack(exceptionDetails.getStack());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
import io.cucumber.plugin.event.PickleStepTestStep;
import io.cucumber.plugin.event.Result;
import io.cucumber.plugin.event.TestCase;
import io.cucumber.plugin.event.TestCaseEvent;
import io.cucumber.plugin.event.TestCaseFinished;
import io.cucumber.plugin.event.TestStep;
import io.testomat.core.model.ExceptionDetails;
import io.testomat.cucumber.exception.StatusNormalizerException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
Expand Down Expand Up @@ -54,6 +56,52 @@ public Map<Object, Object> createExample(TestCaseFinished event) {
return params;
}

/**
* Generates a unique run ID for a Cucumber test case.
* Combines the feature URI, scenario name and parameter values extracted from step text.
* Simple values are appended directly, complex values are hashed.
*
* @param event the Cucumber test case finished event
* @return the generated run ID
*/
public String generateRid(TestCaseFinished event) {
TestCase testCase = event.getTestCase();
StringBuilder ridBuilder = new StringBuilder();
ridBuilder.append(testCase.getUri())
.append(".")
.append(testCase.getName());

Map<String, Object> params = new LinkedHashMap<>();
List<TestStep> testSteps = testCase.getTestSteps();
if (testSteps != null) {
for (TestStep testStep : testSteps) {
if (testStep instanceof PickleStepTestStep) {
String stepText = ((PickleStepTestStep) testStep).getStepText();
params.putAll(extractValuesFromStepText(stepText));
}
}
}

if (params.isEmpty()) {
return ridBuilder.toString();
}

for (Map.Entry<String, Object> entry : params.entrySet()) {
Object param = entry.getValue();
String paramString = param != null ? param.toString() : "null";
String paramName = entry.getKey();

if (paramString.length() <= 20 && paramString.matches("[a-zA-Z0-9._-]+")) {
ridBuilder.append("-").append(paramName).append("_").append(paramString);
} else {
int hash = Math.abs(paramString.hashCode());
ridBuilder.append("-").append(paramName).append("_h").append(hash);
}
}

return ridBuilder.toString();
}

/**
* Extracts exception details from test execution result.
*
Expand All @@ -74,7 +122,10 @@ public ExceptionDetails extractExceptionDetails(TestCaseFinished event) {
* @param event the Cucumber test case finished event
* @return test ID if found, null otherwise
*/
public String extractTestId(TestCaseFinished event) {
public String extractTestId(TestCaseEvent event) {
if (event == null) {
return null;
}
TestCase testCase = event.getTestCase();
if (testCase == null || testCase.getTags() == null) {
return null;
Expand Down Expand Up @@ -138,7 +189,7 @@ public String getNormalizedStatus(TestCaseFinished event) {
}

private Map<String, Object> extractValuesFromStepText(String stepText) {
Map<String, Object> values = new HashMap<>();
Map<String, Object> values = new LinkedHashMap<>();

Pattern quotedPattern = Pattern.compile(QUOTED_PATTERN);
Matcher matcher = quotedPattern.matcher(stepText);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package io.testomat.cucumber.listener;

import static io.testomat.core.constants.CommonConstants.FAILED;
import static io.testomat.core.constants.CommonConstants.PASSED;

import io.cucumber.plugin.EventListener;
import io.cucumber.plugin.Plugin;
import io.cucumber.plugin.event.EventPublisher;
import io.cucumber.plugin.event.TestCaseFinished;
import io.cucumber.plugin.event.TestCaseStarted;
import io.cucumber.plugin.event.TestRunFinished;
import io.cucumber.plugin.event.TestRunStarted;
import io.testomat.core.exception.ReportTestResultException;
Expand Down Expand Up @@ -67,6 +71,8 @@ public void setEventPublisher(EventPublisher eventPublisher) {
TestRunStarted.class, this::handleTestRunStarted);
eventPublisher.registerHandlerFor(
TestRunFinished.class, this::handleTestRunFinished);
eventPublisher.registerHandlerFor(
TestCaseStarted.class, this::handleTestCaseStarted);
eventPublisher.registerHandlerFor(
TestCaseFinished.class, this::handleTestCaseFinished);
}
Expand All @@ -83,6 +89,19 @@ void handleTestRunFinished(TestRunFinished event) {
onTestRunFinishedHookAfterExecution(event);
}

void handleTestCaseStarted(TestCaseStarted event) {
String key = event.getTestCase().getUri()
+ "."
+ event.getTestCase().getName();

if (CucumberTestRegistry.isProcessed(key)
&& !CucumberTestRegistry.containsTestId(dataExtractor.extractTestId(event))) {
return;
}

CucumberTestRegistry.add(key, dataExtractor.extractTestId(event));
}

void handleTestCaseFinished(TestCaseFinished event) {
if (!runManager.isActive()) {
return;
Expand All @@ -95,6 +114,15 @@ void handleTestCaseFinished(TestCaseFinished event) {
try {
onTestCaseFinishedHookBeforeExecution(event);
TestResult result = resultConstructor.constructTestRunResult(event);

if (CucumberTestRegistry.containsTestId(result.getTestId())) {
result.setOverwrite(false);

if (!PASSED.equals(result.getStatus())) {
result.setStatus(FAILED);
}
}

runManager.reportTest(result);
onTestCaseFinishedHookAfterExecution(event);
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package io.testomat.cucumber.listener;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
* Cucumber may recreate the listener instance for each retry within the same JVM
* so the processed keys and test IDs are kept here instead of in the listener.
*/
public final class CucumberTestRegistry {

private static final List<String> processedTests =
Collections.synchronizedList(new ArrayList<>());
private static final Set<String> testIds =
Collections.synchronizedSet(new HashSet<>());

private CucumberTestRegistry() {
}

/**
* Checks whether the composite test key was already processed in this JVM.
*
* @param key the composite test key
* @return true if the key was already processed
*/
public static boolean isProcessed(String key) {
return processedTests.contains(key);
}

/**
* Checks whether the test ID was already seen in this JVM.
*
* @param testId the test ID
* @return true if the test ID was already seen
*/
public static boolean containsTestId(String testId) {
return testIds.contains(testId);
}

/**
* Records the composite test key and test ID.
*
* @param key the composite test key
* @param testId the test ID
*/
public static void add(String key, String testId) {
processedTests.add(key);
testIds.add(testId);
}

/**
* Clears all tracked state. Used by tests to isolate scenarios.
*/
static void reset() {
processedTests.clear();
testIds.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.mockito.MockitoAnnotations;

import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
Expand Down Expand Up @@ -58,12 +59,14 @@ void shouldConstructTestRunResultWithAllFields() {

when(testCaseFinished.getTestCase()).thenReturn(testCase);
when(testCase.getUri()).thenReturn(testUri);
when(testCase.getId()).thenReturn(testCaseId);

when(testCase.getName()).thenReturn("Test Scenario");
when(testCase.getTestSteps()).thenReturn(Collections.emptyList());

when(testDataExtractor.extractExceptionDetails(testCaseFinished)).thenReturn(exceptionDetails);
when(testDataExtractor.getNormalizedStatus(testCaseFinished)).thenReturn("PASSED");
when(testDataExtractor.createExample(testCaseFinished)).thenReturn(example);
when(testDataExtractor.extractTestId(testCaseFinished)).thenReturn("@T12345678");
when(testDataExtractor.generateRid(testCaseFinished)).thenReturn("file:///test/path/TestFeature.feature.Test Scenario");
when(testDataExtractor.extractFileName(testCaseFinished)).thenReturn("file:///test/path/TestFeature.feature");
when(testDataExtractor.extractTitle(testCaseFinished)).thenReturn("Test Title");

Expand All @@ -78,7 +81,7 @@ void shouldConstructTestRunResultWithAllFields() {
assertEquals("@T12345678", result.getTestId());
assertEquals("file:///test/path/TestFeature.feature", result.getFile());
assertEquals("Test Title", result.getTitle());
assertEquals(testCaseId.toString(), result.getRid());
assertEquals("file:///test/path/TestFeature.feature.Test Scenario", result.getRid());
assertEquals("Test error", result.getMessage());
assertEquals("Stack trace", result.getStack());
}
Expand All @@ -93,12 +96,14 @@ void shouldConstructTestRunResultWithEmptyExceptionDetails() {

when(testCaseFinished.getTestCase()).thenReturn(testCase);
when(testCase.getUri()).thenReturn(testUri);
when(testCase.getId()).thenReturn(testCaseId);

when(testCase.getName()).thenReturn("Test Scenario");
when(testCase.getTestSteps()).thenReturn(Collections.emptyList());

when(testDataExtractor.extractExceptionDetails(testCaseFinished)).thenReturn(emptyExceptionDetails);
when(testDataExtractor.getNormalizedStatus(testCaseFinished)).thenReturn("FAILED");
when(testDataExtractor.createExample(testCaseFinished)).thenReturn(emptyExample);
when(testDataExtractor.extractTestId(testCaseFinished)).thenReturn(null);
when(testDataExtractor.generateRid(testCaseFinished)).thenReturn("file:///test/path/TestFeature.feature.Test Scenario");
when(testDataExtractor.extractFileName(testCaseFinished)).thenReturn(null);
when(testDataExtractor.extractTitle(testCaseFinished)).thenReturn("Unknown test");

Expand All @@ -113,7 +118,7 @@ void shouldConstructTestRunResultWithEmptyExceptionDetails() {
assertNull(result.getTestId());
assertNull(result.getFile());
assertEquals("Unknown test", result.getTitle());
assertEquals(testCaseId.toString(), result.getRid());
assertEquals("file:///test/path/TestFeature.feature.Test Scenario", result.getRid());
assertNull(result.getMessage());
assertNull(result.getStack());
}
Expand All @@ -130,7 +135,7 @@ void shouldVerifyAllExtractorMethodsAreCalled() {
when(testDataExtractor.extractExceptionDetails(any())).thenReturn(ExceptionDetails.empty());
when(testDataExtractor.getNormalizedStatus(any())).thenReturn("PASSED");
when(testDataExtractor.createExample(any())).thenReturn(new HashMap<>());
when(testDataExtractor.extractTestId(any())).thenReturn(null);
when(testDataExtractor.extractTestId(any(TestCaseFinished.class))).thenReturn(null);
when(testDataExtractor.extractFileName(any())).thenReturn("file:///test.feature");
when(testDataExtractor.extractTitle(any())).thenReturn("Test");

Expand All @@ -142,6 +147,7 @@ void shouldVerifyAllExtractorMethodsAreCalled() {
verify(testDataExtractor).getNormalizedStatus(testCaseFinished);
verify(testDataExtractor).createExample(testCaseFinished);
verify(testDataExtractor).extractTestId(testCaseFinished);
verify(testDataExtractor).generateRid(testCaseFinished);
verify(testDataExtractor).extractFileName(testCaseFinished);
verify(testDataExtractor).extractTitle(testCaseFinished);
}
Expand Down
Loading
Loading