From 822964fc0303605d0f7a9ac70b11f6dd146fd52b Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Wed, 20 Nov 2024 10:42:15 +0300 Subject: [PATCH] Client version update and refactoring --- CHANGELOG.md | 5 +- build.gradle | 4 +- .../cucumber/AbstractReporter.java | 126 ++++++++++-------- .../reportportal/cucumber/FailedTest.java | 44 +++--- 4 files changed, 90 insertions(+), 89 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc077ea..1712d0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,11 @@ # Changelog ## [Unreleased] +### Added +- Common Stack Trace frames skip in description and logs, by @HardNorth +- Reporting of Last Error Log in Item description, by @HardNorth and @ArtemOAS ### Changed -- Client version updated on [5.2.14](https://github.com/reportportal/client-java/releases/tag/5.2.14), by @HardNorth +- Client version updated on [5.2.21](https://github.com/reportportal/client-java/releases/tag/5.2.21), by @HardNorth ## [5.2.2] ### Changed diff --git a/build.gradle b/build.gradle index d4a43c3..b830bbd 100644 --- a/build.gradle +++ b/build.gradle @@ -37,7 +37,7 @@ repositories { } dependencies { - api 'com.epam.reportportal:client-java:5.2.14' + api 'com.epam.reportportal:client-java:5.2.21' api "io.cucumber:cucumber-java:${project.cucumber_version}" implementation 'org.slf4j:slf4j-api:2.0.7' @@ -58,7 +58,7 @@ dependencies { testImplementation "org.junit.jupiter:junit-jupiter-api:${project.junit_version}" testImplementation "org.junit.jupiter:junit-jupiter-params:${project.junit_version}" testImplementation "org.junit.jupiter:junit-jupiter-engine:${project.junit_version}" - testImplementation 'org.apache.commons:commons-io:1.3.2' + testImplementation 'commons-io:commons-io:2.16.1' } test { diff --git a/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java b/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java index 8369dc1..b5bbc6f 100644 --- a/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java +++ b/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java @@ -27,8 +27,8 @@ import com.epam.reportportal.service.tree.TestItemTree; import com.epam.reportportal.utils.*; import com.epam.reportportal.utils.files.ByteSource; +import com.epam.reportportal.utils.formatting.MarkdownUtils; import com.epam.reportportal.utils.http.ContentType; -import com.epam.reportportal.utils.markdown.MarkdownUtils; import com.epam.reportportal.utils.properties.SystemAttributesExtractor; import com.epam.ta.reportportal.ws.model.FinishExecutionRQ; import com.epam.ta.reportportal.ws.model.FinishTestItemRQ; @@ -67,11 +67,11 @@ 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 com.epam.reportportal.utils.formatting.ExceptionUtils.getStackTrace; import static java.lang.String.format; import static java.util.Optional.of; import static java.util.Optional.ofNullable; import static org.apache.commons.lang3.StringUtils.isNotBlank; -import static org.apache.commons.lang3.exception.ExceptionUtils.getStackTrace; /** * Abstract Cucumber 4.x formatter for Report Portal @@ -92,7 +92,6 @@ public abstract class AbstractReporter implements ConcurrentEventListener { private static final String HOOK_ = "Hook: "; private 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; public static final TestItemTree ITEM_TREE = new TestItemTree(); private static volatile ReportPortal REPORT_PORTAL = ReportPortal.builder().build(); @@ -115,11 +114,11 @@ public abstract class AbstractReporter implements ConcurrentEventListener { /** * This map uses to record the description of the scenario and the step to append the error to the description. */ - private final Map descriptionsMap = new ConcurrentHashMap<>(); + private final Map, String> descriptionsMap = new ConcurrentHashMap<>(); /** * This map uses to record errors to append to the description. */ - private final Map errorMap = new ConcurrentHashMap<>(); + private final Map, Throwable> errorMap = new ConcurrentHashMap<>(); public static ReportPortal getReportPortal() { return REPORT_PORTAL; @@ -175,7 +174,8 @@ protected TestCaseIdEntry getTestCaseId(@Nonnull TestStep testStep, @Nullable St if (definitionMatch != null) { try { Method method = retrieveMethod(definitionMatch); - return TestCaseIdUtils.getTestCaseId(method.getAnnotation(TestCaseId.class), + return TestCaseIdUtils.getTestCaseId( + method.getAnnotation(TestCaseId.class), method, codeRef, (List) ARGUMENTS_TRANSFORM.apply(arguments) @@ -248,14 +248,20 @@ private void addToTree(RunningContext.FeatureContext featureContext, RunningCont * @param scenarioContext current scenario context */ protected void beforeScenario(RunningContext.FeatureContext featureContext, RunningContext.ScenarioContext scenarioContext) { - String scenarioName = Utils.buildName(scenarioContext.getKeyword(), + String scenarioName = Utils.buildName( + scenarioContext.getKeyword(), AbstractReporter.COLON_INFIX, scenarioContext.getTestCase().getName() ); - StartTestItemRQ startTestItemRQ = buildStartScenarioRequest(scenarioContext.getTestCase(), scenarioName, featureContext.getUri(), scenarioContext.getLine()); + StartTestItemRQ startTestItemRQ = buildStartScenarioRequest( + scenarioContext.getTestCase(), + scenarioName, + featureContext.getUri(), + scenarioContext.getLine() + ); Maybe id = startScenario(featureContext.getFeatureId(), startTestItemRQ); scenarioContext.setId(id); - id.subscribe(scenarioId -> descriptionsMap.put(scenarioId, ofNullable(startTestItemRQ.getDescription()).orElse(StringUtils.EMPTY))); + descriptionsMap.put(id, ofNullable(startTestItemRQ.getDescription()).orElse(StringUtils.EMPTY)); if (launch.get().getParameters().isCallbackReportingEnabled()) { addToTree(featureContext, scenarioContext); } @@ -273,9 +279,7 @@ protected ItemStatus mapItemStatus(@Nullable Result.Type 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'.", - status - )); + LOGGER.error("Unable to find direct mapping between Cucumber and ReportPortal for TestItem with status: '{}'.", status); return ItemStatus.SKIPPED; } return STATUS_MAPPING.get(status); @@ -292,34 +296,35 @@ protected ItemStatus mapItemStatus(@Nullable Result.Type status) { */ @Nonnull @SuppressWarnings("unused") - protected Maybe buildFinishTestItemRequest(@Nonnull Maybe itemId, @Nullable Date finishTime, - @Nullable ItemStatus status) { - return itemId.map(id -> { - FinishTestItemRQ rq = new FinishTestItemRQ(); - if (status == ItemStatus.FAILED) { - Optional currentDescription = Optional.ofNullable(descriptionsMap.get(itemId.blockingGet())); - Optional currentError = Optional.ofNullable(errorMap.get(itemId.blockingGet())); - 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; - }); + protected FinishTestItemRQ buildFinishTestItemRequest(@Nonnull Maybe itemId, @Nullable Date finishTime, + @Nullable ItemStatus status) { + FinishTestItemRQ rq = new FinishTestItemRQ(); + if (status == ItemStatus.FAILED) { + Optional currentDescription = Optional.ofNullable(descriptionsMap.remove(itemId)); + Optional currentError = Optional.ofNullable(errorMap.remove(itemId)); + 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 + * @param error Error message * @return Description with error */ private String resolveDescriptionErrorMessage(String currentDescription, Throwable error) { + String errorStr = format(ERROR_FORMAT, getStackTrace(error, new Throwable())); return Optional.ofNullable(currentDescription) .filter(StringUtils::isNotBlank) - .map(description -> format(DESCRIPTION_ERROR_FORMAT, currentDescription, error)) - .orElse(format(ERROR_FORMAT, error)); + .map(description -> MarkdownUtils.asTwoParts(currentDescription, errorStr)) + .orElse(errorStr); } /** @@ -336,9 +341,9 @@ protected Date finishTestItem(Maybe itemId, Result.Type status) { } Date endTime = Calendar.getInstance().getTime(); - Maybe rqMaybe = buildFinishTestItemRequest(itemId, endTime, mapItemStatus(status)); + FinishTestItemRQ rq = buildFinishTestItemRequest(itemId, endTime, mapItemStatus(status)); //noinspection ReactiveStreamsUnusedPublisher - rqMaybe.subscribe(rq -> launch.get().finishTestItem(itemId, rq)); + launch.get().finishTestItem(itemId, rq); return endTime; } @@ -367,8 +372,7 @@ protected void afterScenario(TestCaseFinished event) { String featureUri = context.getFeatureUri(); currentScenarioContextMap.remove(Pair.of(context.getLine(), featureUri)); if (mapItemStatus(event.result.getStatus()) == ItemStatus.FAILED) { - Optional.ofNullable(event.result.getError()) - .ifPresent(error -> context.getId().subscribe(id -> errorMap.put(id, error))); + Optional.ofNullable(event.result.getError()).ifPresent(error -> errorMap.put(context.getId(), error)); } Date endTime = finishTestItem(context.getId(), event.result.getStatus()); featureEndTime.put(featureUri, endTime); @@ -477,7 +481,7 @@ protected void beforeStep(TestStep testStep) { String stepText = step.getText(); context.setCurrentText(stepText); if (rq.isHasStats()) { - stepId.subscribe(id -> descriptionsMap.put(id, ofNullable(rq.getDescription()).orElse(StringUtils.EMPTY))); + descriptionsMap.put(stepId, ofNullable(rq.getDescription()).orElse(StringUtils.EMPTY)); } if (launch.get().getParameters().isCallbackReportingEnabled()) { addToTree(context, stepText, stepId); @@ -492,9 +496,8 @@ protected void beforeStep(TestStep testStep) { protected void afterStep(Result result) { reportResult(result, null); RunningContext.ScenarioContext context = getCurrentScenarioContext(); - if (mapItemStatus(result.getStatus()) == ItemStatus.FAILED){ - Optional.ofNullable(result.getError()) - .ifPresent(error -> context.getCurrentStepId().subscribe(id -> errorMap.put(id, error))); + if (mapItemStatus(result.getStatus()) == ItemStatus.FAILED) { + Optional.ofNullable(result.getError()).ifPresent(error -> errorMap.put(context.getCurrentStepId(), error)); } finishTestItem(context.getCurrentStepId(), result.getStatus()); context.setCurrentStepId(null); @@ -518,8 +521,8 @@ protected StartTestItemRQ buildStartHookRequest(HookType hookType) { /** * Start before/after-hook item on Report Portal * - * @param parentId parent item id - * @param rq hook start request + * @param parentId parent item id + * @param rq hook start request * @return hook item id */ @Nonnull @@ -598,7 +601,7 @@ protected void reportResult(Result result, String message) { if (errorMessage != null) { sendLog(errorMessage, level); } else if (result.getError() != null) { - sendLog(getStackTrace(result.getError()), level); + sendLog(getStackTrace(result.getError(), new Throwable()), level); } } @@ -623,7 +626,8 @@ protected void embedding(@Nullable String name, String mimeType, byte[] data) { String type = ofNullable(mimeType).filter(ContentType::isValidType).orElseGet(() -> getDataType(data, name)); String attachmentName = ofNullable(name).filter(m -> !m.isEmpty()) .orElseGet(() -> ofNullable(type).map(t -> t.substring(0, t.indexOf("/"))).orElse("")); - ReportPortal.emitLog(new ReportPortalMessage(ByteSource.wrap(data), type, attachmentName), + ReportPortal.emitLog( + new ReportPortalMessage(ByteSource.wrap(data), type, attachmentName), "UNKNOWN", Calendar.getInstance().getTime() ); @@ -667,9 +671,9 @@ protected void finishFeature(Maybe itemId, Date dateTime) { return; } Date endTime = ofNullable(dateTime).orElse(Calendar.getInstance().getTime()); - Maybe rqMaybe = buildFinishTestItemRequest(itemId, endTime, null); + FinishTestItemRQ rq = buildFinishTestItemRequest(itemId, endTime, null); //noinspection ReactiveStreamsUnusedPublisher - rqMaybe.subscribe(rq -> launch.get().finishTestItem(itemId, rq)); + launch.get().finishTestItem(itemId, rq); } private void removeFromTree(RunningContext.FeatureContext featureContext) { @@ -793,14 +797,16 @@ protected void handleStartOfTestCase(TestCaseStarted event) { TestCase testCase = event.testCase; RunningContext.FeatureContext newFeatureContext = new RunningContext.FeatureContext(testCase); String featureUri = newFeatureContext.getUri(); - RunningContext.FeatureContext featureContext = currentFeatureContextMap.computeIfAbsent(featureUri, u -> { - getRootItemId(); // trigger root item creation - newFeatureContext.setFeatureId(startFeature(buildStartFeatureRequest(newFeatureContext.getFeature(), featureUri))); - if (launch.get().getParameters().isCallbackReportingEnabled()) { - addToTree(newFeatureContext); - } - return newFeatureContext; - }); + RunningContext.FeatureContext featureContext = currentFeatureContextMap.computeIfAbsent( + featureUri, u -> { + getRootItemId(); // trigger root item creation + newFeatureContext.setFeatureId(startFeature(buildStartFeatureRequest(newFeatureContext.getFeature(), featureUri))); + if (launch.get().getParameters().isCallbackReportingEnabled()) { + addToTree(newFeatureContext); + } + return newFeatureContext; + } + ); if (!featureContext.getUri().equals(testCase.getUri())) { throw new IllegalStateException("Scenario URI does not match Feature URI."); @@ -809,10 +815,12 @@ protected void handleStartOfTestCase(TestCaseStarted event) { RunningContext.ScenarioContext newScenarioContext = featureContext.getScenarioContext(testCase); Pair scenarioLineFeatureURI = Pair.of(newScenarioContext.getLine(), featureContext.getUri()); - RunningContext.ScenarioContext scenarioContext = currentScenarioContextMap.computeIfAbsent(scenarioLineFeatureURI, k -> { - currentScenarioContext.set(newScenarioContext); - return newScenarioContext; - }); + RunningContext.ScenarioContext scenarioContext = currentScenarioContextMap.computeIfAbsent( + scenarioLineFeatureURI, k -> { + currentScenarioContext.set(newScenarioContext); + return newScenarioContext; + } + ); beforeScenario(featureContext, scenarioContext); } @@ -840,14 +848,16 @@ protected void handleTestStepFinished(TestStepFinished event) { } protected void addToTree(RunningContext.ScenarioContext scenarioContext, String text, Maybe stepId) { - retrieveLeaf(scenarioContext.getFeatureUri(), + retrieveLeaf( + scenarioContext.getFeatureUri(), scenarioContext.getLine(), ITEM_TREE ).ifPresent(scenarioLeaf -> scenarioLeaf.getChildItems().put(createKey(text), TestItemTree.createTestItemLeaf(stepId))); } protected void removeFromTree(RunningContext.ScenarioContext scenarioContext, String text) { - retrieveLeaf(scenarioContext.getFeatureUri(), + retrieveLeaf( + scenarioContext.getFeatureUri(), scenarioContext.getLine(), ITEM_TREE ).ifPresent(scenarioLeaf -> scenarioLeaf.getChildItems().remove(createKey(text))); diff --git a/src/test/java/com/epam/reportportal/cucumber/FailedTest.java b/src/test/java/com/epam/reportportal/cucumber/FailedTest.java index 5c55032..342add1 100644 --- a/src/test/java/com/epam/reportportal/cucumber/FailedTest.java +++ b/src/test/java/com/epam/reportportal/cucumber/FailedTest.java @@ -25,6 +25,7 @@ import com.epam.reportportal.service.ReportPortal; import com.epam.reportportal.service.ReportPortalClient; import com.epam.reportportal.util.test.CommonUtils; +import com.epam.reportportal.utils.formatting.MarkdownUtils; import com.epam.ta.reportportal.ws.model.FinishTestItemRQ; import com.epam.ta.reportportal.ws.model.log.SaveLogRQ; import io.cucumber.testng.AbstractTestNGCucumberTests; @@ -42,41 +43,32 @@ 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.hamcrest.Matchers.*; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.*; -/** - * @author Ihar Kahadouski - */ 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 String DESCRIPTION_ERROR_LOG_TEXT = - "file:src/test/resources/features/FailedScenario.feature\n" - + ERROR_LOG_TEXT; + private static final String EXPECTED_STACK_TRACE = EXPECTED_ERROR + + "\n\tat com.epam.reportportal.cucumber.integration.feature.FailedSteps.i_have_a_failed_step(FailedSteps.java:31)" + + "\n\tat ✽.I have a failed step(file:src/test/resources/features/FailedScenario.feature:4)\n"; + private static final String ERROR_LOG_TEXT = "Error:\n" + EXPECTED_STACK_TRACE; - private static final Pair SCENARIO_CODE_REFERENCES_WITH_ERROR = Pair.of("file:", - "src/test/resources/features/FailedScenario.feature\n" + ERROR_LOG_TEXT + private static final String SCENARIO_CODE_REFERENCES_WITH_ERROR = MarkdownUtils.asTwoParts("file:src/test/resources/features/FailedScenario.feature", + ERROR_LOG_TEXT ); @CucumberOptions(features = "src/test/resources/features/FailedScenario.feature", glue = { "com.epam.reportportal.cucumber.integration.feature" }, plugin = { "pretty", "com.epam.reportportal.cucumber.integration.TestScenarioReporter" }) - public static class FailedScenarioReporter extends AbstractTestNGCucumberTests { + public static class FailedScenarioReporterTest extends AbstractTestNGCucumberTests { } @CucumberOptions(features = "src/test/resources/features/FailedScenario.feature", glue = { "com.epam.reportportal.cucumber.integration.feature" }, plugin = { "pretty", "com.epam.reportportal.cucumber.integration.TestStepReporter" }) - public static class FailedStepReporter extends AbstractTestNGCucumberTests { + public static class FailedStepReporterTest extends AbstractTestNGCucumberTests { } @@ -104,7 +96,7 @@ public void initLaunch() { @Test @SuppressWarnings("unchecked") public void verify_failed_step_reporting_scenario_reporter() { - TestUtils.runTests(FailedScenarioReporter.class); + TestUtils.runTests(FailedScenarioReporterTest.class); verify(client).startTestItem(any()); verify(client).startTestItem(same(suiteId), any()); @@ -130,7 +122,7 @@ public void verify_failed_step_reporting_scenario_reporter() { @Test @SuppressWarnings("unchecked") public void verify_failed_step_reporting_step_reporter() { - TestUtils.runTests(FailedStepReporter.class); + TestUtils.runTests(FailedStepReporterTest.class); verify(client).startTestItem(any()); verify(client).startTestItem(same(suiteId), any()); @@ -151,9 +143,8 @@ public void verify_failed_step_reporting_step_reporter() { } @Test - @SuppressWarnings("unchecked") public void verify_failed_nested_step_description_scenario_reporter() { - TestUtils.runTests(FailedScenarioReporter.class); + TestUtils.runTests(FailedScenarioReporterTest.class); verify(client).startTestItem(any()); verify(client).startTestItem(same(suiteId), any()); @@ -172,9 +163,8 @@ public void verify_failed_nested_step_description_scenario_reporter() { } @Test - @SuppressWarnings("unchecked") public void verify_failed_step_description_step_reporter() { - TestUtils.runTests(FailedStepReporter.class); + TestUtils.runTests(FailedStepReporterTest.class); verify(client).startTestItem(any()); verify(client).startTestItem(same(suiteId), any()); @@ -189,8 +179,6 @@ public void verify_failed_step_description_step_reporter() { 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())) - ); + assertThat(test.getDescription(), equalTo(SCENARIO_CODE_REFERENCES_WITH_ERROR)); } }