Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release #256

Merged
merged 9 commits into from
Nov 15, 2024
Merged
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Changelog

## [Unreleased]
### Added
- `rp.launch.uuid.creation.skip` configuration properties to control Launch start on provided UUID, by @HardNorth

## [5.2.20]
### Fixed
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
58 changes: 34 additions & 24 deletions README_TEMPLATE.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public class ListenerParameters implements Cloneable {
// Due to shortcoming of payload calculation mechanism this value is set to 65 million of bytes rather than 65 megabytes
public static final long DEFAULT_BATCH_PAYLOAD_LIMIT = 65 * 1000 * 1000;

public static final boolean DEFAULT_LAUNCH_CREATION_SKIP = true;
public static final boolean DEFAULT_LAUNCH_UUID_PRINT = false;
public static final String DEFAULT_LAUNCH_UUID_OUTPUT = "stdout";

Expand All @@ -89,7 +90,6 @@ public class ListenerParameters implements Cloneable {
private Duration httpWriteTimeout;
private String projectName;
private String launchName;
private String launchUuid;
private Mode launchRunningMode;
private Set<ItemAttributesRQ> attributes;
private Boolean enable;
Expand Down Expand Up @@ -122,6 +122,8 @@ public class ListenerParameters implements Cloneable {
private String truncateReplacement;
private int attributeLengthLimit;

private String launchUuid;
private boolean isLaunchUuidCreationSkip;
private boolean printLaunchUuid;
private PrintStream printLaunchUuidOutput;

Expand Down Expand Up @@ -155,10 +157,12 @@ private static ChronoUnit toChronoUnit(@Nonnull TimeUnit t) {
@Nullable
private static Duration getDurationProperty(@Nonnull PropertiesLoader properties, @Nonnull ListenerProperty value,
@Nonnull ListenerProperty unit) {
return ofNullable(properties.getProperty(value)).map(Long::parseLong).map(t -> Duration.of(t,
ofNullable(properties.getProperty(unit)).map(u -> toChronoUnit(TimeUnit.valueOf(u)))
.orElse(ChronoUnit.MILLIS)
)).orElse(null);
return ofNullable(properties.getProperty(value)).map(Long::parseLong)
.map(t -> Duration.of(
t,
ofNullable(properties.getProperty(unit)).map(u -> toChronoUnit(TimeUnit.valueOf(u))).orElse(ChronoUnit.MILLIS)
))
.orElse(null);
}

/**
Expand Down Expand Up @@ -200,8 +204,8 @@ public ListenerParameters() {
this.attributeLengthLimit = DEFAULT_TRUNCATE_ATTRIBUTE_LIMIT;

this.printLaunchUuid = DEFAULT_LAUNCH_UUID_PRINT;
this.printLaunchUuidOutput =
OutputTypes.valueOf(DEFAULT_LAUNCH_UUID_OUTPUT.toUpperCase(Locale.ROOT)).getOutput();
this.printLaunchUuidOutput = OutputTypes.valueOf(DEFAULT_LAUNCH_UUID_OUTPUT.toUpperCase(Locale.ROOT)).getOutput();
this.isLaunchUuidCreationSkip = DEFAULT_LAUNCH_CREATION_SKIP;

this.btsIssueFail = DEFAULT_BTS_ISSUE_FAIL;
}
Expand All @@ -213,28 +217,21 @@ public ListenerParameters() {
*/
public ListenerParameters(PropertiesLoader properties) {
this.description = properties.getProperty(DESCRIPTION);
this.apiKey = ofNullable(properties.getProperty(API_KEY, properties.getProperty(UUID))).map(String::trim)
.orElse(null);
this.apiKey = ofNullable(properties.getProperty(API_KEY, properties.getProperty(UUID))).map(String::trim).orElse(null);
this.baseUrl = properties.getProperty(BASE_URL) != null ? properties.getProperty(BASE_URL).trim() : null;
this.proxyUrl = properties.getProperty(HTTP_PROXY_URL);
this.proxyUser = properties.getProperty(HTTP_PROXY_USER);
this.proxyPassword = properties.getProperty(HTTP_PROXY_PASSWORD);
this.httpLogging = properties.getPropertyAsBoolean(HTTP_LOGGING, DEFAULT_HTTP_LOGGING);

this.httpCallTimeout = getDurationProperty(properties, HTTP_CALL_TIMEOUT_VALUE, HTTP_CALL_TIMEOUT_UNIT);
this.httpConnectTimeout = getDurationProperty(properties,
HTTP_CONNECT_TIMEOUT_VALUE,
HTTP_CONNECT_TIMEOUT_UNIT
);
this.httpConnectTimeout = getDurationProperty(properties, HTTP_CONNECT_TIMEOUT_VALUE, HTTP_CONNECT_TIMEOUT_UNIT);
this.httpReadTimeout = getDurationProperty(properties, HTTP_READ_TIMEOUT_VALUE, HTTP_READ_TIMEOUT_UNIT);
this.httpWriteTimeout = getDurationProperty(properties, HTTP_WRITE_TIMEOUT_VALUE, HTTP_WRITE_TIMEOUT_UNIT);

this.projectName =
properties.getProperty(PROJECT_NAME) != null ? properties.getProperty(PROJECT_NAME).trim() : null;
this.projectName = properties.getProperty(PROJECT_NAME) != null ? properties.getProperty(PROJECT_NAME).trim() : null;
this.launchName = properties.getProperty(LAUNCH_NAME);
this.launchUuid = properties.getProperty(LAUNCH_UUID);
this.attributes = Collections.unmodifiableSet(AttributeParser.parseAsSet(properties.getProperty(
LAUNCH_ATTRIBUTES)));
this.attributes = Collections.unmodifiableSet(AttributeParser.parseAsSet(properties.getProperty(LAUNCH_ATTRIBUTES)));
this.launchRunningMode = parseLaunchMode(properties.getProperty(MODE));
this.enable = properties.getPropertyAsBoolean(ENABLE, DEFAULT_ENABLE);
this.isSkippedAnIssue = properties.getPropertyAsBoolean(SKIPPED_AS_ISSUE, DEFAULT_SKIP_ISSUE);
Expand All @@ -250,52 +247,41 @@ public ListenerParameters(PropertiesLoader properties) {
this.rerunOf = properties.getProperty(RERUN_OF);

this.asyncReporting = properties.getPropertyAsBoolean(ASYNC_REPORTING, DEFAULT_ASYNC_REPORTING);
this.callbackReportingEnabled = properties.getPropertyAsBoolean(CALLBACK_REPORTING_ENABLED,
DEFAULT_CALLBACK_REPORTING_ENABLED
);
this.callbackReportingEnabled = properties.getPropertyAsBoolean(CALLBACK_REPORTING_ENABLED, DEFAULT_CALLBACK_REPORTING_ENABLED);

this.ioPoolSize = properties.getPropertyAsInt(IO_POOL_SIZE, DEFAULT_IO_POOL_SIZE);

// client join parameters
clientJoin = properties.getPropertyAsBoolean(CLIENT_JOIN_MODE, DEFAULT_CLIENT_JOIN);
clientJoinMode = LaunchIdLockMode.valueOf(properties.getProperty(CLIENT_JOIN_MODE_VALUE,
DEFAULT_CLIENT_JOIN_MODE
));
clientJoinMode = LaunchIdLockMode.valueOf(properties.getProperty(CLIENT_JOIN_MODE_VALUE, DEFAULT_CLIENT_JOIN_MODE));
lockPortNumber = properties.getPropertyAsInt(CLIENT_JOIN_LOCK_PORT, DEFAULT_CLIENT_JOIN_LOCK_PORT);
lockFileName = properties.getProperty(FILE_LOCK_NAME, DEFAULT_LOCK_FILE_NAME);
syncFileName = properties.getProperty(FILE_SYNC_NAME, DEFAULT_SYNC_FILE_NAME);
clientJoinTimeout = ofNullable(properties.getProperty(CLIENT_JOIN_TIMEOUT_VALUE))
.map(t -> TimeUnit.valueOf(properties.getProperty(CLIENT_JOIN_TIMEOUT_UNIT,
clientJoinTimeout = ofNullable(properties.getProperty(CLIENT_JOIN_TIMEOUT_VALUE)).map(t -> TimeUnit.valueOf(properties.getProperty(CLIENT_JOIN_TIMEOUT_UNIT,
DEFAULT_CLIENT_JOIN_TIMEOUT_UNIT
)).toMillis(Long.parseLong(t)))
.orElse(DEFAULT_CLIENT_JOIN_TIMEOUT);
lockWaitTimeout = ofNullable(properties.getProperty(CLIENT_JOIN_LOCK_TIMEOUT_VALUE))
.map(t -> TimeUnit.valueOf(properties.getProperty(CLIENT_JOIN_LOCK_TIMEOUT_UNIT,
lockWaitTimeout = ofNullable(properties.getProperty(CLIENT_JOIN_LOCK_TIMEOUT_VALUE)).map(t -> TimeUnit.valueOf(properties.getProperty(CLIENT_JOIN_LOCK_TIMEOUT_UNIT,
DEFAULT_CLIENT_JOIN_LOCK_TIMEOUT_UNIT
)).toMillis(Long.parseLong(t)))
.orElse(DEFAULT_FILE_WAIT_TIMEOUT);
clientJoinLaunchTimeout = ofNullable(properties.getProperty(CLIENT_JOIN_LAUNCH_TIMEOUT_VALUE))
.map(t -> TimeUnit.valueOf(properties.getProperty(CLIENT_JOIN_LAUNCH_TIMEOUT_UNIT,
clientJoinLaunchTimeout = ofNullable(properties.getProperty(CLIENT_JOIN_LAUNCH_TIMEOUT_VALUE)).map(t -> TimeUnit.valueOf(properties.getProperty(CLIENT_JOIN_LAUNCH_TIMEOUT_UNIT,
DEFAULT_CLIENT_JOIN_LAUNCH_TIMEOUT_UNIT
)).toMillis(Long.parseLong(t)))
.orElse(DEFAULT_CLIENT_JOIN_LAUNCH_TIMEOUT);

this.rxBufferSize = properties.getPropertyAsInt(RX_BUFFER_SIZE, DEFAULT_RX_BUFFER_SIZE);

this.truncateFields = properties.getPropertyAsBoolean(TRUNCATE_FIELDS, DEFAULT_TRUNCATE);
this.truncateItemNamesLimit = properties.getPropertyAsInt(TRUNCATE_ITEM_NAME_LIMIT,
DEFAULT_TRUNCATE_ITEM_NAMES_LIMIT);
this.truncateItemNamesLimit = properties.getPropertyAsInt(TRUNCATE_ITEM_NAME_LIMIT, DEFAULT_TRUNCATE_ITEM_NAMES_LIMIT);
this.truncateReplacement = properties.getProperty(TRUNCATE_REPLACEMENT, DEFAULT_TRUNCATE_REPLACEMENT);
this.attributeLengthLimit = properties.getPropertyAsInt(TRUNCATE_ATTRIBUTE_LIMIT,
DEFAULT_TRUNCATE_ATTRIBUTE_LIMIT);
this.attributeLengthLimit = properties.getPropertyAsInt(TRUNCATE_ATTRIBUTE_LIMIT, DEFAULT_TRUNCATE_ATTRIBUTE_LIMIT);

this.launchUuid = properties.getProperty(LAUNCH_UUID);
this.isLaunchUuidCreationSkip = properties.getPropertyAsBoolean(LAUNCH_UUID_CREATION_SKIP, DEFAULT_LAUNCH_CREATION_SKIP);
this.printLaunchUuid = properties.getPropertyAsBoolean(LAUNCH_UUID_PRINT, DEFAULT_LAUNCH_UUID_PRINT);
this.printLaunchUuidOutput =
OutputTypes.valueOf(
properties
.getProperty(LAUNCH_UUID_PRINT_OUTPUT, DEFAULT_LAUNCH_UUID_OUTPUT)
.toUpperCase(Locale.ROOT)
).getOutput();
this.printLaunchUuidOutput = OutputTypes.valueOf(properties.getProperty(LAUNCH_UUID_PRINT_OUTPUT, DEFAULT_LAUNCH_UUID_OUTPUT)
.toUpperCase(Locale.ROOT)).getOutput();

this.btsProjectId = properties.getProperty(BTS_PROJECT);
this.btsUrl = properties.getProperty(BTS_URL);
Expand Down Expand Up @@ -380,6 +366,14 @@ public void setLaunchUuid(@Nullable String launchUuid) {
this.launchUuid = launchUuid;
}

public boolean isLaunchUuidCreationSkip() {
return isLaunchUuidCreationSkip;
}

public void setLaunchUuidCreationSkip(boolean launchUuidCreationSkip) {
isLaunchUuidCreationSkip = launchUuidCreationSkip;
}

public boolean isPrintLaunchUuid() {
return printLaunchUuid;
}
Expand Down Expand Up @@ -590,9 +584,7 @@ public void setHttpLogging(boolean httpLogging) {
}

public int getRxBufferSize() {
return ofNullable(System.getProperty("rx2.buffer-size")).map(Integer::valueOf)
.map(s -> Math.max(1, s))
.orElse(rxBufferSize);
return ofNullable(System.getProperty("rx2.buffer-size")).map(Integer::valueOf).map(s -> Math.max(1, s)).orElse(rxBufferSize);
}

public void setRxBufferSize(int size) {
Expand Down Expand Up @@ -743,6 +735,7 @@ public String toString() {
sb.append(", projectName='").append(projectName).append('\'');
sb.append(", launchName='").append(launchName).append('\'');
sb.append(", launchUuid='").append(launchUuid).append('\'');
sb.append(", launchUuidCreationSkip='").append(isLaunchUuidCreationSkip).append('\'');
sb.append(", printLaunchUuid='").append(printLaunchUuid).append('\'');
sb.append(", printLaunchUuidOutput='").append(printLaunchUuidOutput).append('\'');
sb.append(", launchRunningMode=").append(launchRunningMode);
Expand Down
25 changes: 16 additions & 9 deletions src/main/java/com/epam/reportportal/service/LaunchImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,13 @@ public class LaunchImpl extends Launch {
&& 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,
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 Down Expand Up @@ -127,10 +129,12 @@ protected LaunchImpl(@Nonnull final ReportPortalClient reportPortalClient, @Nonn
}).subscribeOn(getScheduler()).cache();

//noinspection ResultOfMethodCallIgnored
launchPromise.subscribe(rs -> emitter.onSuccess(rs.getId()), t -> {
LOG_ERROR.accept(t);
emitter.onComplete();
});
launchPromise.subscribe(
rs -> emitter.onSuccess(rs.getId()), t -> {
LOG_ERROR.accept(t);
emitter.onComplete();
}
);
}).cache();
projectSettings = ofNullable(getClient().getProjectSettings()).map(settings -> settings.subscribeOn(getScheduler()).cache())
.orElse(Maybe.empty());
Expand Down Expand Up @@ -213,14 +217,16 @@ 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()
);
}
int valueLength = ofNullable(updated.getValue()).map(String::length).orElse(0);
if (valueLength > limit && valueLength > replacement.length()) {
updated = new ItemAttributesRQ(updated.getKey(),
updated = new ItemAttributesRQ(
updated.getKey(),
updated.getValue().substring(0, limit - replacement.length()) + replacement,
updated.isSystem()
);
Expand Down Expand Up @@ -275,7 +281,7 @@ public Maybe<String> start() {
public void finish(final FinishExecutionRQ request) {
QUEUE.getOrCompute(launch).addToQueue(LaunchLoggingContext.complete());
Completable finish = Completable.concat(QUEUE.getOrCompute(launch).getChildren());
if (StringUtils.isBlank(getParameters().getLaunchUuid())) {
if (StringUtils.isBlank(getParameters().getLaunchUuid()) || !getParameters().isLaunchUuidCreationSkip()) {
FinishExecutionRQ rq = clonePojo(request, FinishExecutionRQ.class);
truncateAttributes(rq);
finish = finish.andThen(launch.map(id -> getClient().finishLaunch(id, rq)
Expand Down Expand Up @@ -464,6 +470,7 @@ public Maybe<OperationCompletionRS> finishTestItem(final Maybe<String> item, fin
FinishTestItemRQ rq = clonePojo(request, FinishTestItemRQ.class);
truncateAttributes(rq);

//noinspection ReactiveStreamsUnusedPublisher
getStepReporter().finishPreviousStep(ofNullable(rq.getStatus()).map(ItemStatus::valueOf).orElse(null));

ItemStatus status = ofNullable(rq.getStatus()).map(ItemStatus::valueOf).orElse(null);
Expand Down
45 changes: 27 additions & 18 deletions src/main/java/com/epam/reportportal/service/ReportPortal.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;

Expand Down Expand Up @@ -118,34 +121,42 @@ public Launch newLaunch(@Nonnull StartLaunchRQ rq) {
return Launch.NOOP_LAUNCH;
}

if (StringUtils.isNotBlank(parameters.getLaunchUuid())) {
// a Launch UUID specified, use it and do not start a new Launch
return new LaunchImpl(rpClient, parameters, Maybe.just(parameters.getLaunchUuid()), executor);
StartLaunchRQ rqCopy = clonePojo(rq, StartLaunchRQ.class);
String launchUuid = parameters.getLaunchUuid();
boolean launchUuidSet = StringUtils.isNotBlank(launchUuid);
if (launchUuidSet) {
if (parameters.isLaunchUuidCreationSkip()) {
// a Launch UUID specified, but we should skip its creation
return new LaunchImpl(rpClient, parameters, Maybe.just(launchUuid), executor);
} else {
// a Launch UUID specified, but we should create a new Launch with it
rqCopy.setUuid(launchUuid);
}
}

if (launchIdLock == null) {
// do not use multi-client mode
return new LaunchImpl(rpClient, parameters, rq, executor);
return new LaunchImpl(rpClient, parameters, rqCopy, executor);
}

final String instanceUuid = UUID.randomUUID().toString();
final String uuid = launchIdLock.obtainLaunchUuid(instanceUuid);
if (uuid == null) {
// timeout locking on file or interrupted, anyway it should be logged already
// we continue to operate normally, since this flag is set by default and we shouldn't fail launches because of it
return new LaunchImpl(rpClient, parameters, rq, executor);
// we continue to operate normally, since this flag is set by default, and we shouldn't fail launches because of it
return new LaunchImpl(rpClient, parameters, rqCopy, executor);
}

if (instanceUuid.equals(uuid)) {
// We got our own UUID as launch UUID, that means we are primary launch.
StartLaunchRQ rqCopy = clonePojo(rq, StartLaunchRQ.class);
rqCopy.setUuid(uuid);
// We got our own instance UUID, that means we are primary launch.
if (!launchUuidSet) {
// If we got Launch UUID from parameters, we should use it, otherwise we should use instance UUID as Launch UUID
rqCopy.setUuid(instanceUuid);
}
return new PrimaryLaunch(rpClient, parameters, rqCopy, executor, launchIdLock, instanceUuid);
} else {
Maybe<String> launch = Maybe.create(emitter -> {
emitter.onSuccess(uuid);
emitter.onComplete();
});
// If we got Launch UUID from parameters, we should use it, otherwise we should use obtained UUID as a Secondary Launch
Maybe<String> launch = launchUuidSet ? Maybe.just(launchUuid) : Maybe.just(uuid);
return new SecondaryLaunch(rpClient, parameters, launch, executor, launchIdLock, instanceUuid);
}
}
Expand Down Expand Up @@ -495,12 +506,10 @@ protected Retrofit buildRestEndpoint(@Nonnull final ListenerParameters parameter
builder.baseUrl(baseUrl);
} catch (NoSuchMethodError e) {
throw new InternalReportPortalClientException(
"Unable to initialize OkHttp client. "
+ "ReportPortal client supports OkHttp version 3.11.0 as minimum.\n"
"Unable to initialize OkHttp client. ReportPortal client supports OkHttp version 3.11.0 as minimum.\n"
+ "Please update OkHttp dependency.\n"
+ "Besides this usually happens due to old selenium-java version (it overrides our dependency), "
+ "please use selenium-java 3.141.0 as minimum.",
e
+ "please use selenium-java 3.141.0 as minimum.", e
);
}
return builder.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.from(executor)))
Expand Down
Loading