Skip to content

Commit

Permalink
Merge pull request #44 from ArtemOAS/RPP_Agents_Java_Cucumber_6_LastE…
Browse files Browse the repository at this point in the history
…rrorLog

Last Error Log
  • Loading branch information
HardNorth authored Nov 15, 2024
2 parents 19fcafa + f50182a commit 47ae888
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 18 deletions.
83 changes: 65 additions & 18 deletions src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import io.cucumber.plugin.ConcurrentEventListener;
import io.cucumber.plugin.event.*;
import io.reactivex.Maybe;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -59,6 +60,7 @@
import static com.epam.reportportal.cucumber.Utils.*;
import static com.epam.reportportal.cucumber.util.ItemTreeUtils.createKey;
import static com.epam.reportportal.cucumber.util.ItemTreeUtils.retrieveLeaf;
import static java.lang.String.format;
import static com.epam.reportportal.utils.formatting.ExceptionUtils.getStackTrace;
import static java.util.Optional.ofNullable;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
Expand All @@ -85,6 +87,8 @@ public abstract class AbstractReporter implements ConcurrentEventListener {
protected static final String METHOD_OPENING_BRACKET = "(";
protected static final String HOOK_ = "Hook: ";
protected static final String DOCSTRING_DECORATOR = "\n\"\"\"\n";
private static final String ERROR_FORMAT = "Error:\n%s";
private static final String DESCRIPTION_ERROR_FORMAT = "%s\n" + ERROR_FORMAT;

private final Map<URI, FeatureContext> featureContextMap = new ConcurrentHashMap<>();
private final TestItemTree itemTree = new TestItemTree();
Expand All @@ -95,6 +99,15 @@ public abstract class AbstractReporter implements ConcurrentEventListener {
// End of feature occurs once launch is finished.
private final Map<URI, Date> featureEndTime = new ConcurrentHashMap<>();

/**
* This map uses to record the description of the scenario and the step to append the error to the description.
*/
private final Map<String, String> descriptionsMap = new ConcurrentHashMap<>();
/**
* This map uses to record errors to append to the description.
*/
private final Map<String, Throwable> errorMap = new ConcurrentHashMap<>();

/**
* A method for creation a Start Launch request which will be sent to Report Portal. You can customize it by overriding the method.
*
Expand Down Expand Up @@ -320,8 +333,12 @@ protected void afterScenario(TestCaseFinished event) {
TestCase testCase = event.getTestCase();
execute(testCase, (f, s) -> {
URI featureUri = f.getUri();
Date endTime = finishTestItem(s.getId(), mapItemStatus(event.getResult().getStatus()));
featureEndTime.put(featureUri, endTime);
if (mapItemStatus(event.getResult().getStatus()) == ItemStatus.FAILED) {
Optional.ofNullable(event.getResult().getError())
.ifPresent(error -> s.getId().subscribe(id -> errorMap.put(id, error)));
}
Date endTime = finishTestItem(s.getId(), mapItemStatus(event.getResult().getStatus()), null);
featureEndTime.put(featureUri, endTime);
removeFromTree(f.getFeature(), testCase);
});
}
Expand Down Expand Up @@ -416,6 +433,9 @@ protected void beforeStep(@Nonnull TestCase testCase, @Nonnull TestStep testStep
String stepPrefix = step.getStep().getLocation().getLine() < s.getLine() ? BACKGROUND_PREFIX : null;
StartTestItemRQ rq = buildStartStepRequest(testStep, stepPrefix, step.getStep().getKeyword());
Maybe<String> stepId = startStep(s.getId(), rq);
if (rq.isHasStats()) {
stepId.subscribe(id -> descriptionsMap.put(id, ofNullable(rq.getDescription()).orElse(StringUtils.EMPTY)));
}
s.setStepId(stepId);
String stepText = step.getStep().getText();
if (getLaunch().getParameters().isCallbackReportingEnabled()) {
Expand All @@ -436,7 +456,11 @@ protected void beforeStep(@Nonnull TestCase testCase, @Nonnull TestStep testStep
protected void afterStep(@Nonnull TestCase testCase, @Nonnull TestStep testStep, @Nonnull Result result) {
execute(testCase, (f, s) -> {
reportResult(result, null);
finishTestItem(s.getStepId(), mapItemStatus(result.getStatus()));
if (mapItemStatus(result.getStatus()) == ItemStatus.FAILED) {
Optional.ofNullable(result.getError())
.ifPresent(error -> s.getStepId().subscribe(id -> errorMap.put(id, error)));
}
finishTestItem(s.getStepId(), mapItemStatus(result.getStatus()), null);
s.setStepId(Maybe.empty());
});
}
Expand Down Expand Up @@ -676,7 +700,9 @@ protected void beforeScenario(@Nonnull Feature feature, @Nonnull TestCase scenar

// If it's a ScenarioOutline use Example's line number as code reference to detach one Test Item from another
int codeLine = s.getExample().map(e -> e.getLocation().getLine()).orElse(s.getLine());
s.setId(startScenario(rootId, buildStartScenarioRequest(scenario, scenarioName, s.getUri(), codeLine)));
StartTestItemRQ startTestItemRQ = buildStartScenarioRequest(scenario, scenarioName, s.getUri(), codeLine);
s.setId(startScenario(rootId, startTestItemRQ));
s.getId().subscribe(id -> descriptionsMap.put(id, ofNullable(startTestItemRQ.getDescription()).orElse(StringUtils.EMPTY)));
if (getLaunch().getParameters().isCallbackReportingEnabled()) {
addToTree(feature, scenario, s.getId());
}
Expand Down Expand Up @@ -868,12 +894,34 @@ protected void handleTestStepFinished(@Nonnull TestStepFinished event) {
*/
@Nonnull
@SuppressWarnings("unused")
protected FinishTestItemRQ buildFinishTestItemRequest(@Nonnull Maybe<String> itemId, @Nullable Date finishTime,
@Nullable ItemStatus status) {
FinishTestItemRQ rq = new FinishTestItemRQ();
ofNullable(status).ifPresent(s -> rq.setStatus(s.name()));
rq.setEndTime(ofNullable(finishTime).orElse(Calendar.getInstance().getTime()));
return rq;
protected Maybe<FinishTestItemRQ> buildFinishTestItemRequest(@Nonnull Maybe<String> itemId, @Nullable Date finishTime,
@Nullable ItemStatus status) {
return itemId.map(id -> {
FinishTestItemRQ rq = new FinishTestItemRQ();
if (status == ItemStatus.FAILED) {
Optional<String> currentDescription = Optional.ofNullable(descriptionsMap.get(id));
Optional<Throwable> currentError = Optional.ofNullable(errorMap.get(id));
currentDescription.flatMap(description -> currentError
.map(errorMessage -> resolveDescriptionErrorMessage(description, errorMessage)))
.ifPresent(rq::setDescription);
}
ofNullable(status).ifPresent(s -> rq.setStatus(s.name()));
rq.setEndTime(finishTime);
return rq;
});
}

/**
* Resolve description
* @param currentDescription Current description
* @param error Error message
* @return Description with error
*/
private String resolveDescriptionErrorMessage(String currentDescription, Throwable error) {
return Optional.ofNullable(currentDescription)
.filter(StringUtils::isNotBlank)
.map(description -> format(DESCRIPTION_ERROR_FORMAT, currentDescription, error))
.orElse(format(ERROR_FORMAT, error));
}

/**
Expand All @@ -889,10 +937,11 @@ protected Date finishTestItem(@Nullable Maybe<String> itemId, @Nullable ItemStat
LOGGER.error("BUG: Trying to finish unspecified test item.");
return null;
}
FinishTestItemRQ rq = buildFinishTestItemRequest(itemId, dateTime, status);
Date endTime = ofNullable(dateTime).orElse(Calendar.getInstance().getTime());
Maybe<FinishTestItemRQ> rq = buildFinishTestItemRequest(itemId, endTime, status);
//noinspection ReactiveStreamsUnusedPublisher
getLaunch().finishTestItem(itemId, rq);
return rq.getEndTime();
rq.subscribe(finishTestItem -> getLaunch().finishTestItem(itemId, finishTestItem));
return endTime;
}

/**
Expand All @@ -907,7 +956,7 @@ protected ItemStatus mapItemStatus(@Nullable Status status) {
return null;
} else {
if (STATUS_MAPPING.get(status) == null) {
LOGGER.error(String.format("Unable to find direct mapping between Cucumber and ReportPortal for TestItem with status: '%s'.",
LOGGER.error(format("Unable to find direct mapping between Cucumber and ReportPortal for TestItem with status: '%s'.",
status
));
return ItemStatus.SKIPPED;
Expand All @@ -921,11 +970,9 @@ protected ItemStatus mapItemStatus(@Nullable Status status) {
*
* @param itemId an ID of the item
* @param status the status of the item
* @return a date and time object of the finish event
*/
@Nullable
protected Date finishTestItem(@Nullable Maybe<String> itemId, @Nullable ItemStatus status) {
return finishTestItem(itemId, status, null);
protected void finishTestItem(@Nullable Maybe<String> itemId, @Nullable ItemStatus status) {
finishTestItem(itemId, status, null);
}

/**
Expand Down
54 changes: 54 additions & 0 deletions src/test/java/com/epam/reportportal/cucumber/FailedTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,13 @@

import static com.epam.reportportal.cucumber.integration.util.TestUtils.filterLogs;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.startsWith;
import static org.mockito.Mockito.*;

/**
Expand All @@ -52,6 +57,11 @@
public class FailedTest {

private static final String EXPECTED_ERROR = "java.lang.IllegalStateException: " + FailedSteps.ERROR_MESSAGE;
private static final String ERROR_LOG_TEXT = "Error:\n" + EXPECTED_ERROR;

private static final Pair<String, String> SCENARIO_CODE_REFERENCES_WITH_ERROR = Pair.of("file:///",
"/agent-java-cucumber6/src/test/resources/features/FailedScenario.feature\n" + ERROR_LOG_TEXT
);

@CucumberOptions(features = "src/test/resources/features/FailedScenario.feature", glue = {
"com.epam.reportportal.cucumber.integration.feature" }, plugin = { "pretty",
Expand Down Expand Up @@ -136,4 +146,48 @@ public void verify_failed_step_reporting_step_reporter() {
SaveLogRQ expectedError = expectedErrorList.get(0);
assertThat(expectedError.getItemUuid(), equalTo(stepId));
}

@Test
@SuppressWarnings("unchecked")
public void verify_failed_nested_step_description_scenario_reporter() {
TestUtils.runTests(FailedScenarioReporter.class);

verify(client).startTestItem(any());
verify(client).startTestItem(same(suiteId), any());
verify(client).startTestItem(same(testId), any());
verify(client).startTestItem(same(stepId), any());
ArgumentCaptor<FinishTestItemRQ> finishCaptor = ArgumentCaptor.forClass(FinishTestItemRQ.class);
verify(client).finishTestItem(same(nestedStepId), finishCaptor.capture());
verify(client).finishTestItem(same(stepId), any());
verify(client).finishTestItem(same(testId), finishCaptor.capture());

List<FinishTestItemRQ> finishRqs = finishCaptor.getAllValues();
finishRqs.subList(0, finishRqs.size() - 1).forEach(e -> assertThat(e.getStatus(), equalTo(ItemStatus.FAILED.name())));

FinishTestItemRQ step = finishRqs.get(0);
assertThat(step.getDescription(), not(equalTo(ERROR_LOG_TEXT)));
}

@Test
@SuppressWarnings("unchecked")
public void verify_failed_step_description_step_reporter() {
TestUtils.runTests(FailedStepReporter.class);

verify(client).startTestItem(any());
verify(client).startTestItem(same(suiteId), any());
verify(client).startTestItem(same(testId), any());
ArgumentCaptor<FinishTestItemRQ> finishCaptor = ArgumentCaptor.forClass(FinishTestItemRQ.class);
verify(client).finishTestItem(same(stepId), finishCaptor.capture());
verify(client).finishTestItem(same(testId), finishCaptor.capture());

List<FinishTestItemRQ> finishRqs = finishCaptor.getAllValues();
finishRqs.forEach(e -> assertThat(e.getStatus(), equalTo(ItemStatus.FAILED.name())));

FinishTestItemRQ step = finishRqs.get(0);
assertThat(step.getDescription(), equalTo(ERROR_LOG_TEXT));
FinishTestItemRQ test = finishRqs.get(1);
assertThat(test.getDescription(),
allOf(notNullValue(), startsWith(SCENARIO_CODE_REFERENCES_WITH_ERROR.getKey()), endsWith(SCENARIO_CODE_REFERENCES_WITH_ERROR.getValue()))
);
}
}

0 comments on commit 47ae888

Please sign in to comment.