From f18cede3bef36b7f2bec3a47ad5eb982797b027d Mon Sep 17 00:00:00 2001 From: qameta-ci Date: Wed, 3 Nov 2021 12:53:21 +0000 Subject: [PATCH 01/13] set next development version 2.17 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 20b6ae314..c34fa2e10 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=2.16.1 +version=2.17-SNAPSHOT org.gradle.daemon=true org.gradle.parallel=true From c8fa70484a12b568d04e927457cff1bef7b8da4e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Nov 2021 14:28:02 +0300 Subject: [PATCH 02/13] bump resteasy-client from 4.7.2.Final to 4.7.3.Final (via #676) --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 983261822..36e7982d1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -149,7 +149,7 @@ configure(libs) { dependency("org.assertj:assertj-core:3.21.0") dependency("org.codehaus.groovy:groovy-all:2.5.13") dependency("org.freemarker:freemarker:2.3.31") - dependency("org.jboss.resteasy:resteasy-client:4.7.2.Final") + dependency("org.jboss.resteasy:resteasy-client:4.7.3.Final") dependency("org.mock-server:mockserver-netty:5.11.2") dependency("org.mockito:mockito-core:4.0.0") dependencySet("org.slf4j:1.7.30") { From 2c21ebe91c29236b7214f52ea0bb760c4b179ff8 Mon Sep 17 00:00:00 2001 From: Sergey Date: Mon, 8 Nov 2021 15:02:31 +0200 Subject: [PATCH 03/13] fix response name caching for rest assured (fixes #678, via #679) --- .../allure/restassured/AllureRestAssured.java | 10 ++-- .../restassured/AllureRestAssuredTest.java | 52 +++++++++++++++++-- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/allure-rest-assured/src/main/java/io/qameta/allure/restassured/AllureRestAssured.java b/allure-rest-assured/src/main/java/io/qameta/allure/restassured/AllureRestAssured.java index c08d960ba..a916012fe 100644 --- a/allure-rest-assured/src/main/java/io/qameta/allure/restassured/AllureRestAssured.java +++ b/allure-rest-assured/src/main/java/io/qameta/allure/restassured/AllureRestAssured.java @@ -33,6 +33,7 @@ import static io.qameta.allure.attachment.http.HttpRequestAttachment.Builder.create; import static io.qameta.allure.attachment.http.HttpResponseAttachment.Builder.create; +import static java.util.Optional.ofNullable; /** * Allure logger filter for Rest-assured. @@ -105,10 +106,11 @@ public Response filter(final FilterableRequestSpecification requestSpec, ); final Response response = filterContext.next(requestSpec, responseSpec); - if (Objects.isNull(responseAttachmentName)) { - responseAttachmentName = response.getStatusLine(); - } - final HttpResponseAttachment responseAttachment = create(responseAttachmentName) + + final String attachmentName = ofNullable(responseAttachmentName) + .orElse(response.getStatusLine()); + + final HttpResponseAttachment responseAttachment = create(attachmentName) .setResponseCode(response.getStatusCode()) .setHeaders(toMapConverter(response.getHeaders())) .setBody(prettifier.getPrettifiedBodyIfPossible(response, response.getBody())) diff --git a/allure-rest-assured/src/test/java/io/qameta/allure/restassured/AllureRestAssuredTest.java b/allure-rest-assured/src/test/java/io/qameta/allure/restassured/AllureRestAssuredTest.java index e92d5d866..d3121ed0f 100644 --- a/allure-rest-assured/src/test/java/io/qameta/allure/restassured/AllureRestAssuredTest.java +++ b/allure-rest-assured/src/test/java/io/qameta/allure/restassured/AllureRestAssuredTest.java @@ -16,6 +16,7 @@ package io.qameta.allure.restassured; import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; import com.google.common.collect.ImmutableList; @@ -23,14 +24,19 @@ import io.qameta.allure.model.TestResult; import io.qameta.allure.test.AllureResults; import io.restassured.RestAssured; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; -import java.util.*; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; + import static io.qameta.allure.test.RunUtils.runWithinTestContext; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.params.provider.Arguments.arguments; @@ -68,6 +74,41 @@ void shouldCreateAttachment(final List attachmentNames, final AllureRest .isEqualTo(attachmentNames); } + @Test + void shouldProperlySetAttachmentNameForSingleFilterInstance() { + final AllureRestAssured filter = new AllureRestAssured(); + + final ResponseDefinitionBuilder responseBuilderOne = WireMock.aResponse() + .withStatus(200) + .withBody("some body"); + + final ResponseDefinitionBuilder responseBuilderTwo = WireMock.aResponse() + .withStatus(400) + .withBody("some other body"); + + RestAssured.replaceFiltersWith(filter); + final AllureResults resultsOne = executeWithStub(responseBuilderOne); + + RestAssured.replaceFiltersWith(filter); + final AllureResults resultsTwo = executeWithStub(responseBuilderTwo); + + assertThat(resultsOne.getTestResults() + .stream() + .map(TestResult::getAttachments) + .flatMap(Collection::stream) + .map(Attachment::getName)) + .hasSize(2) + .anyMatch(res -> res.equals("HTTP/1.1 200 OK")); + + assertThat(resultsTwo.getTestResults() + .stream() + .map(TestResult::getAttachments) + .flatMap(Collection::stream) + .map(Attachment::getName)) + .hasSize(2) + .anyMatch(res -> res.equals("HTTP/1.1 400 Bad Request")); + } + @ParameterizedTest @ArgumentsSource(AttachmentArgumentProvider.class) void shouldCatchAttachmentBody(final List attachmentNames, final AllureRestAssured filter) { @@ -91,15 +132,20 @@ void shouldCatchAttachmentBody(final List attachmentNames, final AllureR } protected final AllureResults execute() { + return executeWithStub(WireMock.aResponse().withBody("some body")); + } + + protected final AllureResults executeWithStub(ResponseDefinitionBuilder responseBuilder) { final WireMockServer server = new WireMockServer(WireMockConfiguration.options().dynamicPort()); + final int statusCode = responseBuilder.build().getStatus(); return runWithinTestContext(() -> { server.start(); WireMock.configureFor(server.port()); - WireMock.stubFor(WireMock.get(WireMock.urlEqualTo("/hello")).willReturn(WireMock.aResponse().withBody("some body"))); + WireMock.stubFor(WireMock.get(WireMock.urlEqualTo("/hello")).willReturn(responseBuilder)); try { - RestAssured.when().get(server.url("/hello")).then().statusCode(200); + RestAssured.when().get(server.url("/hello")).then().statusCode(statusCode); } finally { server.stop(); RestAssured.replaceFiltersWith(ImmutableList.of()); From 156812de46db75413bf99f98fcdfe3c86faf625e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Nov 2021 12:16:02 +0300 Subject: [PATCH 04/13] bump spring-web from 5.3.12 to 5.3.13 (via #682) --- allure-spring-web/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/allure-spring-web/build.gradle.kts b/allure-spring-web/build.gradle.kts index b8f3eac3c..4841fe9d4 100644 --- a/allure-spring-web/build.gradle.kts +++ b/allure-spring-web/build.gradle.kts @@ -1,6 +1,6 @@ description = "Allure Spring Web Integration" -val springWebVersion = "5.3.12" +val springWebVersion = "5.3.13" dependencies { api(project(":allure-attachments")) From d27d555a0dab10bd3781cf522fcf45e76d82d9de Mon Sep 17 00:00:00 2001 From: jsa34 <31512041+jsa34@users.noreply.github.com> Date: Tue, 23 Nov 2021 13:29:21 +0000 Subject: [PATCH 05/13] add cucumberjvm 7 (fixes #684, via #683) --- allure-cucumber7-jvm/build.gradle.kts | 32 + .../cucumber7jvm/AllureCucumber7Jvm.java | 440 ++++++++++ .../allure/cucumber7jvm/LabelBuilder.java | 192 +++++ .../qameta/allure/cucumber7jvm/TagParser.java | 71 ++ .../testsourcemodel/TestSourcesModel.java | 186 +++++ .../TestSourcesModelProxy.java | 76 ++ .../cucumber7jvm/AllureCucumber7JvmTest.java | 768 ++++++++++++++++++ .../cucumber7jvm/samples/AmbigiousSteps.java | 40 + .../cucumber7jvm/samples/AttachmentSteps.java | 39 + .../samples/BackgroundFeatureSteps.java | 43 + .../samples/BrokenFeatureSteps.java | 30 + .../samples/DatatableFeatureSteps.java | 31 + .../cucumber7jvm/samples/HookSteps.java | 64 ++ .../cucumber7jvm/samples/PendingSteps.java | 32 + .../samples/SimpleFeatureSteps.java | 55 ++ .../src/test/resources/allure.properties | 3 + .../test/resources/features/ambigious.feature | 5 + .../resources/features/attachments.feature | 8 + .../resources/features/background.feature | 9 + .../test/resources/features/broken.feature | 4 + .../test/resources/features/datatable.feature | 8 + .../resources/features/description.feature | 17 + .../test/resources/features/examples.feature | 11 + .../test/resources/features/failed.feature | 7 + .../src/test/resources/features/hooks.feature | 98 +++ .../resources/features/multi-examples.feature | 17 + .../test/resources/features/parallel.feature | 17 + .../test/resources/features/pending.feature | 6 + .../features/scenario_description.feature | 19 + .../test/resources/features/simple.feature | 7 + .../src/test/resources/features/tags.feature | 13 + .../test/resources/features/undefined.feature | 6 + build.gradle.kts | 2 +- settings.gradle.kts | 1 + 34 files changed, 2356 insertions(+), 1 deletion(-) create mode 100644 allure-cucumber7-jvm/build.gradle.kts create mode 100644 allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/AllureCucumber7Jvm.java create mode 100644 allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/LabelBuilder.java create mode 100644 allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/TagParser.java create mode 100644 allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/testsourcemodel/TestSourcesModel.java create mode 100644 allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/testsourcemodel/TestSourcesModelProxy.java create mode 100644 allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/AllureCucumber7JvmTest.java create mode 100644 allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/AmbigiousSteps.java create mode 100644 allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/AttachmentSteps.java create mode 100644 allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/BackgroundFeatureSteps.java create mode 100644 allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/BrokenFeatureSteps.java create mode 100644 allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/DatatableFeatureSteps.java create mode 100644 allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/HookSteps.java create mode 100644 allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/PendingSteps.java create mode 100644 allure-cucumber7-jvm/src/test/java/io/qameta/allure/cucumber7jvm/samples/SimpleFeatureSteps.java create mode 100644 allure-cucumber7-jvm/src/test/resources/allure.properties create mode 100644 allure-cucumber7-jvm/src/test/resources/features/ambigious.feature create mode 100644 allure-cucumber7-jvm/src/test/resources/features/attachments.feature create mode 100644 allure-cucumber7-jvm/src/test/resources/features/background.feature create mode 100644 allure-cucumber7-jvm/src/test/resources/features/broken.feature create mode 100644 allure-cucumber7-jvm/src/test/resources/features/datatable.feature create mode 100644 allure-cucumber7-jvm/src/test/resources/features/description.feature create mode 100644 allure-cucumber7-jvm/src/test/resources/features/examples.feature create mode 100644 allure-cucumber7-jvm/src/test/resources/features/failed.feature create mode 100644 allure-cucumber7-jvm/src/test/resources/features/hooks.feature create mode 100644 allure-cucumber7-jvm/src/test/resources/features/multi-examples.feature create mode 100644 allure-cucumber7-jvm/src/test/resources/features/parallel.feature create mode 100644 allure-cucumber7-jvm/src/test/resources/features/pending.feature create mode 100644 allure-cucumber7-jvm/src/test/resources/features/scenario_description.feature create mode 100644 allure-cucumber7-jvm/src/test/resources/features/simple.feature create mode 100644 allure-cucumber7-jvm/src/test/resources/features/tags.feature create mode 100644 allure-cucumber7-jvm/src/test/resources/features/undefined.feature diff --git a/allure-cucumber7-jvm/build.gradle.kts b/allure-cucumber7-jvm/build.gradle.kts new file mode 100644 index 000000000..b6104c0aa --- /dev/null +++ b/allure-cucumber7-jvm/build.gradle.kts @@ -0,0 +1,32 @@ +description = "Allure CucumberJVM 7.0" + +val cucumberVersion = "7.0.0" +val cucumberGherkinVersion = "22.0.0" + +dependencies { + api(project(":allure-java-commons")) + compileOnly("io.cucumber:cucumber-plugin:$cucumberVersion") + implementation("io.cucumber:gherkin:$cucumberGherkinVersion") + testImplementation("io.cucumber:gherkin:$cucumberGherkinVersion") + testImplementation("io.cucumber:cucumber-core:$cucumberVersion") + testImplementation("io.cucumber:cucumber-java:$cucumberVersion") + testImplementation("commons-io:commons-io") + testImplementation("io.github.glytching:junit-extensions") + testImplementation("org.assertj:assertj-core") + testImplementation("org.junit.jupiter:junit-jupiter-api") + testImplementation("org.slf4j:slf4j-simple") + testImplementation(project(":allure-java-commons-test")) + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") +} + +tasks.jar { + manifest { + attributes(mapOf( + "Automatic-Module-Name" to "io.qameta.allure.cucumber7jvm" + )) + } +} + +tasks.test { + useJUnitPlatform() +} diff --git a/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/AllureCucumber7Jvm.java b/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/AllureCucumber7Jvm.java new file mode 100644 index 000000000..467f533af --- /dev/null +++ b/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/AllureCucumber7Jvm.java @@ -0,0 +1,440 @@ +/* + * Copyright 2019 Qameta Software OÜ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.qameta.allure.cucumber7jvm; + +import io.cucumber.messages.types.Examples; +import io.cucumber.messages.types.Feature; +import io.cucumber.messages.types.Scenario; +import io.cucumber.messages.types.TableRow; +import io.cucumber.plugin.ConcurrentEventListener; +import io.cucumber.plugin.event.DataTableArgument; +import io.cucumber.plugin.event.EmbedEvent; +import io.cucumber.plugin.event.EventHandler; +import io.cucumber.plugin.event.EventPublisher; +import io.cucumber.plugin.event.HookTestStep; +import io.cucumber.plugin.event.HookType; +import io.cucumber.plugin.event.PickleStepTestStep; +import io.cucumber.plugin.event.Result; +import io.cucumber.plugin.event.StepArgument; +import io.cucumber.plugin.event.TestCase; +import io.cucumber.plugin.event.TestCaseFinished; +import io.cucumber.plugin.event.TestCaseStarted; +import io.cucumber.plugin.event.TestSourceRead; +import io.cucumber.plugin.event.TestStepFinished; +import io.cucumber.plugin.event.TestStepStarted; +import io.cucumber.plugin.event.WriteEvent; +import io.qameta.allure.Allure; +import io.qameta.allure.AllureLifecycle; +import io.qameta.allure.cucumber7jvm.testsourcemodel.TestSourcesModelProxy; +import io.qameta.allure.model.FixtureResult; +import io.qameta.allure.model.Parameter; +import io.qameta.allure.model.Status; +import io.qameta.allure.model.StatusDetails; +import io.qameta.allure.model.StepResult; +import io.qameta.allure.model.TestResult; +import io.qameta.allure.model.TestResultContainer; + +import java.io.ByteArrayInputStream; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static io.qameta.allure.util.ResultsUtils.createParameter; +import static io.qameta.allure.util.ResultsUtils.getStatus; +import static io.qameta.allure.util.ResultsUtils.getStatusDetails; +import static io.qameta.allure.util.ResultsUtils.md5; + +/** + * Allure plugin for Cucumber JVM 7.0. + */ +@SuppressWarnings({ + "ClassDataAbstractionCoupling", + "ClassFanOutComplexity", + "PMD.ExcessiveImports", + "PMD.GodClass", +}) +public class AllureCucumber7Jvm implements ConcurrentEventListener { + + private final AllureLifecycle lifecycle; + + private final ConcurrentHashMap scenarioUuids = new ConcurrentHashMap<>(); + private final TestSourcesModelProxy testSources = new TestSourcesModelProxy(); + + private final ThreadLocal currentFeature = new InheritableThreadLocal<>(); + private final ThreadLocal currentFeatureFile = new InheritableThreadLocal<>(); + private final ThreadLocal currentTestCase = new InheritableThreadLocal<>(); + private final ThreadLocal currentContainer = new InheritableThreadLocal<>(); + private final ThreadLocal forbidTestCaseStatusChange = new InheritableThreadLocal<>(); + + private final EventHandler featureStartedHandler = this::handleFeatureStartedHandler; + private final EventHandler caseStartedHandler = this::handleTestCaseStarted; + private final EventHandler caseFinishedHandler = this::handleTestCaseFinished; + private final EventHandler stepStartedHandler = this::handleTestStepStarted; + private final EventHandler stepFinishedHandler = this::handleTestStepFinished; + private final EventHandler writeEventHandler = this::handleWriteEvent; + private final EventHandler embedEventHandler = this::handleEmbedEvent; + + private static final String TXT_EXTENSION = ".txt"; + private static final String TEXT_PLAIN = "text/plain"; + + @SuppressWarnings("unused") + public AllureCucumber7Jvm() { + this(Allure.getLifecycle()); + } + + public AllureCucumber7Jvm(final AllureLifecycle lifecycle) { + this.lifecycle = lifecycle; + } + + /* + Event Handlers + */ + @Override + public void setEventPublisher(final EventPublisher publisher) { + publisher.registerHandlerFor(TestSourceRead.class, featureStartedHandler); + + publisher.registerHandlerFor(TestCaseStarted.class, caseStartedHandler); + publisher.registerHandlerFor(TestCaseFinished.class, caseFinishedHandler); + + publisher.registerHandlerFor(TestStepStarted.class, stepStartedHandler); + publisher.registerHandlerFor(TestStepFinished.class, stepFinishedHandler); + + publisher.registerHandlerFor(WriteEvent.class, writeEventHandler); + publisher.registerHandlerFor(EmbedEvent.class, embedEventHandler); + } + + private void handleFeatureStartedHandler(final TestSourceRead event) { + testSources.addTestSourceReadEvent(event.getUri(), event); + } + + private void handleTestCaseStarted(final TestCaseStarted event) { + currentFeatureFile.set(event.getTestCase().getUri()); + currentFeature.set(testSources.getFeature(currentFeatureFile.get())); + currentTestCase.set(event.getTestCase()); + currentContainer.set(UUID.randomUUID().toString()); + forbidTestCaseStatusChange.set(false); + + final Deque tags = new LinkedList<>(currentTestCase.get().getTags()); + + final Feature feature = currentFeature.get(); + final LabelBuilder labelBuilder = new LabelBuilder(feature, currentTestCase.get(), tags); + + final String name = currentTestCase.get().getName(); + final String featureName = feature.getName(); + + final TestResult result = new TestResult() + .setUuid(getTestCaseUuid(currentTestCase.get())) + .setHistoryId(getHistoryId(currentTestCase.get())) + .setFullName(featureName + ": " + name) + .setName(name) + .setLabels(labelBuilder.getScenarioLabels()) + .setLinks(labelBuilder.getScenarioLinks()); + + final Scenario scenarioDefinition = + testSources.getScenarioDefinition( + currentFeatureFile.get(), + currentTestCase.get().getLocation().getLine() + ); + + if (scenarioDefinition.getExamples() != null) { + result.setParameters( + getExamplesAsParameters(scenarioDefinition, currentTestCase.get()) + ); + } + + final String description = Stream.of(feature.getDescription(), scenarioDefinition.getDescription()) + .filter(Objects::nonNull) + .filter(s -> !s.isEmpty()) + .collect(Collectors.joining("\n")); + + if (!description.isEmpty()) { + result.setDescription(description); + } + + final TestResultContainer resultContainer = new TestResultContainer() + .setName(String.format("%s: %s", scenarioDefinition.getKeyword(), scenarioDefinition.getName())) + .setUuid(getTestContainerUuid()) + .setChildren(Collections.singletonList(getTestCaseUuid(currentTestCase.get()))); + + lifecycle.scheduleTestCase(result); + lifecycle.startTestContainer(getTestContainerUuid(), resultContainer); + lifecycle.startTestCase(getTestCaseUuid(currentTestCase.get())); + } + + private void handleTestCaseFinished(final TestCaseFinished event) { + + final String uuid = getTestCaseUuid(event.getTestCase()); + final Optional details = getStatusDetails(event.getResult().getError()); + details.ifPresent(statusDetails -> lifecycle.updateTestCase( + uuid, + testResult -> testResult.setStatusDetails(statusDetails) + )); + lifecycle.stopTestCase(uuid); + lifecycle.stopTestContainer(getTestContainerUuid()); + lifecycle.writeTestCase(uuid); + lifecycle.writeTestContainer(getTestContainerUuid()); + } + + private void handleTestStepStarted(final TestStepStarted event) { + if (event.getTestStep() instanceof PickleStepTestStep) { + final PickleStepTestStep pickleStep = (PickleStepTestStep) event.getTestStep(); + final String stepKeyword = Optional.ofNullable( + testSources.getKeywordFromSource(currentFeatureFile.get(), pickleStep.getStep().getLine()) + ).orElse("UNDEFINED"); + + final StepResult stepResult = new StepResult() + .setName(String.format("%s %s", stepKeyword, pickleStep.getStep().getText())) + .setStart(System.currentTimeMillis()); + + lifecycle.startStep(getTestCaseUuid(currentTestCase.get()), getStepUuid(pickleStep), stepResult); + + final StepArgument stepArgument = pickleStep.getStep().getArgument(); + if (stepArgument instanceof DataTableArgument) { + final DataTableArgument dataTableArgument = (DataTableArgument) stepArgument; + createDataTableAttachment(dataTableArgument); + } + } else if (event.getTestStep() instanceof HookTestStep) { + initHook((HookTestStep) event.getTestStep()); + } + } + + private void initHook(final HookTestStep hook) { + + final FixtureResult hookResult = new FixtureResult() + .setName(hook.getCodeLocation()) + .setStart(System.currentTimeMillis()); + + if (hook.getHookType() == HookType.BEFORE) { + lifecycle.startPrepareFixture(getTestContainerUuid(), getHookStepUuid(hook), hookResult); + } else { + lifecycle.startTearDownFixture(getTestContainerUuid(), getHookStepUuid(hook), hookResult); + } + + } + + private void handleTestStepFinished(final TestStepFinished event) { + if (event.getTestStep() instanceof HookTestStep) { + handleHookStep(event); + } else { + handlePickleStep(event); + } + } + + private void handleWriteEvent(final WriteEvent event) { + lifecycle.addAttachment( + "Text output", + TEXT_PLAIN, + TXT_EXTENSION, + Objects.toString(event.getText()).getBytes(StandardCharsets.UTF_8) + ); + } + + private void handleEmbedEvent(final EmbedEvent event) { + lifecycle.addAttachment(event.name, event.getMediaType(), null, new ByteArrayInputStream(event.getData())); + } + + /* + Utility Methods + */ + + private String getTestContainerUuid() { + return currentContainer.get(); + } + + private String getTestCaseUuid(final TestCase testCase) { + return scenarioUuids.computeIfAbsent(getHistoryId(testCase), it -> UUID.randomUUID().toString()); + } + + private String getStepUuid(final PickleStepTestStep step) { + return currentFeature.get().getName() + getTestCaseUuid(currentTestCase.get()) + + step.getStep().getText() + step.getStep().getLine(); + } + + private String getHookStepUuid(final HookTestStep step) { + return currentFeature.get().getName() + getTestCaseUuid(currentTestCase.get()) + + step.getHookType().toString() + step.getCodeLocation(); + } + + private String getHistoryId(final TestCase testCase) { + final String testCaseLocation = testCase.getUri().toString() + .substring(testCase.getUri().toString().lastIndexOf('/') + 1) + + ":" + testCase.getLocation().getLine(); + return md5(testCaseLocation); + } + + private Status translateTestCaseStatus(final Result testCaseResult) { + switch (testCaseResult.getStatus()) { + case FAILED: + return getStatus(testCaseResult.getError()) + .orElse(Status.FAILED); + case PASSED: + return Status.PASSED; + case SKIPPED: + case PENDING: + return Status.SKIPPED; + case AMBIGUOUS: + case UNDEFINED: + default: + return null; + } + } + + private List getExamplesAsParameters( + final Scenario scenario, final TestCase localCurrentTestCase + ) { + final Optional maybeExample = + scenario.getExamples().stream() + .filter(example -> example.getTableBody().stream() + .anyMatch(row -> row.getLocation().getLine() + == localCurrentTestCase.getLocation().getLine()) + ) + .findFirst(); + + if (!maybeExample.isPresent()) { + return Collections.emptyList(); + } + + final Examples examples = maybeExample.get(); + + final Optional maybeRow = examples.getTableBody().stream() + .filter(example -> example.getLocation().getLine() == localCurrentTestCase.getLocation().getLine()) + .findFirst(); + + if (!maybeRow.isPresent()) { + return Collections.emptyList(); + } + + final TableRow row = maybeRow.get(); + + return IntStream.range(0, examples.getTableHeader().getCells().size()) + .mapToObj(index -> { + final String name = examples.getTableHeader().getCells().get(index).getValue(); + final String value = row.getCells().get(index).getValue(); + return createParameter(name, value); + }) + .collect(Collectors.toList()); + } + + private void createDataTableAttachment(final DataTableArgument dataTableArgument) { + final List> rowsInTable = dataTableArgument.cells(); + final StringBuilder dataTableCsv = new StringBuilder(); + for (List columns : rowsInTable) { + if (!columns.isEmpty()) { + for (int i = 0; i < columns.size(); i++) { + if (i == columns.size() - 1) { + dataTableCsv.append(columns.get(i)); + } else { + dataTableCsv.append(columns.get(i)); + dataTableCsv.append('\t'); + } + } + dataTableCsv.append('\n'); + } + } + final String attachmentSource = lifecycle + .prepareAttachment("Data table", "text/tab-separated-values", "csv"); + lifecycle.writeAttachment(attachmentSource, + new ByteArrayInputStream(dataTableCsv.toString().getBytes(StandardCharsets.UTF_8))); + } + + private void handleHookStep(final TestStepFinished event) { + final HookTestStep hookStep = (HookTestStep) event.getTestStep(); + final String uuid = getHookStepUuid(hookStep); + final FixtureResult fixtureResult = new FixtureResult().setStatus(translateTestCaseStatus(event.getResult())); + + if (!Status.PASSED.equals(fixtureResult.getStatus())) { + final TestResult testResult = new TestResult().setStatus(translateTestCaseStatus(event.getResult())); + final StatusDetails statusDetails = getStatusDetails(event.getResult().getError()) + .orElseGet(StatusDetails::new); + + final String errorMessage = event.getResult().getError() == null ? hookStep.getHookType() + .name() + " is failed." : hookStep.getHookType() + .name() + " is failed: " + event.getResult().getError().getLocalizedMessage(); + statusDetails.setMessage(errorMessage); + + if (hookStep.getHookType() == HookType.BEFORE) { + final TagParser tagParser = new TagParser(currentFeature.get(), currentTestCase.get()); + statusDetails + .setFlaky(tagParser.isFlaky()) + .setMuted(tagParser.isMuted()) + .setKnown(tagParser.isKnown()); + testResult.setStatus(Status.SKIPPED); + updateTestCaseStatus(testResult.getStatus()); + forbidTestCaseStatusChange.set(true); + } else { + testResult.setStatus(Status.BROKEN); + updateTestCaseStatus(testResult.getStatus()); + } + fixtureResult.setStatusDetails(statusDetails); + } + + lifecycle.updateFixture(uuid, result -> result.setStatus(fixtureResult.getStatus()) + .setStatusDetails(fixtureResult.getStatusDetails())); + lifecycle.stopFixture(uuid); + } + + private void handlePickleStep(final TestStepFinished event) { + + final Status stepStatus = translateTestCaseStatus(event.getResult()); + final StatusDetails statusDetails; + if (event.getResult().getStatus() == io.cucumber.plugin.event.Status.UNDEFINED) { + updateTestCaseStatus(Status.PASSED); + + statusDetails = + getStatusDetails(new IllegalStateException("Undefined Step. Please add step definition")) + .orElse(new StatusDetails()); + lifecycle.updateTestCase(getTestCaseUuid(currentTestCase.get()), scenarioResult -> + scenarioResult + .setStatusDetails(statusDetails)); + } else { + statusDetails = + getStatusDetails(event.getResult().getError()) + .orElse(new StatusDetails()); + updateTestCaseStatus(stepStatus); + } + + if (!Status.PASSED.equals(stepStatus) && stepStatus != null) { + forbidTestCaseStatusChange.set(true); + } + + final TagParser tagParser = new TagParser(currentFeature.get(), currentTestCase.get()); + statusDetails + .setFlaky(tagParser.isFlaky()) + .setMuted(tagParser.isMuted()) + .setKnown(tagParser.isKnown()); + + lifecycle.updateStep(getStepUuid((PickleStepTestStep) event.getTestStep()), + stepResult -> stepResult.setStatus(stepStatus).setStatusDetails(statusDetails)); + lifecycle.stopStep(getStepUuid((PickleStepTestStep) event.getTestStep())); + } + + private void updateTestCaseStatus(final Status status) { + if (!forbidTestCaseStatusChange.get()) { + lifecycle.updateTestCase(getTestCaseUuid(currentTestCase.get()), + result -> result.setStatus(status)); + } + } +} diff --git a/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/LabelBuilder.java b/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/LabelBuilder.java new file mode 100644 index 000000000..961138543 --- /dev/null +++ b/allure-cucumber7-jvm/src/main/java/io/qameta/allure/cucumber7jvm/LabelBuilder.java @@ -0,0 +1,192 @@ +/* + * Copyright 2019 Qameta Software OÜ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.qameta.allure.cucumber7jvm; + +import io.cucumber.messages.types.Feature; +import io.cucumber.plugin.event.TestCase; +import io.qameta.allure.model.Label; +import io.qameta.allure.model.Link; +import io.qameta.allure.util.ResultsUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static io.qameta.allure.util.ResultsUtils.createFeatureLabel; +import static io.qameta.allure.util.ResultsUtils.createFrameworkLabel; +import static io.qameta.allure.util.ResultsUtils.createHostLabel; +import static io.qameta.allure.util.ResultsUtils.createLabel; +import static io.qameta.allure.util.ResultsUtils.createLanguageLabel; +import static io.qameta.allure.util.ResultsUtils.createStoryLabel; +import static io.qameta.allure.util.ResultsUtils.createSuiteLabel; +import static io.qameta.allure.util.ResultsUtils.createTestClassLabel; +import static io.qameta.allure.util.ResultsUtils.createThreadLabel; + +/** + * Scenario labels and links builder. + */ +@SuppressWarnings({"CyclomaticComplexity", "PMD.CyclomaticComplexity", "PMD.NcssCount", "MultipleStringLiterals"}) +class LabelBuilder { + private static final Logger LOGGER = LoggerFactory.getLogger(LabelBuilder.class); + private static final String COMPOSITE_TAG_DELIMITER = "="; + + private static final String SEVERITY = "@SEVERITY"; + private static final String ISSUE_LINK = "@ISSUE"; + private static final String TMS_LINK = "@TMSLINK"; + private static final String PLAIN_LINK = "@LINK"; + + private final List