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 #36

Merged
merged 14 commits into from
Sep 13, 2024
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
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
java-version: '11'

- name: Setup git credentials
uses: oleksiyrudenko/gha-git-credentials@v2.1.1
uses: oleksiyrudenko/gha-git-credentials@v2-latest
with:
name: 'reportportal.io'
email: '[email protected]'
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 @@ -28,6 +28,13 @@
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
* This class ensures that {@link Map#computeIfAbsent(Object, Function)} is called only once for the same key. It has inner blocking timeout
* of 1 minute to wait for the value to be computed.
*
* @param <K> a key type for the map
* @param <V> a value type to store
*/
public class BlockingConcurrentHashMap<K, V> {
private static final Logger LOGGER = LoggerFactory.getLogger(BlockingConcurrentHashMap.class);

Expand Down Expand Up @@ -69,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
Loading