diff --git a/.gitignore b/.gitignore index cc5f8f5d7..6e7d7c79e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .gradle schema build +/*/allure-results/ #IDEA Files .idea @@ -15,3 +16,6 @@ build #Mac OS stuff .DS_Store + +#Netbeans files +/.nb-gradle/ \ No newline at end of file diff --git a/allure-cucumber-jvm/build.gradle b/allure-cucumber-jvm/build.gradle new file mode 100644 index 000000000..f9d243772 --- /dev/null +++ b/allure-cucumber-jvm/build.gradle @@ -0,0 +1,38 @@ +description = 'Allure CucumberJVM' + +configurations { + agent +} + +dependencies { + agent 'org.aspectj:aspectjweaver' + + compile project(':allure-java-commons') + compile 'info.cukes:gherkin:2.12.2' + compile 'info.cukes:cucumber-core:1.2.5' + compile 'info.cukes:cucumber-java:1.2.5' + compile 'info.cukes:cucumber-junit:1.2.5' + + testCompile 'junit:junit:4.12' + + +} + +test.doFirst { + jvmArgs "-javaagent:${configurations.agent.singleFile}" +} + +test { + systemProperty 'allure.model.indentOutput', true + systemProperty 'allure.results.directory', 'build/allure-results' +} + + +task spiOffJar(type: Jar, dependsOn: classes) { + classifier = 'spi-off' + from sourceSets.main.allJava +} + +artifacts { + archives spiOffJar +} \ No newline at end of file diff --git a/allure-cucumber-jvm/src/main/java/io/qameta/allure/cucumberjvm/AllureCucumberJvm.java b/allure-cucumber-jvm/src/main/java/io/qameta/allure/cucumberjvm/AllureCucumberJvm.java new file mode 100644 index 000000000..d40bbbfa2 --- /dev/null +++ b/allure-cucumber-jvm/src/main/java/io/qameta/allure/cucumberjvm/AllureCucumberJvm.java @@ -0,0 +1,240 @@ +package io.qameta.allure.cucumberjvm; + +import cucumber.runtime.StepDefinitionMatch; +import gherkin.I18n; +import gherkin.formatter.Formatter; +import gherkin.formatter.Reporter; +import gherkin.formatter.model.Background; +import gherkin.formatter.model.Examples; +import gherkin.formatter.model.Feature; +import gherkin.formatter.model.Match; +import gherkin.formatter.model.Result; +import gherkin.formatter.model.Scenario; +import gherkin.formatter.model.ScenarioOutline; +import gherkin.formatter.model.Step; +import gherkin.formatter.model.Tag; +import io.qameta.allure.Allure; +import io.qameta.allure.AllureLifecycle; +import io.qameta.allure.ResultsUtils; +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 java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; + + +/** + * Allure plugin for Cucumber-JVM. + */ +public class AllureCucumberJvm implements Reporter, Formatter { + + private static final List SCENARIO_OUTLINE_KEYWORDS = Collections.synchronizedList(new ArrayList()); + + private static final String FAILED = "failed"; + private static final String PASSED = "passed"; + private static final String SKIPPED = "skipped"; + + + private final Deque gherkinSteps = new LinkedList<>(); + private final AllureLifecycle lifecycle; + private Feature currentFeature; + private boolean isNullMatch = true; + private Scenario currentScenario; + + + public AllureCucumberJvm() { + this.lifecycle = Allure.getLifecycle(); + final List i18nList = I18n.getAll(); + + i18nList.forEach(i18n -> SCENARIO_OUTLINE_KEYWORDS.addAll(i18n.keywords("scenario_outline"))); + } + + @Override + public void feature(final Feature feature) { + this.currentFeature = feature; + } + + @Override + public void before(final Match match, final Result result) { + new StepUtils(currentFeature, currentScenario).fireFixtureStep(match, result, true); + } + + @Override + public void after(final Match match, final Result result) { + new StepUtils(currentFeature, currentScenario).fireFixtureStep(match, result, false); + } + + @Override + public void startOfScenarioLifeCycle(final Scenario scenario) { + this.currentScenario = scenario; + + final Deque tags = new LinkedList<>(); + tags.addAll(scenario.getTags()); + + if (SCENARIO_OUTLINE_KEYWORDS.contains(scenario.getKeyword())) { + synchronized (gherkinSteps) { + gherkinSteps.clear(); + } + } else { + tags.addAll(currentFeature.getTags()); + } + + final LabelBuilder labelBuilder = new LabelBuilder(currentFeature, scenario, tags); + + + final TestResult result = new TestResult() + .withUuid(scenario.getId()) + .withHistoryId(StepUtils.getHistoryId(scenario.getId())) + .withName(scenario.getName()) + .withLabels(labelBuilder.getScenarioLabels()) + .withLinks(labelBuilder.getScenarioLinks()); + + if (!currentFeature.getDescription().isEmpty()) { + result.withDescription(currentFeature.getDescription()); + } + + lifecycle.scheduleTestCase(result); + lifecycle.startTestCase(scenario.getId()); + + } + + @Override + public void step(final Step step) { + synchronized (gherkinSteps) { + gherkinSteps.add(step); + } + } + + @Override + public void match(final Match match) { + final StepUtils stepUtils = new StepUtils(currentFeature, currentScenario); + if (match instanceof StepDefinitionMatch) { + isNullMatch = false; + final Step step = stepUtils.extractStep((StepDefinitionMatch) match); + synchronized (gherkinSteps) { + while (gherkinSteps.peek() != null && !stepUtils.isEqualSteps(step, gherkinSteps.peek())) { + stepUtils.fireCanceledStep(gherkinSteps.remove()); + } + if (stepUtils.isEqualSteps(step, gherkinSteps.peek())) { + gherkinSteps.remove(); + } + } + final StepResult stepResult = new StepResult(); + stepResult.withName(String.format("%s %s", step.getKeyword(), step.getName())) + .withStart(System.currentTimeMillis()); + lifecycle.startStep(currentScenario.getId(), stepUtils.getStepUuid(step), stepResult); + } + } + + @Override + public void result(final Result result) { + if (!isNullMatch) { + final StatusDetails statusDetails = new StatusDetails(); + final TagParser tagParser = new TagParser(currentFeature, currentScenario); + statusDetails + .withFlaky(tagParser.isFlaky()) + .withMuted(tagParser.isMuted()) + .withKnown(tagParser.isKnown()); + + switch (result.getStatus()) { + case FAILED: + lifecycle.updateStep(stepResult -> stepResult.withStatus(Status.FAILED)); + lifecycle.updateTestCase(currentScenario.getId(), scenarioResult -> + scenarioResult.withStatus(Status.FAILED) + .withStatusDetails(ResultsUtils.getStatusDetails(result.getError()).get())); + lifecycle.stopStep(); + break; + case SKIPPED: + lifecycle.updateStep(stepResult -> stepResult.withStatus(Status.SKIPPED)); + lifecycle.stopStep(); + break; + case PASSED: + lifecycle.updateStep(stepResult -> stepResult.withStatus(Status.PASSED)); + lifecycle.stopStep(); + lifecycle.updateTestCase(currentScenario.getId(), scenarioResult -> + scenarioResult.withStatus(Status.PASSED) + .withStatusDetails(statusDetails)); + break; + default: + break; + } + isNullMatch = true; + } + } + + @Override + public void endOfScenarioLifeCycle(final Scenario scenario) { + final StepUtils stepUtils = new StepUtils(currentFeature, currentScenario); + synchronized (gherkinSteps) { + while (gherkinSteps.peek() != null) { + stepUtils.fireCanceledStep(gherkinSteps.remove()); + } + } + lifecycle.stopTestCase(scenario.getId()); + lifecycle.writeTestCase(scenario.getId()); + } + + @Override + public void embedding(final String string, final byte[] bytes) { + //Nothing to do with Allure + } + + @Override + public void write(final String string) { + //Nothing to do with Allure + } + + @Override + public void syntaxError(final String state, final String event, + final List legalEvents, final String uri, final Integer line) { + //Nothing to do with Allure + } + + @Override + public void uri(final String uri) { + //Nothing to do with Allure + } + + @Override + public void scenarioOutline(final ScenarioOutline so) { + //Nothing to do with Allure + } + + @Override + public void examples(final Examples exmpls) { + //Nothing to do with Allure + } + + + @Override + public void background(final Background b) { + //Nothing to do with Allure + } + + @Override + public void scenario(final Scenario scnr) { + //Nothing to do with Allure + } + + @Override + public void done() { + //Nothing to do with Allure + } + + @Override + public void close() { + //Nothing to do with Allure + } + + @Override + public void eof() { + //Nothing to do with Allure + + } + +} diff --git a/allure-cucumber-jvm/src/main/java/io/qameta/allure/cucumberjvm/LabelBuilder.java b/allure-cucumber-jvm/src/main/java/io/qameta/allure/cucumberjvm/LabelBuilder.java new file mode 100644 index 000000000..0723ed1c0 --- /dev/null +++ b/allure-cucumber-jvm/src/main/java/io/qameta/allure/cucumberjvm/LabelBuilder.java @@ -0,0 +1,141 @@ +package io.qameta.allure.cucumberjvm; + +import gherkin.formatter.model.Feature; +import gherkin.formatter.model.Scenario; +import gherkin.formatter.model.Tag; +import io.qameta.allure.ResultsUtils; +import io.qameta.allure.Severity; +import io.qameta.allure.SeverityLevel; +import io.qameta.allure.Story; +import io.qameta.allure.model.Label; +import io.qameta.allure.model.Link; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; + +import static io.qameta.allure.ResultsUtils.getHostName; +import static io.qameta.allure.ResultsUtils.getThreadName; + +/** + * Scenario labels and links builder. + */ +class LabelBuilder { + private static final Logger LOG = 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 final List