diff --git a/CHANGELOG.md b/CHANGELOG.md index 369a2aad..cd6d356a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## [Unreleased] +### Added +- Item name formatting according to passed parameters, by @HardNorth ## [5.4.8] ### Added diff --git a/src/main/java/com/epam/reportportal/service/Launch.java b/src/main/java/com/epam/reportportal/service/Launch.java index 8facd277..731dcba8 100644 --- a/src/main/java/com/epam/reportportal/service/Launch.java +++ b/src/main/java/com/epam/reportportal/service/Launch.java @@ -19,6 +19,7 @@ import com.epam.reportportal.service.step.DefaultStepReporter; import com.epam.reportportal.service.step.StepReporter; import com.epam.reportportal.utils.StaticStructuresUtils; +import com.epam.reportportal.utils.formatting.templating.TemplateConfiguration; import com.epam.ta.reportportal.ws.model.FinishExecutionRQ; import com.epam.ta.reportportal.ws.model.FinishTestItemRQ; import com.epam.ta.reportportal.ws.model.OperationCompletionRS; @@ -237,6 +238,14 @@ public ReportPortalClient getClient() { @Nonnull public abstract Maybe getLaunch(); + /** + * Returns current configuration of Template engine to adjust it. + * + * @return Template engine configuration. + */ + @Nonnull + public abstract TemplateConfiguration getTemplateConfiguration(); + /** * Launch implementation for disabled Reporting, every method does nothing. */ @@ -249,6 +258,8 @@ public ReportPortalClient getClient() { new ListenerParameters(), StepReporter.NOOP_STEP_REPORTER ) { + private final TemplateConfiguration templateConfiguration = new TemplateConfiguration(); + @Override public boolean useMicroseconds() { return false; @@ -323,6 +334,12 @@ public Maybe finishTestItem(Maybe itemId, FinishT public Maybe getLaunch() { return Maybe.empty(); } + + @Nonnull + @Override + public TemplateConfiguration getTemplateConfiguration() { + return templateConfiguration; + } }; /** diff --git a/src/main/java/com/epam/reportportal/service/LaunchImpl.java b/src/main/java/com/epam/reportportal/service/LaunchImpl.java index fb51c63e..79cddc62 100644 --- a/src/main/java/com/epam/reportportal/service/LaunchImpl.java +++ b/src/main/java/com/epam/reportportal/service/LaunchImpl.java @@ -26,6 +26,8 @@ import com.epam.reportportal.service.statistics.StatisticsService; import com.epam.reportportal.utils.*; import com.epam.reportportal.utils.files.ByteSource; +import com.epam.reportportal.utils.formatting.templating.TemplateConfiguration; +import com.epam.reportportal.utils.formatting.templating.TemplateProcessing; import com.epam.reportportal.utils.http.HttpRequestUtils; import com.epam.reportportal.utils.properties.DefaultProperties; import com.epam.ta.reportportal.ws.model.*; @@ -47,6 +49,7 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.time.Instant; @@ -54,11 +57,14 @@ import java.util.concurrent.*; import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; import static com.epam.reportportal.service.logs.LaunchLoggingCallback.LOG_ERROR; import static com.epam.reportportal.service.logs.LaunchLoggingCallback.LOG_SUCCESS; import static com.epam.reportportal.utils.BasicUtils.compareSemanticVersions; import static com.epam.reportportal.utils.ObjectUtils.clonePojo; +import static com.epam.reportportal.utils.ParameterUtils.NULL_VALUE; import static com.epam.reportportal.utils.SubscriptionUtils.*; import static com.epam.reportportal.utils.files.ImageConverter.convert; import static com.epam.reportportal.utils.files.ImageConverter.isImage; @@ -135,6 +141,7 @@ public class LaunchImpl extends Launch { private final ExecutorService executor; private final Scheduler scheduler; private final LoggingSubscriber loggingSubscriber; + private final TemplateConfiguration templateConfiguration; private StatisticsService statisticsService; private volatile Boolean useMicroseconds; @@ -199,6 +206,7 @@ protected LaunchImpl(@Nonnull final ReportPortalClient reportPortalClient, @Nonn logEmitter = createLogEmitter(getClient(), getParameters(), getScheduler(), loggingSubscriber); projectSettings = getProjectSettings(getClient(), getScheduler()); apiInfo = getApiInfo(getClient(), getScheduler()); + templateConfiguration = new TemplateConfiguration(); } /** @@ -242,6 +250,7 @@ protected LaunchImpl(@Nonnull final ReportPortalClient reportPortalClient, @Nonn logEmitter = createLogEmitter(getClient(), getParameters(), getScheduler(), loggingSubscriber); projectSettings = getProjectSettings(getClient(), getScheduler()); apiInfo = getApiInfo(getClient(), getScheduler()); + templateConfiguration = new TemplateConfiguration(); } private Supplier> getLaunchSupplier(@Nonnull final ReportPortalClient client, @Nonnull final Scheduler scheduler, @@ -330,6 +339,17 @@ StatisticsService getStatisticsService() { return statisticsService; } + /** + * Returns current configuration of Template engine to adjust it. + * + * @return Template engine configuration. + */ + @Nonnull + @Override + public TemplateConfiguration getTemplateConfiguration() { + return templateConfiguration; + } + private void truncateName(@Nonnull final StartTestItemRQ rq) { if (!getParameters().isTruncateFields() || rq.getName() == null || rq.getName().isEmpty()) { return; @@ -339,6 +359,34 @@ private void truncateName(@Nonnull final StartTestItemRQ rq) { rq.setName(BasicUtils.truncateString(name, params.getTruncateItemNamesLimit(), params.getTruncateReplacement())); } + private void applyNameFormat(@Nonnull StartTestItemRQ rq) { + TemplateConfiguration config = getTemplateConfiguration(); + List params = rq.getParameters(); + if (params == null || params.isEmpty() || rq.getName() == null || rq.getName().isEmpty()) { + return; + } + Map formatParameters = IntStream.range(0, params.size()).boxed().flatMap(i -> { + var param = params.get(i); + var newParam = new ParameterResource(); + newParam.setKey(param.getKey()); + newParam.setValue(param.getValue()); + var idxParam = new ParameterResource(); + idxParam.setKey(String.valueOf(i)); + idxParam.setValue(param.getValue()); + return Stream.of(newParam, idxParam); + }).peek(param -> { + if (param.getKey() == null) { + param.setKey(NULL_VALUE); + } + if (param.getValue() == null) { + param.setValue(NULL_VALUE); + } + }).collect(Collectors.toMap( + ParameterResource::getKey, ParameterResource::getValue, (existing, replacement) -> replacement + )); + rq.setName(TemplateProcessing.processTemplate(rq.getName(), null, null, formatParameters, config)); + } + @Nullable private Set truncateAttributes(@Nullable final Set attributes) { if (!getParameters().isTruncateFields() || attributes == null || attributes.isEmpty()) { @@ -574,6 +622,17 @@ private static Maybe createErrorResponse(Throwable cause) { return Maybe.error(cause); } + @NotNull + private StartTestItemRQ applyRequestModifications(StartTestItemRQ request) { + StartTestItemRQ rq = clonePojo(request, StartTestItemRQ.class); + rq.setStartTime(convertIfNecessary(rq.getStartTime())); + truncateName(rq); // Truncate before templating to not allow too long names to be passed to Template engine + applyNameFormat(rq); + truncateName(rq); // Truncate after templating to not allow too long names to be posted + truncateAttributes(rq); + return rq; + } + /** * Starts new root test item in ReportPortal asynchronously (non-blocking). * @@ -589,10 +648,7 @@ public Maybe startTestItem(final StartTestItemRQ request) { */ return createErrorResponse(new NullPointerException("StartTestItemRQ should not be null")); } - StartTestItemRQ rq = clonePojo(request, StartTestItemRQ.class); - rq.setStartTime(convertIfNecessary(rq.getStartTime())); - truncateName(rq); - truncateAttributes(rq); + StartTestItemRQ rq = applyRequestModifications(request); String itemDescription = String.format("root test item [%s] '%s'", rq.getType(), rq.getName()); final Maybe item = getLaunch().flatMap((Function>) launchId -> { @@ -627,10 +683,7 @@ public Maybe startTestItem(final Maybe parentId, final StartTest */ return createErrorResponse(new NullPointerException("StartTestItemRQ should not be null")); } - StartTestItemRQ rq = clonePojo(request, StartTestItemRQ.class); - rq.setStartTime(convertIfNecessary(rq.getStartTime())); - truncateName(rq); - truncateAttributes(rq); + StartTestItemRQ rq = applyRequestModifications(request); String itemDescription = String.format("child test item [%s] '%s'", rq.getType(), rq.getName()); final Maybe item = RxJavaPlugins.onAssembly(Maybe.zip( diff --git a/src/main/java/com/epam/reportportal/service/OAuth2PasswordGrantAuthInterceptor.java b/src/main/java/com/epam/reportportal/service/OAuth2PasswordGrantAuthInterceptor.java index aa5c8d41..e7f49fa5 100644 --- a/src/main/java/com/epam/reportportal/service/OAuth2PasswordGrantAuthInterceptor.java +++ b/src/main/java/com/epam/reportportal/service/OAuth2PasswordGrantAuthInterceptor.java @@ -111,10 +111,10 @@ public OAuth2PasswordGrantAuthInterceptor(@Nonnull ListenerParameters parameters clientBuilder.proxy(Proxy.NO_PROXY); } - ofNullable(parameters.getHttpConnectTimeout()).ifPresent(d -> clientBuilder.connectTimeout(d.toMillis(), TimeUnit.MILLISECONDS)); - ofNullable(parameters.getHttpReadTimeout()).ifPresent(d -> clientBuilder.readTimeout(d.toMillis(), TimeUnit.MILLISECONDS)); - ofNullable(parameters.getHttpWriteTimeout()).ifPresent(d -> clientBuilder.writeTimeout(d.toMillis(), TimeUnit.MILLISECONDS)); - ofNullable(parameters.getHttpCallTimeout()).ifPresent(d -> clientBuilder.callTimeout(d.toMillis(), TimeUnit.MILLISECONDS)); + ofNullable(parameters.getHttpConnectTimeout()).ifPresent(clientBuilder::connectTimeout); + ofNullable(parameters.getHttpReadTimeout()).ifPresent(clientBuilder::readTimeout); + ofNullable(parameters.getHttpWriteTimeout()).ifPresent(clientBuilder::writeTimeout); + ofNullable(parameters.getHttpCallTimeout()).ifPresent(clientBuilder::callTimeout); client = clientBuilder.build(); } diff --git a/src/main/java/com/epam/reportportal/utils/AttributeParser.java b/src/main/java/com/epam/reportportal/utils/AttributeParser.java index ae7341ae..9edf52ea 100644 --- a/src/main/java/com/epam/reportportal/utils/AttributeParser.java +++ b/src/main/java/com/epam/reportportal/utils/AttributeParser.java @@ -54,7 +54,7 @@ public static Set parseAsSet(@Nullable String rawAttributes) { if (null == rawAttributes) { return Collections.emptySet(); } - Set attributes = new HashSet<>(); + Set attributes = new LinkedHashSet<>(); String[] attributesSplit = rawAttributes.trim().split(ATTRIBUTES_SPLITTER); for (String s : attributesSplit) { diff --git a/src/test/java/com/epam/reportportal/service/LaunchTest.java b/src/test/java/com/epam/reportportal/service/LaunchTest.java index 44718a3b..d0d2a36d 100644 --- a/src/test/java/com/epam/reportportal/service/LaunchTest.java +++ b/src/test/java/com/epam/reportportal/service/LaunchTest.java @@ -35,6 +35,7 @@ import com.epam.ta.reportportal.ws.model.launch.StartLaunchRQ; import io.reactivex.Maybe; import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; import org.apache.commons.lang3.RandomStringUtils; import org.aspectj.lang.reflect.MethodSignature; import org.awaitility.Awaitility; @@ -42,6 +43,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.ArgumentCaptor; import org.mockito.Mock; @@ -52,10 +55,12 @@ import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Collections; +import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.stream.Stream; import static com.epam.reportportal.test.TestUtils.*; import static com.epam.reportportal.util.test.CommonUtils.shutdownExecutorService; @@ -350,6 +355,54 @@ public void launch_should_truncate_long_item_names() { assertThat(testName, allOf(hasLength(1024), endsWith("..."))); } + @Nonnull + private static ParameterResource parameter(@Nullable String key, @Nullable String value) { + ParameterResource resource = new ParameterResource(); + resource.setKey(key); + resource.setValue(value); + return resource; + } + + private static Stream item_name_templates() { + return Stream.of( + Arguments.of("Step {param}", List.of(parameter("param", "value")), "Step value"), + Arguments.of("Step {0}", List.of(parameter("paramByIdx", "valueByIdx")), "Step valueByIdx"), + Arguments.of("Step {param}", null, "Step {param}"), + Arguments.of("Step {0}", List.of(parameter(null, "value")), "Step value"), + Arguments.of("Step {param}", List.of(parameter("param", "value"), parameter("param", "value2")), "Step value2"), + Arguments.of("Step {0} {1}", List.of(parameter("param", "value"), parameter("param", "value2")), "Step value value2"), + Arguments.of("Step {param}", List.of(parameter("param", null)), "Step NULL") + ); + } + + @ParameterizedTest + @MethodSource("item_name_templates") + public void launch_should_format_item_name_template(String templateName, List parameters, String expectedName) { + simulateStartLaunchResponse(rpClient); + simulateStartTestItemResponse(rpClient); + simulateStartChildTestItemResponse(rpClient); + Launch launch = createLaunch(); + + StartTestItemRQ rootRq = standardStartSuiteRequest(); + rootRq.setName(templateName); + rootRq.setParameters(parameters); + StartTestItemRQ childRq = standardStartTestRequest(); + childRq.setName(templateName); + childRq.setParameters(parameters); + + launch.start(); + Maybe rootId = launch.startTestItem(rootRq); + launch.startTestItem(rootId, childRq); + + ArgumentCaptor rootCaptor = ArgumentCaptor.forClass(StartTestItemRQ.class); + verify(rpClient, timeout(1000)).startTestItem(rootCaptor.capture()); + assertThat(rootCaptor.getValue().getName(), equalTo(expectedName)); + + ArgumentCaptor childCaptor = ArgumentCaptor.forClass(StartTestItemRQ.class); + verify(rpClient, timeout(1000)).startTestItem(eq(rootId.blockingGet()), childCaptor.capture()); + assertThat(childCaptor.getValue().getName(), equalTo(expectedName)); + } + private static void verify_attribute_truncation(Set attributes) { assertThat(attributes, hasSize(1)); ItemAttributesRQ suiteAttribute = attributes.iterator().next();