Skip to content

Commit

Permalink
Merge pull request #255 from reportportal/develop
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
HardNorth authored Nov 8, 2024
2 parents 9d7f509 + 2f1b042 commit d6ff563
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 43 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Changelog

## [Unreleased]
### Fixed
- `@Issue` and `@Issues` annotations handling in certain cases, by @HardNorth
### Added
- Issue Locator lookup by value in `@Issue` annotation, by @HardNorth

## [5.2.19]
### Added
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,15 @@ etc.
| rp.truncation.item.name.limit | Integer | Default: `1024`<br> Maximum item names length before truncation. |
| rp.truncation.attribute.limit | Integer | Default: `128`<br> Maximum attribute key and value limit (counts separately) |

### Bug Tracking System parameters

| **Property name** | **Type** | **Description** |
|-------------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| rp.bts.project | String | Bug Tracking System Project name to use along with `@ExternalIssue` annotation. Should be the same as in corresponding integration. |
| rp.bts.url | String | Bug Tracking System base URL. Should be the same as in corresponding integration. |
| rp.bts.issue.url | String | Bug Tracking System URL Pattern for Issues. Use <code>{issue_id}</code> and <code>{bts_project}</code> placeholders to mark a place where to put Issue ID and Bug Tracking System Project name. |
| rp.bts.issue.fail | Boolean | Default: `true`<br> Fail tests marked with `@Issue` annotation if they passed. Designed to not miss the moment when the issue got fixed but test is still marked by annotation. |

## Proxy configuration

