diff --git a/CHANGELOG.md b/CHANGELOG.md index 847f0ae..e5a5dd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## [Unreleased] +### Changed +- Many static methods from Util class were moved to AbstractReporter class and made protected to ease extension ## [5.0.0-RC-1] ### Added diff --git a/build.gradle b/build.gradle index be55262..c311c6b 100644 --- a/build.gradle +++ b/build.gradle @@ -51,9 +51,10 @@ dependencies { api 'com.epam.reportportal:client-java:5.0.11' api 'com.epam.reportportal:commons-model:5.0.0' api 'com.google.code.findbugs:jsr305:3.0.2' - api 'info.cukes:gherkin:2.12.2' + implementation 'org.apache.commons:commons-text:1.9' + testImplementation 'org.aspectj:aspectjweaver:1.9.2' testImplementation 'info.cukes:cucumber-java:1.2.6' testImplementation 'info.cukes:cucumber-testng:1.2.6' diff --git a/gradle.properties b/gradle.properties index c129bf4..c0b9a24 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ -version=5.0.0-RC-2-SNAPSHOT +version=5.0.0-SNAPSHOT description=EPAM Report portal. Cucumber version 1 integration diff --git a/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java b/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java index 45a66a7..4ed3a79 100644 --- a/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java +++ b/src/main/java/com/epam/reportportal/cucumber/AbstractReporter.java @@ -15,6 +15,8 @@ */ package com.epam.reportportal.cucumber; +import com.epam.reportportal.annotations.TestCaseId; +import com.epam.reportportal.annotations.attribute.Attributes; import com.epam.reportportal.listeners.ItemStatus; import com.epam.reportportal.listeners.ListenerParameters; import com.epam.reportportal.message.ReportPortalMessage; @@ -22,34 +24,49 @@ import com.epam.reportportal.service.ReportPortal; import com.epam.reportportal.service.item.TestCaseIdEntry; import com.epam.reportportal.service.tree.TestItemTree; +import com.epam.reportportal.utils.AttributeParser; +import com.epam.reportportal.utils.ParameterUtils; +import com.epam.reportportal.utils.TestCaseIdUtils; import com.epam.reportportal.utils.properties.SystemAttributesExtractor; import com.epam.ta.reportportal.ws.model.FinishExecutionRQ; +import com.epam.ta.reportportal.ws.model.FinishTestItemRQ; +import com.epam.ta.reportportal.ws.model.ParameterResource; import com.epam.ta.reportportal.ws.model.StartTestItemRQ; import com.epam.ta.reportportal.ws.model.attribute.ItemAttributesRQ; import com.epam.ta.reportportal.ws.model.launch.StartLaunchRQ; +import gherkin.formatter.Argument; import gherkin.formatter.Formatter; import gherkin.formatter.Reporter; import gherkin.formatter.model.*; import io.reactivex.Maybe; +import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.commons.text.StringEscapeUtils; import org.apache.tika.Tika; import org.apache.tika.mime.MediaType; import org.apache.tika.mime.MimeTypeException; import org.apache.tika.mime.MimeTypes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import rp.com.google.common.base.Strings; import rp.com.google.common.base.Supplier; import rp.com.google.common.base.Suppliers; +import rp.com.google.common.collect.ImmutableMap; import rp.com.google.common.io.ByteSource; import javax.annotation.Nonnull; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; import java.util.stream.IntStream; -import static com.epam.reportportal.cucumber.Utils.extractAttributes; -import static com.epam.reportportal.cucumber.Utils.getCodeRef; +import static com.epam.reportportal.cucumber.Utils.ARGUMENTS_TRANSFORM; +import static com.epam.reportportal.cucumber.Utils.retrieveMethod; import static com.epam.reportportal.cucumber.util.ItemTreeUtils.createKey; import static com.epam.reportportal.cucumber.util.ItemTreeUtils.retrieveLeaf; import static java.util.Optional.ofNullable; @@ -68,6 +85,26 @@ public abstract class AbstractReporter implements Formatter, Reporter { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractReporter.class); private static final String AGENT_PROPERTIES_FILE = "agent.properties"; private static final int DEFAULT_CAPACITY = 16; + private static final String STEP_DEFINITION_FIELD_NAME = "stepDefinition"; + private static final String GET_LOCATION_METHOD_NAME = "getLocation"; + private static final String METHOD_OPENING_BRACKET = "("; + private static final String TABLE_SEPARATOR = "|"; + private static final String DOCSTRING_DECORATOR = "\n\"\"\"\n"; + + //@formatter:off + private static final Map STATUS_MAPPING = ImmutableMap.builder() + .put("passed", ItemStatus.PASSED) + .put("failed", ItemStatus.FAILED) + .put("skipped", ItemStatus.SKIPPED) + .put("pending", ItemStatus.SKIPPED) + .put("undefined", ItemStatus.SKIPPED).build(); + private static final Map LOG_LEVEL_MAPPING = ImmutableMap.builder() + .put("passed", "INFO") + .put("failed", "ERROR") + .put("skipped", "WARN") + .put("pending", "WARN") + .put("undefined", "WARN").build(); + //@formatter:on public static final TestItemTree ITEM_TREE = new TestItemTree(); private static volatile ReportPortal REPORT_PORTAL = ReportPortal.builder().build(); @@ -151,6 +188,40 @@ private void addToTree(RunningContext.FeatureContext context) { ITEM_TREE.getTestItems().put(createKey(context.getUri()), TestItemTree.createTestItemLeaf(context.getId(), DEFAULT_CAPACITY)); } + /** + * Returns a scenario name + * + * @param scenario Cucumber's Scenario object + * @return scenario name + */ + protected String buildScenarioName(Scenario scenario) { + return Utils.buildName(scenario.getKeyword(), COLON_INFIX, scenario.getName()); + } + + /** + * Extension point to customize scenario creation event/request + * + * @param scenario Cucumber's Scenario object + * @param uri a scenario relative path + * @return start test item request ready to send on RP + */ + protected StartTestItemRQ buildStartScenarioRequest(Scenario scenario, String uri) { + StartTestItemRQ rq = new StartTestItemRQ(); + rq.setName(buildScenarioName(scenario)); + String description = getDescription(scenario, uri); + String codeRef = getCodeRef(uri, scenario.getLine()); + rq.setDescription(description); + rq.setCodeRef(codeRef); + rq.setAttributes(extractAttributes(scenario.getTags())); + rq.setStartTime(Calendar.getInstance().getTime()); + String type = getScenarioTestItemType(); + rq.setType(type); + if ("STEP".equals(type)) { + rq.setTestCaseId(ofNullable(getTestCaseId(codeRef, null)).map(TestCaseIdEntry::getId).orElse(null)); + } + return rq; + } + /** * Start Cucumber Feature (if not started) and Scenario * @@ -162,28 +233,19 @@ protected void beforeScenario(Scenario scenario, String outlineIteration) { // By this reason, it cannot be started in #beforeFeature method, // because it will be executed even if all Scenarios in the Feature are excluded. RunningContext.FeatureContext featureContext = currentFeatureContext.get(); + Launch myLaunch = launch.get(); if (null == featureContext.getId()) { StartTestItemRQ startFeatureRq = featureContext.getItemRq(); Optional> root = getRootItemId(); startFeatureRq.setStartTime(Calendar.getInstance().getTime()); - Maybe currentFeatureId = root.map(i -> launch.get().startTestItem(i, startFeatureRq)) - .orElseGet(() -> launch.get().startTestItem(startFeatureRq)); + Maybe currentFeatureId = root.map(i -> myLaunch.startTestItem(i, startFeatureRq)) + .orElseGet(() -> myLaunch.startTestItem(startFeatureRq)); featureContext.setId(currentFeatureId); addToTree(featureContext); } String uri = featureContext.getUri(); - String description = Utils.getDescription(uri); - String codeRef = Utils.getCodeRef(uri, scenario.getLine()); - Launch myLaunch = launch.get(); - Maybe id = Utils.startNonLeafNode( - myLaunch, - featureContext.getId(), - Utils.buildName(scenario.getKeyword(), COLON_INFIX, scenario.getName()), - description, - codeRef, - scenario.getTags(), - getScenarioTestItemType() - ); + StartTestItemRQ rq = buildStartScenarioRequest(scenario, uri); + Maybe id = myLaunch.startTestItem(featureContext.getId(), rq); RunningContext.ScenarioContext scenarioContext = getCurrentScenarioContext(); scenarioContext.setId(id); scenarioContext.setLine(scenario.getLine()); @@ -203,41 +265,52 @@ private void removeFromTree(RunningContext.FeatureContext featureContext, Runnin */ protected void afterScenario() { RunningContext.ScenarioContext context = getCurrentScenarioContext(); - Utils.finishTestItem(launch.get(), context.getId(), context.getStatus()); + finishTestItem(context.getId(), context.getStatus()); currentScenarioContext.set(null); removeFromTree(currentFeatureContext.get(), context); } /** - * Define Start Cucumber feature RQ + * Extension point to customize feature creation event/request * - * @param feature Step feature + * @param feature a Cucumber's Feature object + * @param uri a path to the feature + * @return Request to ReportPortal */ - protected void beforeFeature(Feature feature) { - //define start feature RQ in this method, because only here we can receive Feature details - RunningContext.FeatureContext featureContext = currentFeatureContext.get(); + protected StartTestItemRQ buildStartFeatureRequest(Feature feature, String uri) { String featureKeyword = feature.getKeyword(); String featureName = feature.getName(); StartTestItemRQ startFeatureRq = new StartTestItemRQ(); - startFeatureRq.setDescription(Utils.getDescription(featureContext.getUri())); - startFeatureRq.setCodeRef(getCodeRef(featureContext.getUri(), 0)); + startFeatureRq.setDescription(getDescription(feature, uri)); + startFeatureRq.setCodeRef(getCodeRef(uri, 0)); startFeatureRq.setName(Utils.buildName(featureKeyword, AbstractReporter.COLON_INFIX, featureName)); startFeatureRq.setAttributes(extractAttributes(feature.getTags())); startFeatureRq.setType(getFeatureTestItemType()); - featureContext.setItemRq(startFeatureRq); + return startFeatureRq; } /** - * Finish Cucumber feature + * Define Start Cucumber feature RQ + * + * @param feature Step feature + */ + protected void beforeFeature(Feature feature) { + //define start feature RQ in this method, because only here we can receive Feature details + RunningContext.FeatureContext featureContext = currentFeatureContext.get(); + featureContext.setItemRq(buildStartFeatureRequest(feature, featureContext.getUri())); + } + + /** + * Finish current Cucumber feature */ protected void afterFeature() { RunningContext.FeatureContext currentFeature = currentFeatureContext.get(); if (null != currentFeature && null != currentFeature.getId()) { - Utils.finishTestItem(launch.get(), currentFeature.getId()); + finishTestItem(currentFeature.getId()); } } - protected RunningContext.ScenarioContext getCurrentScenarioContext() { + private RunningContext.ScenarioContext getCurrentScenarioContext() { RunningContext.ScenarioContext context = currentScenarioContext.get(); if (context == null) { context = new RunningContext.ScenarioContext(); @@ -246,20 +319,35 @@ protected RunningContext.ScenarioContext getCurrentScenarioContext() { return context; } + /** + * Extension point to customize step creation event/request + * + * @param step a Cucumber's Step object + * @param stepPrefix a prefix of the step (e.g. 'Background') + * @param match a Cucumber's Match object + * @return Request to ReportPortal + */ protected StartTestItemRQ buildStartStepRequest(Step step, String stepPrefix, Match match) { StartTestItemRQ rq = new StartTestItemRQ(); rq.setName(Utils.buildName(stepPrefix, step.getKeyword(), step.getName())); - rq.setDescription(Utils.buildMultilineArgument(step)); + rq.setDescription(buildMultilineArgument(step)); rq.setStartTime(Calendar.getInstance().getTime()); rq.setType("STEP"); - String codeRef = Utils.getCodeRef(match); - rq.setParameters(Utils.getParameters(codeRef, match)); + String codeRef = getCodeRef(match); + rq.setParameters(getParameters(step, codeRef, match)); rq.setCodeRef(codeRef); - rq.setTestCaseId(ofNullable(Utils.getTestCaseId(match, codeRef)).map(TestCaseIdEntry::getId).orElse(null)); - rq.setAttributes(Utils.getAttributes(match)); + rq.setTestCaseId(ofNullable(getTestCaseId(match, codeRef)).map(TestCaseIdEntry::getId).orElse(null)); + rq.setAttributes(getAttributes(match)); return rq; } + private void addToTree(RunningContext.ScenarioContext scenarioContext, String text, Maybe stepId) { + retrieveLeaf(scenarioContext.getFeatureUri(), + scenarioContext.getLine(), + ITEM_TREE + ).ifPresent(scenarioLeaf -> scenarioLeaf.getChildItems().put(createKey(text), TestItemTree.createTestItemLeaf(stepId, 0))); + } + /** * Start Cucumber step * @@ -273,7 +361,6 @@ protected void beforeStep(Step step, Match match) { Maybe stepId = myLaunch.startTestItem(context.getId(), rq); context.setCurrentStepId(stepId); String stepText = step.getName(); - context.setCurrentText(stepText); if (myLaunch.getParameters().isCallbackReportingEnabled()) { addToTree(context, stepText, stepId); @@ -288,11 +375,9 @@ protected void beforeStep(Step step, Match match) { protected void afterStep(Result result) { reportResult(result, null); RunningContext.ScenarioContext context = getCurrentScenarioContext(); - Launch myLaunch = launch.get(); - myLaunch.getStepReporter().finishPreviousStep(); - Utils.finishTestItem(myLaunch, context.getCurrentStepId(), Utils.mapStatus(result.getStatus())); + launch.get().getStepReporter().finishPreviousStep(); + finishTestItem(context.getCurrentStepId(), mapStatus(result.getStatus())); context.setCurrentStepId(null); - context.setCurrentText(null); } /** @@ -329,9 +414,8 @@ protected void beforeHooks(boolean isBefore) { */ protected void afterHooks(Boolean isBefore) { RunningContext.ScenarioContext context = getCurrentScenarioContext(); - Launch myLaunch = launch.get(); - myLaunch.getStepReporter().finishPreviousStep(); - Utils.finishTestItem(myLaunch, context.getHookStepId(), context.getHookStatus()); + launch.get().getStepReporter().finishPreviousStep(); + finishTestItem(context.getHookStepId(), context.getHookStatus()); context.setHookStepId(null); } @@ -344,7 +428,7 @@ protected void afterHooks(Boolean isBefore) { */ protected void hookFinished(Match match, Result result, Boolean isBefore) { reportResult(result, (isBefore ? "Before" : "After") + " hook: " + match.getLocation()); - getCurrentScenarioContext().setHookStatus(Utils.mapStatus(result.getStatus())); + getCurrentScenarioContext().setHookStatus(mapStatus(result.getStatus())); } /** @@ -355,19 +439,19 @@ protected void hookFinished(Match match, Result result, Boolean isBefore) { */ protected void reportResult(Result result, String message) { String cukesStatus = result.getStatus(); - String level = Utils.mapLevel(cukesStatus); + String level = mapLevel(cukesStatus); if (message != null) { - Utils.sendLog(message, level); + sendLog(message, level); } String errorMessage = result.getErrorMessage(); if (errorMessage != null) { - Utils.sendLog(errorMessage, level); + sendLog(errorMessage, level); } if (result.getError() != null) { - Utils.sendLog(getStackTraceAsString(result.getError()), level); + sendLog(getStackTraceAsString(result.getError()), level); } RunningContext.ScenarioContext currentScenario = getCurrentScenarioContext(); - currentScenario.updateStatus(Utils.mapStatus(result.getStatus())); + currentScenario.updateStatus(mapStatus(result.getStatus())); } /** @@ -451,12 +535,11 @@ public void embedding(String mimeType, byte[] data) { @Override public void write(String text) { - Utils.sendLog(text, "INFO"); + sendLog(text, "INFO"); } @Override public void syntaxError(String state, String event, List legalEvents, String uri, Integer line) { - //TODO find out when this is called } @Override @@ -544,21 +627,274 @@ public void eof() { afterFeature(); } + /** + * Returns the ID of the root item of the launch + * + * @return the ID of the root item of the launch + */ protected abstract Optional> getRootItemId(); - protected void addToTree(RunningContext.ScenarioContext scenarioContext, String text, Maybe stepId) { - retrieveLeaf( - scenarioContext.getFeatureUri(), - scenarioContext.getLine(), - ITEM_TREE - ).ifPresent(scenarioLeaf -> scenarioLeaf.getChildItems().put(createKey(text), TestItemTree.createTestItemLeaf(stepId, 0))); + /** + * Return a Test Case ID for a feature file + * + * @param codeRef a code reference + * @param arguments a scenario arguments + * @return Test Case ID entity or null if it's not possible to calculate + */ + @SuppressWarnings("unchecked") + @Nullable + private TestCaseIdEntry getTestCaseId(@Nullable String codeRef, @Nullable List arguments) { + return TestCaseIdUtils.getTestCaseId(codeRef, (List) ARGUMENTS_TRANSFORM.apply(arguments)); } - protected void removeFromTree(RunningContext.ScenarioContext scenarioContext, String text) { - retrieveLeaf( - scenarioContext.getFeatureUri(), - scenarioContext.getLine(), - ITEM_TREE - ).ifPresent(scenarioLeaf -> scenarioLeaf.getChildItems().remove(createKey(text))); + /** + * Return a Test Case ID for mapped code + * + * @param match Cucumber's Match object + * @param codeRef a code reference + * @return Test Case ID entity or null if it's not possible to calculate + */ + @SuppressWarnings("unchecked") + @Nullable + protected TestCaseIdEntry getTestCaseId(@Nonnull Match match, @Nullable String codeRef) { + try { + Method method = retrieveMethod(match); + if (method == null) { + return getTestCaseId(codeRef, match.getArguments()); + } + return TestCaseIdUtils.getTestCaseId(method.getAnnotation(TestCaseId.class), + method, + codeRef, + (List) ARGUMENTS_TRANSFORM.apply(match.getArguments()) + ); + } catch (NoSuchFieldException | IllegalAccessException e) { + return getTestCaseId(codeRef, match.getArguments()); + } + } + + /** + * Build an item description for a scenario + * + * @param scenario a Cucumber's Scenario object + * @param uri a feature URI + * @return item description + */ + @Nonnull + protected String getDescription(@Nonnull Scenario scenario, @Nonnull String uri) { + return uri; + } + + /** + * Build an item description for a feature + * + * @param feature a Cucumber's Feature object + * @param uri a feature URI + * @return item description + */ + @Nonnull + protected String getDescription(@Nonnull Feature feature, @Nonnull String uri) { + return uri; + } + + /** + * Finish a test item with no specific status + * + * @param itemId an ID of the item + */ + protected void finishTestItem(Maybe itemId) { + finishTestItem(itemId, null); + } + + /** + * Finish a test item with specified status + * + * @param itemId an ID of the item + * @param status the status of the item + */ + protected void finishTestItem(Maybe itemId, ItemStatus status) { + if (itemId == null) { + LOGGER.error("BUG: Trying to finish unspecified test item."); + return; + } + + FinishTestItemRQ rq = new FinishTestItemRQ(); + ofNullable(status).ifPresent(s -> rq.setStatus(s.name())); + rq.setEndTime(Calendar.getInstance().getTime()); + + launch.get().finishTestItem(itemId, rq); + + } + + /** + * Returns code reference for mapped code + * + * @param match Cucumber's Match object + * @return a code reference, or null if not possible to determine (ambiguous, undefined, etc.) + */ + @Nullable + protected String getCodeRef(Match match) { + try { + Field stepDefinitionField = match.getClass().getDeclaredField(STEP_DEFINITION_FIELD_NAME); + stepDefinitionField.setAccessible(true); + Object javaStepDefinition = stepDefinitionField.get(match); + Method getLocationMethod = javaStepDefinition.getClass().getDeclaredMethod(GET_LOCATION_METHOD_NAME, boolean.class); + getLocationMethod.setAccessible(true); + String fullCodeRef = String.valueOf(getLocationMethod.invoke(javaStepDefinition, true)); + return fullCodeRef != null ? fullCodeRef.substring(0, fullCodeRef.indexOf(METHOD_OPENING_BRACKET)) : null; + } catch (NoSuchFieldException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + return null; + } + + } + + /** + * Returns code reference for feature files by URI and text line number + * + * @param uri a feature URI + * @param line a scenario line number + * @return a code reference + */ + @Nonnull + protected String getCodeRef(@Nonnull String uri, int line) { + return uri + ":" + line; + } + + /** + * Map Cucumber statuses to RP statuses + * + * @param cukesStatus - Cucumber status + * @return RP status + */ + @Nonnull + protected ItemStatus mapStatus(@Nullable String cukesStatus) { + if (Strings.isNullOrEmpty(cukesStatus)) { + return ItemStatus.FAILED; + } + ItemStatus status = STATUS_MAPPING.get(cukesStatus.toLowerCase()); + return null == status ? ItemStatus.FAILED : status; + } + + /** + * Map Cucumber statuses to RP log levels + * + * @param cukesStatus - Cucumber status + * @return regular log level + */ + @Nonnull + protected String mapLevel(@Nullable String cukesStatus) { + if (Strings.isNullOrEmpty(cukesStatus)) { + return "ERROR"; + } + String level = LOG_LEVEL_MAPPING.get(cukesStatus.toLowerCase()); + return null == level ? "ERROR" : level; + } + + /** + * Send a text log entry to Report Portal using current datetime as timestamp + * + * @param message a text message + * @param level a log level, see standard Log4j / logback logging levels + */ + protected void sendLog(final String message, final String level) { + ReportPortal.emitLog(message, level, Calendar.getInstance().getTime()); + } + + /** + * Transform tags from Cucumber to RP format + * + * @param tags - Cucumber tags + * @return set of tags + */ + @Nonnull + protected Set extractAttributes(@Nonnull List tags) { + Set result = new HashSet<>(); + for (Tag tag : tags) { + result.add(new ItemAttributesRQ(null, tag.getName())); + } + return result; + } + + /** + * Returns a list of parameters for a step + * + * @param step Cucumber's Step object + * @param codeRef a method code reference to retrieve parameter types + * @param match Cucumber's Match object + * @return a list of parameters or empty list if none + */ + @Nonnull + protected List getParameters(@Nonnull Step step, @Nullable String codeRef, @Nonnull Match match) { + List> params = ofNullable(match.getArguments()).map(a -> IntStream.range(0, a.size()) + .mapToObj(i -> Pair.of("arg" + i, a.get(i).getVal())) + .collect(Collectors.toList())).orElse(null); + if (params == null || params.isEmpty()) { + DocString docstring = step.getDocString(); + if (docstring != null) { + return ParameterUtils.getParameters(codeRef, + Collections.singletonList(Pair.of("docstring", StringEscapeUtils.escapeHtml4(docstring.getValue()))) + ); + } else { + return Collections.emptyList(); + } + } else { + return ParameterUtils.getParameters(codeRef, params); + } + } + + /** + * Returns static attributes defined by {@link Attributes} annotation in code. + * + * @param match Cucumber's Match object + * @return a set of attributes or null if no such method provided by the match object + */ + @Nullable + protected Set getAttributes(Match match) { + try { + Method method = retrieveMethod(match); + if (method == null) { + return null; + } + Attributes attributesAnnotation = method.getAnnotation(Attributes.class); + if (attributesAnnotation != null) { + return AttributeParser.retrieveAttributes(attributesAnnotation); + } + } catch (NoSuchFieldException | IllegalAccessException e) { + return null; + } + return null; + } + + /** + * Generate multiline argument (DataTable or DocString) representation + * + * @param step - Cucumber step object + * @return - transformed multiline argument (or empty string if there is + * none) + */ + protected String buildMultilineArgument(Step step) { + List table = step.getRows(); + DocString ds = step.getDocString(); + StringBuilder marg = new StringBuilder(); + StringBuilder markDownSeparator = new StringBuilder(); + if (table != null) { + marg.append("\r\n\r\n"); + for (Row row : table) { + marg.append(TABLE_SEPARATOR); + for (String cell : row.getCells()) { + marg.append(" ").append(cell).append(" ").append(TABLE_SEPARATOR); + } + marg.append("\r\n"); + if (markDownSeparator.length() == 0) { + markDownSeparator.append(TABLE_SEPARATOR).append("-").append(TABLE_SEPARATOR); + marg.append(markDownSeparator); + marg.append("\r\n"); + } + } + } + + if (ds != null) { + marg.append(DOCSTRING_DECORATOR).append(ds.getValue()).append(DOCSTRING_DECORATOR); + } + return marg.toString(); } } diff --git a/src/main/java/com/epam/reportportal/cucumber/RunningContext.java b/src/main/java/com/epam/reportportal/cucumber/RunningContext.java index f62b9bd..d72b527 100644 --- a/src/main/java/com/epam/reportportal/cucumber/RunningContext.java +++ b/src/main/java/com/epam/reportportal/cucumber/RunningContext.java @@ -65,7 +65,6 @@ public static class ScenarioContext { private Maybe id; private ItemStatus status; private Integer line; - private String currentText; private String featureUri; public ScenarioContext() { @@ -160,10 +159,6 @@ public void setLine(Integer scenarioLine) { line = scenarioLine; } - public void setCurrentText(String stepText) { - currentText = stepText; - } - public void setFeatureUri(String featureUri) { this.featureUri = featureUri; } diff --git a/src/main/java/com/epam/reportportal/cucumber/ScenarioReporter.java b/src/main/java/com/epam/reportportal/cucumber/ScenarioReporter.java index 730050d..453a963 100644 --- a/src/main/java/com/epam/reportportal/cucumber/ScenarioReporter.java +++ b/src/main/java/com/epam/reportportal/cucumber/ScenarioReporter.java @@ -100,7 +100,7 @@ protected void afterLaunch() { LOGGER.debug("There is no scenarios in the launch"); return; } - Utils.finishTestItem(launch.get(), rootSuiteId.get()); + finishTestItem(rootSuiteId.get()); rootSuiteId = null; super.afterLaunch(); } diff --git a/src/main/java/com/epam/reportportal/cucumber/Utils.java b/src/main/java/com/epam/reportportal/cucumber/Utils.java index 57ad441..b8d7444 100644 --- a/src/main/java/com/epam/reportportal/cucumber/Utils.java +++ b/src/main/java/com/epam/reportportal/cucumber/Utils.java @@ -15,214 +15,26 @@ */ package com.epam.reportportal.cucumber; -import com.epam.reportportal.annotations.TestCaseId; -import com.epam.reportportal.annotations.attribute.Attributes; -import com.epam.reportportal.listeners.ItemStatus; -import com.epam.reportportal.service.Launch; -import com.epam.reportportal.service.ReportPortal; -import com.epam.reportportal.service.item.TestCaseIdEntry; -import com.epam.reportportal.utils.AttributeParser; -import com.epam.reportportal.utils.ParameterUtils; -import com.epam.reportportal.utils.TestCaseIdUtils; -import com.epam.ta.reportportal.ws.model.FinishTestItemRQ; -import com.epam.ta.reportportal.ws.model.ParameterResource; -import com.epam.ta.reportportal.ws.model.StartTestItemRQ; -import com.epam.ta.reportportal.ws.model.attribute.ItemAttributesRQ; import gherkin.formatter.Argument; -import gherkin.formatter.model.*; -import io.reactivex.Maybe; -import io.reactivex.annotations.Nullable; -import org.apache.commons.lang3.tuple.Pair; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import rp.com.google.common.base.Strings; -import rp.com.google.common.collect.ImmutableMap; +import gherkin.formatter.model.Match; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.*; +import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.IntStream; import static java.util.Optional.ofNullable; public class Utils { - private static final Logger LOGGER = LoggerFactory.getLogger(Utils.class); - private static final String TABLE_SEPARATOR = "|"; - private static final String DOCSTRING_DECORATOR = "\n\"\"\"\n"; private static final String STEP_DEFINITION_FIELD_NAME = "stepDefinition"; - private static final String GET_LOCATION_METHOD_NAME = "getLocation"; - private static final String METHOD_OPENING_BRACKET = "("; private static final String METHOD_FIELD_NAME = "method"; - //@formatter:off - private static final Map STATUS_MAPPING = ImmutableMap.builder() - .put("passed", ItemStatus.PASSED) - .put("failed", ItemStatus.FAILED) - .put("skipped", ItemStatus.SKIPPED) - .put("pending", ItemStatus.SKIPPED) - .put("undefined", ItemStatus.SKIPPED).build(); - //@formatter:on - private Utils() { } - public static void finishTestItem(Launch rp, Maybe itemId) { - finishTestItem(rp, itemId, null); - } - - public static void finishTestItem(Launch rp, Maybe itemId, ItemStatus status) { - if (itemId == null) { - LOGGER.error("BUG: Trying to finish unspecified test item."); - return; - } - - FinishTestItemRQ rq = new FinishTestItemRQ(); - ofNullable(status).ifPresent(s -> rq.setStatus(s.name())); - rq.setEndTime(Calendar.getInstance().getTime()); - - rp.finishTestItem(itemId, rq); - - } - - public static Maybe startNonLeafNode(Launch rp, Maybe rootItemId, String name, String description, String codeRef, - List tags, String type) { - StartTestItemRQ rq = new StartTestItemRQ(); - rq.setDescription(description); - rq.setCodeRef(codeRef); - rq.setName(name); - rq.setAttributes(extractAttributes(tags)); - rq.setStartTime(Calendar.getInstance().getTime()); - rq.setType(type); - if ("STEP".equals(type)) { - rq.setTestCaseId(ofNullable(Utils.getTestCaseId(codeRef, null)).map(TestCaseIdEntry::getId).orElse(null)); - } - - return rp.startTestItem(rootItemId, rq); - } - - static void sendLog(final String message, final String level) { - ReportPortal.emitLog(message, level, Calendar.getInstance().getTime()); - } - - /** - * Transform tags from Cucumber to RP format - * - * @param tags - Cucumber tags - * @return set of tags - */ - public static Set extractAttributes(List tags) { - Set result = new HashSet<>(); - for (Tag tag : tags) { - result.add(new ItemAttributesRQ(null, tag.getName())); - } - return result; - } - - /** - * Map Cucumber statuses to RP log levels - * - * @param cukesStatus - Cucumber status - * @return regular log level - */ - public static String mapLevel(String cukesStatus) { - if (cukesStatus.equalsIgnoreCase("passed")) { - return "INFO"; - } else if (cukesStatus.equalsIgnoreCase("skipped")) { - return "WARN"; - } else { - return "ERROR"; - } - } - - /** - * Map Cucumber statuses to RP - * - * @param cukesStatus - Cucumber status - * @return regular status - */ - @Nonnull - public static ItemStatus mapStatus(String cukesStatus) { - if (Strings.isNullOrEmpty(cukesStatus)) { - return ItemStatus.FAILED; - } - ItemStatus status = STATUS_MAPPING.get(cukesStatus.toLowerCase()); - return null == status ? ItemStatus.FAILED : status; - } - - /** - * Generate multiline argument (DataTable or DocString) representation - * - * @param step - Cucumber step object - * @return - transformed multiline argument (or empty string if there is - * none) - */ - public static String buildMultilineArgument(Step step) { - List table = step.getRows(); - DocString ds = step.getDocString(); - StringBuilder marg = new StringBuilder(); - StringBuilder markDownSeparator = new StringBuilder(); - if (table != null) { - marg.append("\r\n\r\n"); - for (Row row : table) { - marg.append(TABLE_SEPARATOR); - for (String cell : row.getCells()) { - marg.append(" ").append(cell).append(" ").append(TABLE_SEPARATOR); - } - marg.append("\r\n"); - if (markDownSeparator.length() == 0) { - markDownSeparator.append(TABLE_SEPARATOR).append("-").append(TABLE_SEPARATOR); - marg.append(markDownSeparator); - marg.append("\r\n"); - } - } - } - - if (ds != null) { - marg.append(DOCSTRING_DECORATOR).append(ds.getValue()).append(DOCSTRING_DECORATOR); - } - return marg.toString(); - } - - @Nullable - public static Set getAttributes(Match match) { - try { - Method method = retrieveMethod(match); - Attributes attributesAnnotation = method.getAnnotation(Attributes.class); - if (attributesAnnotation != null) { - return AttributeParser.retrieveAttributes(attributesAnnotation); - } - } catch (NoSuchFieldException | IllegalAccessException e) { - return null; - } - return null; - } - - @Nullable - public static String getCodeRef(Match match) { - - try { - Field stepDefinitionField = match.getClass().getDeclaredField(STEP_DEFINITION_FIELD_NAME); - stepDefinitionField.setAccessible(true); - Object javaStepDefinition = stepDefinitionField.get(match); - Method getLocationMethod = javaStepDefinition.getClass().getDeclaredMethod(GET_LOCATION_METHOD_NAME, boolean.class); - getLocationMethod.setAccessible(true); - String fullCodeRef = String.valueOf(getLocationMethod.invoke(javaStepDefinition, true)); - return fullCodeRef != null ? fullCodeRef.substring(0, fullCodeRef.indexOf(METHOD_OPENING_BRACKET)) : null; - } catch (NoSuchFieldException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - return null; - } - - } - - @Nonnull - public static String getCodeRef(@Nonnull String uri, int line) { - return uri + ":" + line; - } - /** * Generate name representation * @@ -231,18 +43,13 @@ public static String getCodeRef(@Nonnull String uri, int line) { * @param argument - main text to process * @return transformed string */ - public static String buildName(String prefix, String infix, String argument) { + @Nonnull + public static String buildName(@Nullable String prefix, @Nullable String infix, @Nullable String argument) { return (prefix == null ? "" : prefix) + infix + argument; } - static List getParameters(String codeRef, Match match) { - List> params = ofNullable(match.getArguments()).map(a -> IntStream.range(0, a.size()) - .mapToObj(i -> Pair.of("arg" + i, a.get(i).getVal())) - .collect(Collectors.toList())).orElse(null); - return ParameterUtils.getParameters(codeRef, params); - } - - private static Method retrieveMethod(Match match) throws NoSuchFieldException, IllegalAccessException { + @Nullable + public static Method retrieveMethod(@Nonnull Match match) throws NoSuchFieldException, IllegalAccessException { Field stepDefinitionField = match.getClass().getDeclaredField(STEP_DEFINITION_FIELD_NAME); stepDefinitionField.setAccessible(true); Object javaStepDefinition = stepDefinitionField.get(match); @@ -251,32 +58,8 @@ private static Method retrieveMethod(Match match) throws NoSuchFieldException, I return (Method) methodField.get(javaStepDefinition); } - private static final Function, List> ARGUMENTS_TRANSFORM = arguments -> ofNullable(arguments).map(args -> args.stream() + public static final Function, List> ARGUMENTS_TRANSFORM = arguments -> ofNullable(arguments).map(args -> args.stream() .map(Argument::getVal) .collect(Collectors.toList())).orElse(null); - @SuppressWarnings("unchecked") - public static TestCaseIdEntry getTestCaseId(Match match, String codeRef) { - try { - Method method = retrieveMethod(match); - return TestCaseIdUtils.getTestCaseId( - method.getAnnotation(TestCaseId.class), - method, - codeRef, - (List) ARGUMENTS_TRANSFORM.apply(match.getArguments()) - ); - } catch (NoSuchFieldException | IllegalAccessException e) { - return getTestCaseId(codeRef, match.getArguments()); - } - } - - @SuppressWarnings("unchecked") - private static TestCaseIdEntry getTestCaseId(String codeRef, List arguments) { - return TestCaseIdUtils.getTestCaseId(codeRef, (List) ARGUMENTS_TRANSFORM.apply(arguments)); - } - - @Nonnull - public static String getDescription(@Nonnull String uri) { - return uri; - } } diff --git a/src/test/java/com/epam/reportportal/cucumber/ParameterStepReporterTest.java b/src/test/java/com/epam/reportportal/cucumber/ParameterStepReporterTest.java index 0ac5623..315ec92 100644 --- a/src/test/java/com/epam/reportportal/cucumber/ParameterStepReporterTest.java +++ b/src/test/java/com/epam/reportportal/cucumber/ParameterStepReporterTest.java @@ -53,6 +53,13 @@ public static class InlineParametersTestStepReporter extends AbstractTestNGCucum } + @CucumberOptions(features = "src/test/resources/features/BasicDocStringParameters.feature", glue = { + "com.epam.reportportal.cucumber.integration.feature" }, plugin = { "pretty", + "com.epam.reportportal.cucumber.integration.TestStepReporter" }) + public static class DocstringParameterTestStepReporter extends AbstractTestNGCucumberTests { + + } + private final String launchId = CommonUtils.namedId("launch_"); private final String suiteId = CommonUtils.namedId("suite_"); private final List testIds = Stream.generate(() -> CommonUtils.namedId("test_")).limit(3).collect(Collectors.toList()); @@ -157,4 +164,19 @@ public void verify_inline_parameters() { assertThat(param3.getKey(), equalTo("my name")); assertThat(param3.getValue(), equalTo("string")); } + + @Test + public void verify_docstring_parameters() { + TestUtils.runTests(DocstringParameterTestStepReporter.class); + + ArgumentCaptor captor = ArgumentCaptor.forClass(StartTestItemRQ.class); + verify(client, times(4)).startTestItem(same(testIds.get(0)), captor.capture()); + + List items = captor.getAllValues(); + List params = items.get(2).getParameters(); + assertThat(params, allOf(notNullValue(), hasSize(1))); + ParameterResource param1 = params.get(0); + assertThat(param1.getKey(), equalTo("java.lang.String")); + assertThat(param1.getValue(), equalTo("My very long parameter\nWith some new lines")); + } }