Skip to content
Draft
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
63 changes: 55 additions & 8 deletions src/main/java/com/microfocus/bdd/Bdd2Octane.java
Original file line number Diff line number Diff line change
Expand Up @@ -171,20 +171,67 @@ private void writeFeatureToXML(XMLStreamWriter writer, OctaneFeature octaneFeatu
}

private void mergeScenario(BddFrameworkHandler bddFrameworkHandler, OctaneScenario scenario) {
Iterator<OctaneStep> stepsIterator = scenario.getSteps().iterator();
while (stepsIterator.hasNext()) {
OctaneStep octaneStep = stepsIterator.next();
List<OctaneStep> steps = scenario.getSteps();

// Pass 1: let the handler fill status for all steps
for (OctaneStep octaneStep : steps) {
octaneStep.setAddSystemErrors(addSystemErrors);
bddFrameworkHandler.fillStep(octaneStep);
if (octaneStep.getStatus() != Status.PASSED) {
}

// Pass 2: find the failure point
int failureIndex = -1;

// First, look for a step explicitly marked FAILED or SKIPPED by the handler
for (int i = 0; i < steps.size(); i++) {
Status status = steps.get(i).getStatus();
if (status == Status.FAILED || status == Status.SKIPPED) {
failureIndex = i;
break;
}
}
while (stepsIterator.hasNext()) {
//todo, need to confirm whether to ignore steps or mark them as skipped.
OctaneStep octaneStep = stepsIterator.next();
octaneStep.setStatus(Status.SKIPPED);

// If no explicit failure, look for null status (handler couldn't determine)
if (failureIndex < 0) {
Optional<String> errorMessage = bddFrameworkHandler.getErrorMessage();
if (errorMessage.isPresent()) {
for (int i = 0; i < steps.size(); i++) {
if (steps.get(i).getStatus() == null) {
steps.get(i).setStatus(Status.FAILED);
steps.get(i).setErrorMessage(errorMessage.get());
failureIndex = i;
break;
}
}
// Last resort: all steps set to PASSED but handler has error
if (failureIndex < 0 && !steps.isEmpty()) {
int lastIndex = steps.size() - 1;
steps.get(lastIndex).setStatus(Status.FAILED);
steps.get(lastIndex).setErrorMessage(errorMessage.get());
failureIndex = lastIndex;
}
}
}

// Apply BDD invariant: PASSED* FAILED SKIPPED*
if (failureIndex >= 0) {
for (int i = 0; i < failureIndex; i++) {
if (steps.get(i).getStatus() == null) {
steps.get(i).setStatus(Status.PASSED);
}
}
for (int i = failureIndex + 1; i < steps.size(); i++) {
steps.get(i).setStatus(Status.SKIPPED);
}
}

// Defensive: ensure no step has null status (would write as "undefined")
for (OctaneStep step : steps) {
if (step.getStatus() == null) {
step.setStatus(Status.PASSED);
}
}

scenario.markMerged();
}

Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/microfocus/bdd/CucumberJsHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ public Optional<String> findFeatureFile() {
return Optional.empty();
}

@Override
public Optional<String> getErrorMessage() {
return Optional.ofNullable(errorMessage);
}

public String getTestCaseElementName() {
return "testsuite";
}
Expand Down
25 changes: 12 additions & 13 deletions src/main/java/com/microfocus/bdd/CucumberJvmHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -332,19 +332,14 @@ public void fillStep(OctaneStep octaneStep) {
return;
}
if (failedStep == null) {
octaneStep.setStatus(Status.PASSED);
if (errorMessage == null) {
octaneStep.setStatus(Status.PASSED);
}
// else: leave null — test failed but can't identify which step
return;
}
if (octaneStep.getName().equals(failedStep)) {
octaneStep.setStatus(Status.FAILED);
if(octaneStep.getAddSystemErrors() && systemErrors!= null && !systemErrors.isEmpty()){
octaneStep.setErrorMessage(errorMessage + "\n" +
"-------------------------------\n" +
"SYSTEM-ERRORS:\n"+systemErrors);
} else {
octaneStep.setErrorMessage(errorMessage);
}
} else if((octaneStep.getKeyword() + " " + octaneStep.getName()).equals(failedStep)){
if (octaneStep.getName().equals(failedStep) ||
(octaneStep.getKeyword() + " " + octaneStep.getName()).equals(failedStep)) {
octaneStep.setStatus(Status.FAILED);
if(octaneStep.getAddSystemErrors() && systemErrors!= null && !systemErrors.isEmpty()){
octaneStep.setErrorMessage(errorMessage + "\n" +
Expand All @@ -353,9 +348,13 @@ public void fillStep(OctaneStep octaneStep) {
} else {
octaneStep.setErrorMessage(errorMessage);
}
} else {
octaneStep.setStatus(Status.PASSED);
}
// else: leave null — step didn't match the failed step name
}

@Override
public Optional<String> getErrorMessage() {
return Optional.ofNullable(errorMessage);
}

@Override
Expand Down
8 changes: 6 additions & 2 deletions src/main/java/com/microfocus/bdd/CucumberRubyHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -148,15 +148,19 @@ public void fillStep(OctaneStep octaneStep) {
if (octaneStep.getName().equals(failedStep)) {
octaneStep.setStatus(Status.FAILED);
octaneStep.setErrorMessage(errorMessage);
} else {
octaneStep.setStatus(Status.PASSED);
}
// else: leave null — step didn't match the failed step name
} else if (statusTag.equals(Status.SKIPPED)) {
octaneStep.setStatus(Status.SKIPPED);
} else {
}
}

@Override
public Optional<String> getErrorMessage() {
return Optional.ofNullable(errorMessage);
}

@Override
public String getTestCaseElementName() {
return "testcase";
Expand Down
4 changes: 1 addition & 3 deletions src/main/java/com/microfocus/bdd/JunitReportReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,7 @@ private Element getElement(StartElement startElementEvent) {
XMLEvent event = reader.nextEvent();
if (event.isCharacters()) {
Characters cev = (Characters) event;
if (!cev.isWhiteSpace()) {
element.setText(element.getText() + cev.getData());
}
element.setText(element.getText() + cev.getData());
} else if (event.isStartElement()) {
StartElement startEvent = (StartElement) event;
element.appendChild(getElement(startEvent));
Expand Down
8 changes: 6 additions & 2 deletions src/main/java/com/microfocus/bdd/PhpBehatHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,18 @@ public void fillStep(OctaneStep octaneStep) {
if (errorMessage.contains(octaneStep.getName())) {
octaneStep.setStatus(Status.FAILED);
octaneStep.setErrorMessage(errorMessage);
} else {
octaneStep.setStatus(Status.PASSED);
}
// else: leave null — step didn't match the error message
} else {
octaneStep.setStatus(Status.SKIPPED);
}
}

@Override
public Optional<String> getErrorMessage() {
return Optional.ofNullable(errorMessage);
}

@Override
public String getTestCaseElementName() {
return "testcase";
Expand Down
11 changes: 8 additions & 3 deletions src/main/java/com/microfocus/bdd/PythonBehaveHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,17 @@ public void fillStep(OctaneStep octaneStep) {
if (stepName.equals(failedStepName.get())) {
octaneStep.setStatus(Status.FAILED);
octaneStep.setErrorMessage(errorMessage);
} else {
octaneStep.setStatus(Status.PASSED);
}
} else {
// else: leave null — step didn't match the failed step name
} else if (errorMessage == null) {
octaneStep.setStatus(Status.PASSED);
}
// else: leave null — test failed but can't identify which step
}

@Override
public Optional<String> getErrorMessage() {
return Optional.ofNullable(errorMessage);
}

@Override
Expand Down
10 changes: 9 additions & 1 deletion src/main/java/com/microfocus/bdd/PythonRadishHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,15 @@ public void fillStep(OctaneStep octaneStep) {
return;
}

octaneStep.setStatus(Status.PASSED);
if (errorMessage == null) {
octaneStep.setStatus(Status.PASSED);
}
// else: leave null — test failed but can't identify which step
}

@Override
public Optional<String> getErrorMessage() {
return Optional.ofNullable(errorMessage);
}

@Override
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/com/microfocus/bdd/api/BddFrameworkHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,15 @@ public interface BddFrameworkHandler {
*/
String getTestCaseElementName();

/**
* Returns the error message if the test case failed at the test-case level,
* regardless of whether the specific failed step could be identified.
* Used as a fallback when fillStep() cannot match the failure to a specific step.
*
* @return Optional containing the error message if the test case failed, empty otherwise.
*/
default Optional<String> getErrorMessage() {
return Optional.empty();
}

}
83 changes: 82 additions & 1 deletion src/test/java/com/microfocus/bdd/ut/Bdd2OctaneTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Copyright 2021-2023 Open Text
*
* The only warranties for products and services of Open Text and
* its affiliates and licensors (Open Text) are as may be set forth
* its affiliates and licensors ("Open Text") are as may be set forth
* in the express warranty statements accompanying such products and services.
* Nothing herein should be construed as constituting an additional warranty.
* Open Text shall not be liable for technical or editorial errors or
Expand Down Expand Up @@ -31,5 +31,86 @@
*/
package com.microfocus.bdd.ut;

import com.microfocus.bdd.Bdd2Octane;
import org.junit.Assert;
import org.junit.Test;

import java.io.File;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Bdd2OctaneTest {

@Test
public void testFailedTestWithUnmatchedStepNameIsNotReportedAsPassed() throws Exception {
List<String> reportFiles = Arrays.asList(
"src/test/resources/cucumber-jvm/unmatched-step/TEST-unmatched-step.xml");
List<String> featureFiles = Arrays.asList(
"src/test/resources/features/robustgherkin.feature");
String outputFile = "target/test-unmatched-step-result.xml";

Bdd2Octane bdd2Octane = new Bdd2Octane(reportFiles, featureFiles, outputFile, "cucumber-jvm");
bdd2Octane.run();

File result = new File(outputFile);
Assert.assertTrue("Output file should exist", result.exists());

String content = new String(Files.readAllBytes(result.toPath()));
Assert.assertTrue("Output should contain a failed step",
content.contains("status=\"failed\""));
Assert.assertTrue("Steps after failure should be skipped",
content.contains("status=\"skipped\""));

// Verify BDD invariant: no PASSED step should appear after a FAILED step
int failedPos = content.indexOf("status=\"failed\"");
Assert.assertTrue("Should have a failed step", failedPos >= 0);
String afterFailure = content.substring(failedPos);
Assert.assertFalse("No step should be PASSED after a FAILED step (BDD invariant)",
afterFailure.contains("status=\"passed\""));
}

@Test
public void testCucumberJsUnmatchedFailureProducesPassPassFailSkipPattern() throws Exception {
// CucumberJs tracks passed steps individually, so when the failed step name
// doesn't match (e.g. trailing space), passed steps are still correctly identified.
// This proves the exact pass/pass/fail/skip/skip pattern the user reported.
List<String> reportFiles = Arrays.asList(
"src/test/resources/cucumber-js/unmatched-failure/cucumber-js-unmatched-failure.xml");
List<String> featureFiles = Arrays.asList(
"src/test/resources/features/robustgherkin.feature");
String outputFile = "target/test-cucumberjs-unmatched-failure-result.xml";

Bdd2Octane bdd2Octane = new Bdd2Octane(reportFiles, featureFiles, outputFile, "cucumber-js");
bdd2Octane.run();

File result = new File(outputFile);
Assert.assertTrue("Output file should exist", result.exists());

String content = new String(Files.readAllBytes(result.toPath()));

// Extract all step statuses in order
List<String> statuses = new ArrayList<>();
Matcher m = Pattern.compile("status=\"(\\w+)\"").matcher(content);
while (m.find()) {
statuses.add(m.group(1));
}

// 3 background + 6 scenario = 9 steps
// Steps 1-5 passed (in XML), step 6 failed (name mismatch), steps 7-9 not in XML
// Expected: passed(x5), failed, skipped(x3)
Assert.assertEquals("Should have 9 steps, got: " + statuses, 9, statuses.size());

for (int i = 0; i < 5; i++) {
Assert.assertEquals("Step " + (i + 1) + " should be passed", "passed", statuses.get(i));
}
Assert.assertEquals("Step 6 should be failed (first unmatched step with error)",
"failed", statuses.get(5));
for (int i = 6; i < 9; i++) {
Assert.assertEquals("Step " + (i + 1) + " should be skipped", "skipped", statuses.get(i));
}
}
}
24 changes: 20 additions & 4 deletions src/test/java/com/microfocus/bdd/ut/TestJunitReportReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ public void testPassedXml() throws XMLStreamException, FileNotFoundException {
Element element = iter.next();
Assert.assertTrue("element has a name", element.getName().equals("testcase"));
Assert.assertEquals("element has one child named system-out", "system-out", element.getChildren().get(0).getName());
Assert.assertEquals("element has one child contains text", "Given a global administrator named \"Greg\"...................................passed\n" +
Assert.assertEquals("element has one child contains text", ("Given a global administrator named \"Greg\"...................................passed\n" +
"* a blog named \"Greg's anti-tax rants\"......................................passed\n" +
"* a customer named \"Wilson\".................................................passed\n" +
"Given the following people exist:...........................................passed\n" +
"And some precondition 1.....................................................passed\n" +
"When some action by the actor...............................................passed\n" +
"And some other action.......................................................passed\n" +
"Then some testable outcome is achieved......................................passed\n" +
"And something else we can check happens too.................................passed\n", element.getChildren().get(0).getText());
"And something else we can check happens too.................................passed\n").trim(), element.getChildren().get(0).getText().trim());
}

@Test
Expand All @@ -73,7 +73,7 @@ public void testFailedXml() throws XMLStreamException, FileNotFoundException {
Element element = iter.next();
Assert.assertTrue("element has a name", element.getName().equals("testcase"));
Assert.assertEquals("element has one child named failure", "failure", element.getChildren().get(0).getName());
Assert.assertEquals("Given a global administrator named \"Greg\"...................................passed\n" +
Assert.assertEquals(("Given a global administrator named \"Greg\"...................................passed\n" +
"* a blog named \"Greg's anti-tax rants\"......................................passed\n" +
"* a customer named \"Wilson\".................................................passed\n" +
"Given the following people exist:...........................................failed\n" +
Expand All @@ -89,6 +89,22 @@ public void testFailedXml() throws XMLStreamException, FileNotFoundException {
"\tat org.junit.Assert.assertTrue(Assert.java:42)\n" +
"\tat org.junit.Assert.assertTrue(Assert.java:53)\n" +
"\tat hellocucumber.failed.StepDefinitions.the_following_people_exist(StepDefinitions.java:12)\n" +
"\tat ✽.the following people exist:(file:///C:/junit2octane/src/test/resources/features/robustgherkin.feature:14)\n", element.getChildren().get(0).getText());
"\tat ✽.the following people exist:(file:///C:/junit2octane/src/test/resources/features/robustgherkin.feature:14)\n").trim(), element.getChildren().get(0).getText().trim());
}

@Test
public void testEntityEncodedAngleBracketsPreserveSpaces() throws XMLStreamException, FileNotFoundException {
JunitReportReader reader = new JunitReportReader(
new FileInputStream("src/test/resources/cucumber-jvm/entity-encoding-test.xml"),
"testcase");
Iterator<Element> iter = reader.iterator();
Assert.assertTrue(iter.hasNext());
Element element = iter.next();
Element failure = element.getChildren().get(0);
Assert.assertEquals("failure", failure.getName());
String text = failure.getText();
// Spaces between entity-decoded angle brackets must be preserved
Assert.assertTrue("spaces between angle brackets must be preserved",
text.contains("<Token A> <Token B> <Token C>"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public void testPassSkipFailedScenario() {
handler.getFeatureFile().get());
OctaneStep passStep = new OctaneStep("Then", "some testable outcome is achieved", null, null, null);
handler.fillStep(passStep);
Assert.assertTrue("Handle recognize passed status", passStep.getStatus()==Status.PASSED);
Assert.assertNull("Non-matching step should have null status (determined by mergeScenario)", passStep.getStatus());

OctaneStep failedStep = new OctaneStep( "Given", "the following people exist:", null, null, null);
handler.fillStep(failedStep);
Expand Down
Loading