Skip to content

Commit

Permalink
Merge pull request #35 from reportportal/call_logic_fix
Browse files Browse the repository at this point in the history
Call logic fix
  • Loading branch information
HardNorth authored Sep 13, 2024
2 parents 875f389 + 42541dc commit e2fdfa6
Show file tree
Hide file tree
Showing 11 changed files with 134 additions and 51 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ on:
pull_request:
branches:
- main
- develop

jobs:
build:
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Changelog

## [Unreleased]
### Changed
- Client version updated on [5.2.14](https://github.com/reportportal/client-java/releases/tag/5.2.14), by @HardNorth
- Called inner Features are now Nested Steps inside base Feature, by @HardNorth
- Unify Markdown description generation with other agents, by @HardNorth

## [5.0.5]
### Changed
Expand Down
1 change: 0 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ compileJava.options.encoding = 'UTF-8'
compileTestJava.options.encoding = 'UTF-8'

repositories {
mavenLocal()
mavenCentral()
}

Expand Down
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
name=agent-java-karate
version=5.0.6-SNAPSHOT
version=5.1.0-SNAPSHOT
description=EPAM ReportPortal. Karate test framework [1.3.1, ) adapter
gradle_version=8.2
karate_version=1.4.1
junit_version=5.10.1
mockito_version=5.4.0
test_utils_version=0.0.3
client_version=5.2.13
client_version=5.2.14
slf4j_api_version=2.0.7
logger_version=5.2.2
hamcrest_version=2.2
Expand Down
58 changes: 46 additions & 12 deletions src/main/java/com/epam/reportportal/karate/ReportPortalHook.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@

import com.epam.reportportal.karate.utils.BlockingConcurrentHashMap;
import com.epam.reportportal.listeners.ItemStatus;
import com.epam.reportportal.listeners.ItemType;
import com.epam.reportportal.listeners.ListenerParameters;
import com.epam.reportportal.listeners.LogLevel;
import com.epam.reportportal.service.Launch;
import com.epam.reportportal.service.ReportPortal;
import com.epam.reportportal.utils.MemoizingSupplier;
import com.epam.reportportal.utils.StatusEvaluation;
import com.epam.reportportal.utils.markdown.MarkdownUtils;
import com.epam.ta.reportportal.ws.model.FinishExecutionRQ;
import com.epam.ta.reportportal.ws.model.FinishTestItemRQ;
import com.epam.ta.reportportal.ws.model.StartTestItemRQ;
Expand All @@ -34,15 +36,13 @@
import com.intuit.karate.http.HttpRequest;
import com.intuit.karate.http.Response;
import io.reactivex.Maybe;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.Optional;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;

Expand All @@ -64,6 +64,7 @@ public class ReportPortalHook implements RuntimeHook {
private final Map<String, ItemStatus> backgroundStatusMap = new ConcurrentHashMap<>();
private final Map<String, Maybe<String>> stepIdMap = new ConcurrentHashMap<>();
private final Map<Maybe<String>, Date> stepStartTimeMap = new ConcurrentHashMap<>();
private final Set<Maybe<String>> innerFeatures = Collections.newSetFromMap(new ConcurrentHashMap<>());
private volatile Thread shutDownHook;

/**
Expand All @@ -72,9 +73,9 @@ public class ReportPortalHook implements RuntimeHook {
* @param reportPortal the ReportPortal instance
*/
public ReportPortalHook(ReportPortal reportPortal) {
ListenerParameters params = reportPortal.getParameters();
StartLaunchRQ rq = buildStartLaunchRq(params);
launch = new MemoizingSupplier<>(() -> {
ListenerParameters params = reportPortal.getParameters();
StartLaunchRQ rq = buildStartLaunchRq(params);
Launch newLaunch = reportPortal.newLaunch(rq);
//noinspection ReactiveStreamsUnusedPublisher
newLaunch.start();
Expand Down Expand Up @@ -153,7 +154,7 @@ protected StartTestItemRQ buildStartFeatureRq(@Nonnull FeatureRuntime fr) {
String parameters = String.format(PARAMETERS_PATTERN, formatParametersAsTable(getParameters(args)));
String description = rq.getDescription();
if (isNotBlank(description)) {
rq.setDescription(String.format(MARKDOWN_DELIMITER_PATTERN, parameters, description));
rq.setDescription(MarkdownUtils.asTwoParts(parameters, description));
} else {
rq.setDescription(parameters);
}
Expand All @@ -163,9 +164,28 @@ protected StartTestItemRQ buildStartFeatureRq(@Nonnull FeatureRuntime fr) {

@Override
public boolean beforeFeature(FeatureRuntime fr) {
StartTestItemRQ rq = buildStartFeatureRq(fr);
featureIdMap.computeIfAbsent(fr.featureCall.feature.getNameForReport(),
f -> new MemoizingSupplier<>(() -> launch.get().startTestItem(buildStartFeatureRq(fr)))
);
f -> new MemoizingSupplier<>(() -> {
if(fr.caller == null || fr.caller.depth == 0) {
return launch.get().startTestItem(rq);
} else {
Maybe<String> scenarioId = scenarioIdMap.get(fr.caller.parentRuntime.scenario.getUniqueId());
if (scenarioId == null) {
LOGGER.error("ERROR: Trying to post unspecified scenario.");
return launch.get().startTestItem(rq);
}
rq.setType(ItemType.STEP.name());
rq.setHasStats(false);
rq.setName(getInnerFeatureName(rq.getName()));
Maybe<String> itemId = launch.get().startTestItem(scenarioId, rq);
innerFeatures.add(itemId);
if (StringUtils.isNotBlank(rq.getDescription())) {
ReportPortalUtils.sendLog(itemId, rq.getDescription(), LogLevel.INFO, rq.getStartTime());
}
return itemId;
}
}));
return true;
}

Expand All @@ -189,6 +209,7 @@ public void afterFeature(FeatureRuntime fr) {
optionalId.ifPresent(featureId -> {
//noinspection ReactiveStreamsUnusedPublisher
launch.get().finishTestItem(featureId, buildFinishFeatureRq(fr));
innerFeatures.remove(featureId);
});
}

Expand All @@ -200,18 +221,31 @@ public void afterFeature(FeatureRuntime fr) {
*/
@Nonnull
protected StartTestItemRQ buildStartScenarioRq(@Nonnull ScenarioRuntime sr) {
return ReportPortalUtils.buildStartScenarioRq(sr.result);
StartTestItemRQ rq = ReportPortalUtils.buildStartScenarioRq(sr.result);
ofNullable(featureIdMap.get(sr.featureRuntime.featureCall.feature.getNameForReport()))
.map(Supplier::get)
.map(featureId -> innerFeatures.contains(featureId) ? featureId : null)
.ifPresent(featureId -> {
rq.setType(ItemType.STEP.name());
rq.setHasStats(false);
rq.setName(getInnerScenarioName(rq.getName()));
});
return rq;
}

@Override
public boolean beforeScenario(ScenarioRuntime sr) {
Optional<Maybe<String>> optionalId = ofNullable(featureIdMap.get(sr.featureRuntime.featureCall.feature.getNameForReport())).map(Supplier::get);
StartTestItemRQ rq = buildStartScenarioRq(sr);
Optional<Maybe<String>> optionalId = ofNullable(featureIdMap.get(sr.featureRuntime.featureCall.feature.getNameForReport()))
.map(Supplier::get);
if (optionalId.isEmpty()) {
LOGGER.error("ERROR: Trying to post unspecified feature.");
}
optionalId.ifPresent(featureId -> {
StartTestItemRQ rq = buildStartScenarioRq(sr);
Maybe<String> scenarioId = launch.get().startTestItem(featureId, rq);
if (innerFeatures.contains(featureId) && StringUtils.isNotBlank(rq.getDescription())) {
ReportPortalUtils.sendLog(scenarioId, rq.getDescription(), LogLevel.INFO);
}
scenarioIdMap.put(sr.scenario.getUniqueId(), scenarioId);
});
return true;
Expand Down
51 changes: 45 additions & 6 deletions src/main/java/com/epam/reportportal/karate/ReportPortalUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.epam.reportportal.utils.AttributeParser;
import com.epam.reportportal.utils.ParameterUtils;
import com.epam.reportportal.utils.TestCaseIdUtils;
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;
Expand Down Expand Up @@ -60,8 +61,10 @@ public class ReportPortalUtils {
public static final String SKIPPED_ISSUE_KEY = "skippedIssue";
public static final String SCENARIO_CODE_REFERENCE_PATTERN = "%s/[SCENARIO:%s]";
public static final String EXAMPLE_CODE_REFERENCE_PATTERN = "%s/[EXAMPLE:%s%s]";
public static final String MARKDOWN_DELIMITER = "\n\n---\n\n";
public static final String MARKDOWN_DELIMITER = "\n" + MarkdownUtils.LOGICAL_SEPARATOR + "\n";
public static final String MARKDOWN_DELIMITER_PATTERN = "%s" + MARKDOWN_DELIMITER + "%s";
public static final String FEATURE_TAG = "Feature: ";
public static final String SCENARIO_TAG = "Scenario: ";
private static final Logger LOGGER = LoggerFactory.getLogger(ReportPortalUtils.class);
private static final String PARAMETER_ITEMS_START = "[";
private static final String PARAMETER_ITEMS_END = "]";
Expand Down Expand Up @@ -244,7 +247,7 @@ public static StartTestItemRQ buildStartFeatureRq(@Nonnull Feature feature) {
String featurePath = feature.getResource().getUri().toString();
String description = feature.getDescription();
if (isNotBlank(description)) {
rq.setDescription(String.format(MARKDOWN_DELIMITER_PATTERN, featurePath, description));
rq.setDescription(MarkdownUtils.asTwoParts(featurePath, description));
} else {
rq.setDescription(featurePath);
}
Expand Down Expand Up @@ -319,13 +322,17 @@ public static StartTestItemRQ buildStartScenarioRq(@Nonnull ScenarioResult resul
@Nonnull
public static FinishTestItemRQ buildFinishScenarioRq(@Nonnull ScenarioResult result) {
Scenario scenario = result.getScenario();
FinishTestItemRQ rq = buildFinishTestItemRq(Calendar.getInstance().getTime(), result.getFailureMessageForDisplay() == null ? ItemStatus.PASSED : ItemStatus.FAILED);
FinishTestItemRQ rq = buildFinishTestItemRq(
Calendar.getInstance().getTime(),
result.getFailureMessageForDisplay() == null ? ItemStatus.PASSED : ItemStatus.FAILED
);
rq.setDescription(buildDescription(scenario, result.getErrorMessage(), getParameters(scenario)));
return rq;
}

@Nonnull
private static String buildDescription(@Nonnull Scenario scenario, @Nullable String errorMessage, @Nullable List<ParameterResource> parameters) {
private static String buildDescription(@Nonnull Scenario scenario, @Nullable String errorMessage,
@Nullable List<ParameterResource> parameters) {
StringBuilder descriptionBuilder = new StringBuilder();

if (parameters != null && !parameters.isEmpty()) {
Expand Down Expand Up @@ -425,18 +432,30 @@ public static ItemStatus getStepStatus(String status) {
* @param itemId item ID future
* @param message log message to send
* @param level log level
* @param logTime log time
*/
public static void sendLog(Maybe<String> itemId, String message, LogLevel level) {
public static void sendLog(Maybe<String> itemId, String message, LogLevel level, Date logTime) {
ReportPortal.emitLog(itemId, id -> {
SaveLogRQ rq = new SaveLogRQ();
rq.setMessage(message);
rq.setItemUuid(id);
rq.setLevel(level.name());
rq.setLogTime(Calendar.getInstance().getTime());
rq.setLogTime(logTime);
return rq;
});
}

/**
* Send Step logs to ReportPortal.
*
* @param itemId item ID future
* @param message log message to send
* @param level log level
*/
public static void sendLog(Maybe<String> itemId, String message, LogLevel level) {
sendLog(itemId, message, level, Calendar.getInstance().getTime());
}

/**
* Builds markdown representation of some code or script to be logged to ReportPortal
*
Expand All @@ -446,4 +465,24 @@ public static void sendLog(Maybe<String> itemId, String message, LogLevel level)
public static String asMarkdownCode(String code) {
return String.format(MARKDOWN_CODE_PATTERN, code);
}

/**
* Build name of inner scenario (called by another scenario).
*
* @param name Scenario name
* @return Inner scenario name
*/
public static String getInnerScenarioName(String name) {
return SCENARIO_TAG + name;
}

/**
* Build name of inner feature (called by another scenario).
*
* @param name Feature name
* @return Inner feature name
*/
public static String getInnerFeatureName(String name) {
return FEATURE_TAG + name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public T get(long timeout, TimeUnit unit) throws InterruptedException {

private final Map<K, BlockingReference<V>> map = new ConcurrentHashMap<>();

public void computeIfAbsent(@Nonnull K key, Function<?, V> mappingFunction) {
public void computeIfAbsent(@Nonnull K key, Function<K, V> mappingFunction) {
map.computeIfAbsent(key, k -> new BlockingReference<>()).set(mappingFunction);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
Expand All @@ -44,33 +45,36 @@
public class CallWithParametersHookTest {
private static final String TEST_FEATURE = "classpath:feature/call.feature";
private static final String PARAMETERS_DESCRIPTION_PATTERN =
"Parameters:\n\n" + MarkdownUtils.TABLE_INDENT + "| vara | result |\n" + MarkdownUtils.TABLE_INDENT + "|------|--------|\n"
+ MarkdownUtils.TABLE_INDENT + "|  2   |   4    |\n\n" + MarkdownUtils.TABLE_ROW_SEPARATOR;
private final List<String> featureIds = Stream.generate(() -> CommonUtils.namedId("feature_")).limit(2).collect(Collectors.toList());
private final List<String> scenarioIds = Stream.generate(() -> CommonUtils.namedId("scenario_")).limit(2).collect(Collectors.toList());
private final List<String> stepIds = Stream.generate(() -> CommonUtils.namedId("step_")).limit(4).collect(Collectors.toList());
private final List<Pair<String, Collection<Pair<String, List<String>>>>> features = Stream.of(
Pair.of(featureIds.get(0),
(Collection<Pair<String, List<String>>>) Collections.singletonList(Pair.of(
scenarioIds.get(0),
Collections.singletonList(stepIds.get(0))
))
),
Pair.of(
featureIds.get(1),
(Collection<Pair<String, List<String>>>) Collections.singletonList(Pair.of(
scenarioIds.get(1),
stepIds.subList(1, stepIds.size())
))
)
"Parameters:\n\n" + MarkdownUtils.TABLE_INDENT + "|\u00A0vara\u00A0|\u00A0result\u00A0|\n" + MarkdownUtils.TABLE_INDENT
+ "|------|--------|\n" + MarkdownUtils.TABLE_INDENT
+ "|\u00A0\u00A02\u00A0\u00A0\u00A0|\u00A0\u00A0\u00A04\u00A0\u00A0\u00A0\u00A0|\n"
+ MarkdownUtils.TABLE_ROW_SEPARATOR;
private final String featureId = CommonUtils.namedId("feature_");
private final String scenarioId = CommonUtils.namedId("scenario_");
private final String innerFeatureId = CommonUtils.namedId("feature_step_");
private final List<String> stepIds = Arrays.asList(CommonUtils.namedId("step_"), innerFeatureId);
private final String innerScenarioId = CommonUtils.namedId("scenario_step_");
private final List<String> innerStepIds = Stream.generate(() -> CommonUtils.namedId("inner_step_"))
.limit(3)
.collect(Collectors.toList());

private final List<Pair<String, Collection<Pair<String, List<String>>>>> features = Stream.of(Pair.of(featureId,
(Collection<Pair<String, List<String>>>) Collections.singletonList(Pair.of(scenarioId, stepIds))
))
.collect(Collectors.toList());
private final List<Pair<String, String>> nestedSteps = Stream.concat(
Stream.of(Pair.of(innerFeatureId, innerScenarioId)),
innerStepIds.stream().map(id -> Pair.of(innerScenarioId, id))
).collect(Collectors.toList());

private final ReportPortalClient client = mock(ReportPortalClient.class);
private final ReportPortal rp = ReportPortal.create(client, standardParameters(), testExecutor());

@BeforeEach
public void setupMock() {
mockLaunch(client, null);
mockFeatures(client, features);
mockNestedSteps(client, nestedSteps);
mockBatchLogging(client);
}

Expand All @@ -80,17 +84,19 @@ public void test_call_feature_with_parameters_hook_reporting() {
assertThat(results.getFailCount(), equalTo(0));

ArgumentCaptor<StartTestItemRQ> featureCaptor = ArgumentCaptor.forClass(StartTestItemRQ.class);
verify(client, times(2)).startTestItem(featureCaptor.capture());
verify(client, times(1)).startTestItem(featureCaptor.capture());
ArgumentCaptor<StartTestItemRQ> scenarioCaptor = ArgumentCaptor.forClass(StartTestItemRQ.class);
verify(client).startTestItem(same(featureIds.get(0)), scenarioCaptor.capture());
verify(client).startTestItem(same(featureIds.get(1)), scenarioCaptor.capture());
verify(client).startTestItem(same(featureId), scenarioCaptor.capture());
ArgumentCaptor<StartTestItemRQ> stepCaptor = ArgumentCaptor.forClass(StartTestItemRQ.class);
verify(client).startTestItem(same(scenarioIds.get(0)), stepCaptor.capture());
verify(client, times(3)).startTestItem(same(scenarioIds.get(1)), stepCaptor.capture());
verify(client, times(2)).startTestItem(same(scenarioId), stepCaptor.capture());
ArgumentCaptor<StartTestItemRQ> innerScenarioCaptor = ArgumentCaptor.forClass(StartTestItemRQ.class);
verify(client).startTestItem(same(innerFeatureId), innerScenarioCaptor.capture());
ArgumentCaptor<StartTestItemRQ> innerStepCaptor = ArgumentCaptor.forClass(StartTestItemRQ.class);
verify(client, times(3)).startTestItem(same(innerScenarioId), innerStepCaptor.capture());

StartTestItemRQ calledFeature = featureCaptor.getAllValues()
StartTestItemRQ calledFeature = stepCaptor.getAllValues()
.stream()
.filter(rq -> "a feature which is called with parameters".equals(rq.getName()))
.filter(rq -> "Feature: a feature which is called with parameters".equals(rq.getName()))
.findAny()
.orElseThrow();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@

public class ScenarioDescriptionErrorLogWithDescriptionAndExamplesTest {

public static final String MARKDOWN_DELIMITER_PATTERN_THREE_ARGS = "%s\n\n---\n\n%s\n\n---\n\n%s";
public static final String MARKDOWN_DELIMITER_PATTERN_THREE_ARGS = "%s\n---\n%s\n---\n%s";
public static final String ERROR = "did not evaluate to 'true': mathResult == 5\nclasspath:feature/simple_failed_description_examples.feature:8";
public static final String ERROR_MESSAGE = "Then assert mathResult == 5\n" + ERROR;
public static final String DESCRIPTION_ERROR_LOG = "Error:\n" + ERROR;
Expand Down
Loading

0 comments on commit e2fdfa6

Please sign in to comment.