Skip to content

Commit

Permalink
Merge pull request #28 from reportportal/develop
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
HardNorth authored Nov 21, 2024
2 parents 0ebd64c + 8ce216a commit e1b2fcb
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 41 deletions.
9 changes: 6 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ on:
- README_TEMPLATE.md
- CHANGELOG.md

workflow_dispatch:

env:
VERSION_FILE: gradle.properties
VERSION_EXTRACT_PATTERN: '(?<=version=).+'
Expand All @@ -36,6 +38,9 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: 'master'
fetch-depth: 0

- name: Generate versions
uses: HardNorth/github-version-generate@v1
Expand All @@ -51,7 +56,7 @@ jobs:
java-version: '8'

- name: Setup git credentials
uses: oleksiyrudenko/gha-git-credentials@v2.1.1
uses: oleksiyrudenko/gha-git-credentials@v2-latest
with:
name: 'reportportal.io'
email: '[email protected]'
Expand Down Expand Up @@ -95,15 +100,13 @@ jobs:
body: ${{ steps.readChangelogEntry.outputs.changes }}

- name: Checkout develop branch
if: ${{github.ref}} == 'master'
uses: actions/checkout@v4
with:
ref: 'develop'
fetch-depth: 0

- name: Merge release branch into develop
id: mergeIntoDevelop
if: ${{github.ref}} == 'master'
run: |
git merge -m 'Merge master branch into develop after a release' origin/master
git status | (! grep -Fq 'both modified:') || git status | grep -F 'both modified:' \
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +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.22](https://github.com/reportportal/client-java/releases/tag/5.2.22), by @HardNorth

## [5.2.2]
### Changed
Expand Down
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ repositories {
}

dependencies {
api 'com.epam.reportportal:client-java:5.2.13'
api 'com.epam.reportportal:client-java:5.2.22'
api "io.cucumber:cucumber-java:${project.cucumber_version}"

implementation 'org.slf4j:slf4j-api:2.0.7'
Expand All @@ -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 {
Expand Down
119 changes: 89 additions & 30 deletions src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.reportportal.utils.reflect.Accessible;
import com.epam.ta.reportportal.ws.model.FinishExecutionRQ;
Expand All @@ -43,6 +43,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 @@ -62,9 +63,10 @@
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.ofNullable;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.exception.ExceptionUtils.getStackTrace;

/**
* Abstract Cucumber 5.x formatter for Report Portal
Expand All @@ -84,6 +86,7 @@ public abstract class AbstractReporter implements ConcurrentEventListener {
private static final String METHOD_OPENING_BRACKET = "(";
private static final String STEP_DEFINITION_FIELD_NAME = "stepDefinition";
private static final String DOCSTRING_DECORATOR = "\n\"\"\"\n";
private static final String ERROR_FORMAT = "Error:\n%s";

public static final TestItemTree ITEM_TREE = new TestItemTree();
private static volatile ReportPortal REPORT_PORTAL = ReportPortal.builder().build();
Expand All @@ -100,6 +103,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<Maybe<String>, String> descriptionsMap = new ConcurrentHashMap<>();
/**
* This map uses to record errors to append to the description.
*/
private final Map<Maybe<String>, Throwable> errorMap = new ConcurrentHashMap<>();

private final ThreadLocal<RunningContext.ScenarioContext> currentScenarioContext = new ThreadLocal<>();

public static ReportPortal getReportPortal() {
Expand Down Expand Up @@ -219,14 +231,20 @@ protected Maybe<String> startScenario(@Nonnull Maybe<String> featureId, @Nonnull
* @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()
);
Maybe<String> id = startScenario(featureContext.getFeatureId(),
buildStartScenarioRequest(scenarioContext.getTestCase(), scenarioName, featureContext.getUri(), scenarioContext.getLine())
StartTestItemRQ startTestItemRQ = buildStartScenarioRequest(
scenarioContext.getTestCase(),
scenarioName,
featureContext.getUri(),
scenarioContext.getLine()
);
Maybe<String> id = startScenario(featureContext.getFeatureId(), startTestItemRQ);
scenarioContext.setId(id);
descriptionsMap.put(id, ofNullable(startTestItemRQ.getDescription()).orElse(StringUtils.EMPTY));
if (launch.get().getParameters().isCallbackReportingEnabled()) {
addToTree(featureContext, scenarioContext);
}
Expand All @@ -247,6 +265,9 @@ protected void afterScenario(TestCaseFinished event) {
RunningContext.ScenarioContext context = getCurrentScenarioContext();
URI featureUri = context.getFeatureUri();
currentScenarioContextMap.remove(Pair.of(context.getLine(), featureUri));
if (mapItemStatus(event.getResult().getStatus()) == ItemStatus.FAILED) {
Optional.ofNullable(event.getResult().getError()).ifPresent(error -> errorMap.put(context.getId(), error));
}
Date endTime = finishTestItem(context.getId(), event.getResult().getStatus());
featureEndTime.put(featureUri, endTime);
currentScenarioContext.remove();
Expand Down Expand Up @@ -353,7 +374,9 @@ protected void beforeStep(TestStep testStep) {
context.setCurrentStepId(stepId);
String stepText = step.getText();
context.setCurrentText(stepText);

if (rq.isHasStats()) {
descriptionsMap.put(stepId, ofNullable(rq.getDescription()).orElse(StringUtils.EMPTY));
}
if (launch.get().getParameters().isCallbackReportingEnabled()) {
addToTree(context, stepText, stepId);
}
Expand All @@ -367,6 +390,9 @@ 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 -> errorMap.put(context.getCurrentStepId(), error));
}
finishTestItem(context.getCurrentStepId(), result.getStatus());
context.setCurrentStepId(null);
}
Expand Down Expand Up @@ -466,7 +492,7 @@ protected void reportResult(@Nonnull Result result, @Nullable String message) {
sendLog(message, level);
}
if (result.getError() != null) {
sendLog(getStackTrace(result.getError()), level);
sendLog(getStackTrace(result.getError(), new Throwable()), level);
}
}

Expand All @@ -491,7 +517,8 @@ protected void embedding(@Nullable String name, @Nullable String mimeType, @Nonn
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()
);
Expand Down Expand Up @@ -616,14 +643,16 @@ protected void handleStartOfTestCase(@Nonnull TestCaseStarted event) {
TestCase testCase = event.getTestCase();
RunningContext.FeatureContext newFeatureContext = new RunningContext.FeatureContext(testCase);
URI 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.");
Expand All @@ -632,10 +661,12 @@ protected void handleStartOfTestCase(@Nonnull TestCaseStarted event) {
RunningContext.ScenarioContext newScenarioContext = featureContext.getScenarioContext(testCase);

Pair<Integer, URI> 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);
}
Expand Down Expand Up @@ -664,14 +695,16 @@ protected void handleTestStepFinished(@Nonnull TestStepFinished event) {

protected void addToTree(@Nonnull RunningContext.ScenarioContext scenarioContext, @Nullable String text,
@Nonnull Maybe<String> stepId) {
retrieveLeaf(scenarioContext.getFeatureUri(),
retrieveLeaf(
scenarioContext.getFeatureUri(),
scenarioContext.getLine(),
ITEM_TREE
).ifPresent(scenarioLeaf -> scenarioLeaf.getChildItems().put(createKey(text), TestItemTree.createTestItemLeaf(stepId)));
}

protected void removeFromTree(@Nonnull RunningContext.ScenarioContext scenarioContext, @Nullable String text) {
retrieveLeaf(scenarioContext.getFeatureUri(),
retrieveLeaf(
scenarioContext.getFeatureUri(),
scenarioContext.getLine(),
ITEM_TREE
).ifPresent(scenarioLeaf -> scenarioLeaf.getChildItems().remove(createKey(text)));
Expand All @@ -686,15 +719,37 @@ protected void removeFromTree(@Nonnull RunningContext.ScenarioContext scenarioCo
* @return finish request
*/
@Nonnull
@SuppressWarnings("unused")
protected FinishTestItemRQ buildFinishTestItemRequest(@Nonnull Maybe<String> itemId, @Nullable Date finishTime,
@Nullable ItemStatus status) {
FinishTestItemRQ rq = new FinishTestItemRQ();
if (status == ItemStatus.FAILED) {
Optional<String> currentDescription = Optional.ofNullable(descriptionsMap.remove(itemId));
Optional<Throwable> 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(ofNullable(finishTime).orElse(Calendar.getInstance().getTime()));
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) {
String errorStr = format(ERROR_FORMAT, getStackTrace(error, new Throwable()));
return Optional.ofNullable(currentDescription)
.filter(StringUtils::isNotBlank)
.map(description -> MarkdownUtils.asTwoParts(currentDescription, errorStr))
.orElse(errorStr);
}

/**
* Finish a feature with specific date and time
*
Expand All @@ -706,8 +761,10 @@ protected void finishFeature(@Nullable Maybe<String> itemId, @Nullable Date date
LOGGER.error("BUG: Trying to finish unspecified test item.");
return;
}
Date endTime = ofNullable(dateTime).orElse(Calendar.getInstance().getTime());
FinishTestItemRQ rq = buildFinishTestItemRequest(itemId, endTime, null);
//noinspection ReactiveStreamsUnusedPublisher
launch.get().finishTestItem(itemId, buildFinishTestItemRequest(itemId, dateTime, null));
launch.get().finishTestItem(itemId, rq);
}

/**
Expand All @@ -723,11 +780,11 @@ protected Date finishTestItem(@Nullable Maybe<String> itemId, @Nullable Status s
LOGGER.error("BUG: Trying to finish unspecified test item.");
return null;
}
FinishTestItemRQ rq = buildFinishTestItemRequest(itemId, null, mapItemStatus(status));
new FinishTestItemRQ();
Date endTime = Calendar.getInstance().getTime();
FinishTestItemRQ rq = buildFinishTestItemRequest(itemId, endTime, mapItemStatus(status));
//noinspection ReactiveStreamsUnusedPublisher
launch.get().finishTestItem(itemId, rq);
return rq.getEndTime();
return endTime;
}

/**
Expand All @@ -751,7 +808,8 @@ 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(String.format(
"Unable to find direct mapping between Cucumber and ReportPortal for TestItem with status: '%s'.",
status
));
return ItemStatus.SKIPPED;
Expand Down Expand Up @@ -954,7 +1012,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<Object>) ARGUMENTS_TRANSFORM.apply(arguments)
Expand Down
Loading

0 comments on commit e1b2fcb

Please sign in to comment.