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

Call logic fix #35

Merged
merged 11 commits into from
Sep 13, 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
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