ReportPortal supports 2 options for setting Proxy configuration:
Expand Down
105 changes: 67 additions & 38 deletions src/main/java/com/epam/reportportal/service/LaunchImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.epam.ta.reportportal.ws.model.item.ItemCreatedRS;
import com.epam.ta.reportportal.ws.model.launch.StartLaunchRQ;
import com.epam.ta.reportportal.ws.model.launch.StartLaunchRS;
import com.epam.ta.reportportal.ws.model.project.config.ProjectSettingsResource;
import io.reactivex.*;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
Expand Down Expand Up @@ -72,17 +73,15 @@ public class LaunchImpl extends Launch {
private static final int ITEM_FINISH_RETRY_TIMEOUT = 10;

private static final Predicate<Throwable> INTERNAL_CLIENT_EXCEPTION_PREDICATE = throwable -> throwable instanceof InternalReportPortalClientException;
private static final Predicate<Throwable> TEST_ITEM_FINISH_RETRY_PREDICATE = throwable ->
(throwable instanceof ReportPortalException
&& ErrorType.FINISH_ITEM_NOT_ALLOWED.equals(((ReportPortalException) throwable).getError()
.getErrorType())) || INTERNAL_CLIENT_EXCEPTION_PREDICATE.test(throwable);
private static final Predicate<Throwable> TEST_ITEM_FINISH_RETRY_PREDICATE = throwable -> (throwable instanceof ReportPortalException
&& ErrorType.FINISH_ITEM_NOT_ALLOWED.equals(((ReportPortalException) throwable).getError().getErrorType()))
|| INTERNAL_CLIENT_EXCEPTION_PREDICATE.test(throwable);

private static final RetryWithDelay DEFAULT_REQUEST_RETRY = new RetryWithDelay(INTERNAL_CLIENT_EXCEPTION_PREDICATE,
DEFAULT_RETRY_COUNT,
TimeUnit.SECONDS.toMillis(DEFAULT_RETRY_TIMEOUT)
);
private static final RetryWithDelay TEST_ITEM_FINISH_REQUEST_RETRY = new RetryWithDelay(
TEST_ITEM_FINISH_RETRY_PREDICATE,
private static final RetryWithDelay TEST_ITEM_FINISH_REQUEST_RETRY = new RetryWithDelay(TEST_ITEM_FINISH_RETRY_PREDICATE,
ITEM_FINISH_MAX_RETRIES,
TimeUnit.SECONDS.toMillis(ITEM_FINISH_RETRY_TIMEOUT)
);
Expand All @@ -101,14 +100,14 @@ public class LaunchImpl extends Launch {
protected final ComputationConcurrentHashMap QUEUE = new ComputationConcurrentHashMap();

protected final Maybe<String> launch;
protected final StartLaunchRQ startRq;
protected final Maybe<ProjectSettingsResource> projectSettings;
private final ExecutorService executor;
private final Scheduler scheduler;
private StatisticsService statisticsService;
private final StartLaunchRQ startRq;

protected LaunchImpl(@Nonnull final ReportPortalClient reportPortalClient,
@Nonnull final ListenerParameters parameters, @Nonnull final StartLaunchRQ rq,
@Nonnull final ExecutorService executorService) {
protected LaunchImpl(@Nonnull final ReportPortalClient reportPortalClient, @Nonnull final ListenerParameters parameters,
@Nonnull final StartLaunchRQ rq, @Nonnull final ExecutorService executorService) {
super(reportPortalClient, parameters);
requireNonNull(parameters, "Parameters shouldn't be NULL");
executor = requireNonNull(executorService);
Expand All @@ -133,11 +132,12 @@ protected LaunchImpl(@Nonnull final ReportPortalClient reportPortalClient,
emitter.onComplete();
});
}).cache();
projectSettings = ofNullable(getClient().getProjectSettings()).map(settings -> settings.subscribeOn(getScheduler()).cache())
.orElse(Maybe.empty());
}

protected LaunchImpl(@Nonnull final ReportPortalClient reportPortalClient,
@Nonnull final ListenerParameters parameters, @Nonnull final Maybe<String> launchMaybe,
@Nonnull final ExecutorService executorService) {
protected LaunchImpl(@Nonnull final ReportPortalClient reportPortalClient, @Nonnull final ListenerParameters parameters,
@Nonnull final Maybe<String> launchMaybe, @Nonnull final ExecutorService executorService) {
super(reportPortalClient, parameters);
requireNonNull(parameters, "Parameters shouldn't be NULL");
executor = requireNonNull(executorService);
Expand All @@ -147,14 +147,13 @@ protected LaunchImpl(@Nonnull final ReportPortalClient reportPortalClient,

LOGGER.info("Rerun: {}", parameters.isRerun());
launch = launchMaybe.cache();
projectSettings = ofNullable(getClient().getProjectSettings()).map(settings -> settings.subscribeOn(getScheduler()).cache())
.orElse(Maybe.empty());
}

private static StartLaunchRQ emptyStartLaunchForStatistics() {
StartLaunchRQ result = new StartLaunchRQ();
result.setAttributes(Collections.singleton(new ItemAttributesRQ(DefaultProperties.AGENT.getName(),
CUSTOM_AGENT,
true
)));
result.setAttributes(Collections.singleton(new ItemAttributesRQ(DefaultProperties.AGENT.getName(), CUSTOM_AGENT, true)));
return result;
}

Expand Down Expand Up @@ -214,8 +213,7 @@ private Set<ItemAttributesRQ> truncateAttributes(@Nullable final Set<ItemAttribu
ItemAttributesRQ updated = attribute;
int keyLength = ofNullable(updated.getKey()).map(String::length).orElse(0);
if (keyLength > limit && keyLength > replacement.length()) {
updated = new ItemAttributesRQ(
updated.getKey().substring(0, limit - replacement.length()) + replacement,
updated = new ItemAttributesRQ(updated.getKey().substring(0, limit - replacement.length()) + replacement,
updated.getValue(),
updated.isSystem()
);
Expand Down Expand Up @@ -249,11 +247,11 @@ private void truncateAttributes(@Nonnull final FinishExecutionRQ rq) {
protected Maybe<String> start(boolean statistics) {
launch.subscribe(logMaybeResults("Launch start"));
ListenerParameters params = getParameters();
if(params.isPrintLaunchUuid()) {
if (params.isPrintLaunchUuid()) {
launch.subscribe(printLaunch(params));
}
LaunchLoggingContext.init(this.launch, getClient(), getScheduler(), getParameters());
if(statistics) {
if (statistics) {
getStatisticsService().sendEvent(launch, startRq);
}
return launch;
Expand Down Expand Up @@ -322,10 +320,7 @@ public Maybe<String> startTestItem(final StartTestItemRQ request) {

Maybe<String> item = launch.flatMap((Function<String, Maybe<String>>) launchId -> {
rq.setLaunchUuid(launchId);
return getClient().startTestItem(rq)
.retry(DEFAULT_REQUEST_RETRY)
.doOnSuccess(logCreated("item"))
.map(TO_ID);
return getClient().startTestItem(rq).retry(DEFAULT_REQUEST_RETRY).doOnSuccess(logCreated("item")).map(TO_ID);
}).cache();

item.subscribeOn(getScheduler()).subscribe(logMaybeResults("Start test item"));
Expand All @@ -345,8 +340,7 @@ public Maybe<String> startTestItem(final StartTestItemRQ request) {
* @return Test Item ID promise
*/
@Nonnull
public Maybe<String> startTestItem(final Maybe<String> parentId, final Maybe<String> retryOf,
final StartTestItemRQ rq) {
public Maybe<String> startTestItem(final Maybe<String> parentId, final Maybe<String> retryOf, final StartTestItemRQ rq) {
return retryOf.flatMap((Function<String, Maybe<String>>) s -> startTestItem(parentId, rq)).cache();
}

Expand Down Expand Up @@ -389,16 +383,43 @@ public Maybe<String> startTestItem(final Maybe<String> parentId, final StartTest
return item;
}

protected void completeBtsIssues(@Nullable Issue issue) {
if(!ofNullable(issue).map(Issue::getExternalSystemIssues).filter(issues -> !issues.isEmpty()).isPresent()) {
/**
* Lookup for the Issue Type locator in project settings and fill missed external issue fields based on properties.
*
* @param issue Issue to complete
*/
protected void completeIssues(@Nonnull Issue issue) {
String issueType = issue.getIssueType();
if (StringUtils.isBlank(issueType)) {
return;
}
ofNullable(projectSettings.blockingGet()).map(ProjectSettingsResource::getSubTypes)
.ifPresent(subTypes -> subTypes.values().stream().flatMap(List::stream).forEach(value -> {
if (issueType.equals(value.getLocator())) {
return;
}
if (issueType.equalsIgnoreCase(value.getShortName())) {
issue.setIssueType(value.getLocator());
return;
}
if (issueType.equalsIgnoreCase(value.getLongName())) {
issue.setIssueType(value.getLocator());
return;
}
if (issueType.equals(value.getTypeRef())) {
issue.setIssueType(value.getLocator());
}
}));

if (!ofNullable(issue.getExternalSystemIssues()).filter(issues -> !issues.isEmpty()).isPresent()) {
return;
}
ListenerParameters params = getParameters();
Optional<String> btsUrl = ofNullable(params.getBtsUrl()).filter(StringUtils::isNotBlank);
Optional<String> btsProjectId = ofNullable(params.getBtsProjectId()).filter(StringUtils::isNotBlank);
Optional<String> btsIssueUrl = ofNullable(params.getBtsIssueUrl()).filter(StringUtils::isNotBlank);
issue.getExternalSystemIssues().stream().filter(Objects::nonNull).forEach(externalIssue -> {
if(StringUtils.isBlank(externalIssue.getTicketId())) {
if (StringUtils.isBlank(externalIssue.getTicketId())) {
return;
}
if (btsUrl.isPresent() && StringUtils.isBlank(externalIssue.getBtsUrl())) {
Expand Down Expand Up @@ -446,13 +467,21 @@ public Maybe<OperationCompletionRS> finishTestItem(final Maybe<String> item, fin
getStepReporter().finishPreviousStep(ofNullable(rq.getStatus()).map(ItemStatus::valueOf).orElse(null));

ItemStatus status = ofNullable(rq.getStatus()).map(ItemStatus::valueOf).orElse(null);
if (status == ItemStatus.FAILED || (status == ItemStatus.SKIPPED && getParameters().getSkippedAnIssue())) {
completeBtsIssues(rq.getIssue());
} else if (status == ItemStatus.SKIPPED && !getParameters().getSkippedAnIssue()) {
rq.setIssue(Launch.NOT_ISSUE);
} else if (status == ItemStatus.PASSED && rq.getIssue() != null && getParameters().isBtsIssueFail()) {
rq.setStatus(ItemStatus.FAILED.name());
rq.setIssue(StaticStructuresUtils.REDUNDANT_ISSUE);
if (rq.getIssue() == null) {
if (status == ItemStatus.SKIPPED && !getParameters().getSkippedAnIssue()) {
rq.setIssue(Launch.NOT_ISSUE);
}
} else {
if (status == ItemStatus.FAILED || (status == ItemStatus.SKIPPED && getParameters().getSkippedAnIssue())) {
completeIssues(rq.getIssue());
} else if (status == ItemStatus.PASSED) {
if (getParameters().isBtsIssueFail()) {
rq.setStatus(ItemStatus.FAILED.name());
rq.setIssue(StaticStructuresUtils.REDUNDANT_ISSUE);
} else {
rq.setIssue(null);
}
}
}

QUEUE.getOrCompute(launch).addToQueue(LoggingContext.complete());
Expand Down Expand Up @@ -523,7 +552,7 @@ public Maybe<String> getParent() {
}
}

protected static class ComputationConcurrentHashMap extends ConcurrentHashMap<Maybe<String>, LaunchImpl.TreeItem> {
protected static class ComputationConcurrentHashMap extends ConcurrentHashMap<Maybe<String>, LaunchImpl.TreeItem> {
public LaunchImpl.TreeItem getOrCompute(Maybe<String> key) {
return computeIfAbsent(key, k -> new LaunchImpl.TreeItem());
}
Expand Down
115 changes: 110 additions & 5 deletions src/test/java/com/epam/reportportal/service/LaunchTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import static com.epam.reportportal.util.test.CommonUtils.shutdownExecutorService;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
Expand Down Expand Up @@ -563,11 +564,7 @@ public void verify_not_failing_item_with_issue_on_passed_test_if_it_turned_off()
verify(rpClient).finishTestItem(eq(itemId.blockingGet()), captor.capture());
FinishTestItemRQ resultFinishRq = captor.getValue();

assertThat(resultFinishRq.getIssue(), notNullValue());
Issue issue = resultFinishRq.getIssue();
assertThat(issue.getIssueType(), nullValue());
assertThat(issue.getComment(), nullValue());
assertThat(issue.getExternalSystemIssues(), nullValue());
assertThat(resultFinishRq.getIssue(), nullValue());
}

@Test
Expand Down Expand Up @@ -646,4 +643,112 @@ public void verify_external_issue_url_is_used_for_project_and_ticket_ids() {
Issue.ExternalSystemIssue resultExternalIssue = resultFinishRq.getIssue().getExternalSystemIssues().iterator().next();
assertThat(resultExternalIssue.getUrl(), equalTo("https://test.com/example_project/issue/RP-001"));
}

@Test
public void verify_issue_type_lookup_by_locator() {
simulateStartLaunchResponse(rpClient);
simulateStartTestItemResponse(rpClient);
simulateFinishTestItemResponse(rpClient);
when(rpClient.getProjectSettings()).thenReturn(Maybe.just(standardProjectSettings()));

Launch launch = createLaunch(standardParameters());
String launchUuid = launch.start().blockingGet();
StartTestItemRQ itemRq = standardStartStepRequest();
itemRq.setLaunchUuid(launchUuid);
Maybe<String> itemId = launch.startTestItem(itemRq);
FinishTestItemRQ finishRq = positiveFinishRequest();
finishRq.setStatus(ItemStatus.FAILED.name());
Issue issue = new Issue();
issue.setIssueType("pb001");
finishRq.setIssue(issue);
launch.finishTestItem(itemId, finishRq).blockingGet();

ArgumentCaptor<FinishTestItemRQ> captor = ArgumentCaptor.forClass(FinishTestItemRQ.class);
verify(rpClient).finishTestItem(eq(itemId.blockingGet()), captor.capture());

FinishTestItemRQ resultFinishRq = captor.getValue();
assertThat(resultFinishRq.getIssue(), notNullValue());
assertThat(resultFinishRq.getIssue().getIssueType(), equalTo("pb001"));
}

@Test
public void verify_issue_type_lookup_by_short_name() {
simulateStartLaunchResponse(rpClient);
simulateStartTestItemResponse(rpClient);
simulateFinishTestItemResponse(rpClient);
when(rpClient.getProjectSettings()).thenReturn(Maybe.just(standardProjectSettings()));

Launch launch = createLaunch(standardParameters());
String launchUuid = launch.start().blockingGet();
StartTestItemRQ itemRq = standardStartStepRequest();
itemRq.setLaunchUuid(launchUuid);
Maybe<String> itemId = launch.startTestItem(itemRq);
FinishTestItemRQ finishRq = positiveFinishRequest();
finishRq.setStatus(ItemStatus.FAILED.name());
Issue issue = new Issue();
issue.setIssueType("ab");
finishRq.setIssue(issue);
launch.finishTestItem(itemId, finishRq).blockingGet();

ArgumentCaptor<FinishTestItemRQ> captor = ArgumentCaptor.forClass(FinishTestItemRQ.class);
verify(rpClient).finishTestItem(eq(itemId.blockingGet()), captor.capture());

FinishTestItemRQ resultFinishRq = captor.getValue();
assertThat(resultFinishRq.getIssue(), notNullValue());
assertThat(resultFinishRq.getIssue().getIssueType(), equalTo("ab001"));
}

@Test
public void verify_issue_type_lookup_by_long_name() {
simulateStartLaunchResponse(rpClient);
simulateStartTestItemResponse(rpClient);
simulateFinishTestItemResponse(rpClient);
when(rpClient.getProjectSettings()).thenReturn(Maybe.just(standardProjectSettings()));

Launch launch = createLaunch(standardParameters());
String launchUuid = launch.start().blockingGet();
StartTestItemRQ itemRq = standardStartStepRequest();
itemRq.setLaunchUuid(launchUuid);
Maybe<String> itemId = launch.startTestItem(itemRq);
FinishTestItemRQ finishRq = positiveFinishRequest();
finishRq.setStatus(ItemStatus.FAILED.name());
Issue issue = new Issue();
issue.setIssueType("system issue");
finishRq.setIssue(issue);
launch.finishTestItem(itemId, finishRq).blockingGet();

ArgumentCaptor<FinishTestItemRQ> captor = ArgumentCaptor.forClass(FinishTestItemRQ.class);
verify(rpClient).finishTestItem(eq(itemId.blockingGet()), captor.capture());

FinishTestItemRQ resultFinishRq = captor.getValue();
assertThat(resultFinishRq.getIssue(), notNullValue());
assertThat(resultFinishRq.getIssue().getIssueType(), equalTo("si001"));
}

@Test
public void verify_issue_type_lookup_by_type_reference() {
simulateStartLaunchResponse(rpClient);
simulateStartTestItemResponse(rpClient);
simulateFinishTestItemResponse(rpClient);
when(rpClient.getProjectSettings()).thenReturn(Maybe.just(standardProjectSettings()));

Launch launch = createLaunch(standardParameters());
String launchUuid = launch.start().blockingGet();
StartTestItemRQ itemRq = standardStartStepRequest();
itemRq.setLaunchUuid(launchUuid);
Maybe<String> itemId = launch.startTestItem(itemRq);
FinishTestItemRQ finishRq = positiveFinishRequest();
finishRq.setStatus(ItemStatus.FAILED.name());
Issue issue = new Issue();
issue.setIssueType("NO_DEFECT");
finishRq.setIssue(issue);
launch.finishTestItem(itemId, finishRq).blockingGet();

ArgumentCaptor<FinishTestItemRQ> captor = ArgumentCaptor.forClass(FinishTestItemRQ.class);
verify(rpClient).finishTestItem(eq(itemId.blockingGet()), captor.capture());

FinishTestItemRQ resultFinishRq = captor.getValue();
assertThat(resultFinishRq.getIssue(), notNullValue());
assertThat(resultFinishRq.getIssue().getIssueType(), equalTo("nd001"));
}
}
Loading

0 comments on commit d6ff563

Please sign in to comment.