From a98793d4cc2fa03b6e0b6bbfe4ab5af8be6775f5 Mon Sep 17 00:00:00 2001 From: nik Date: Mon, 25 Mar 2013 18:39:30 +0400 Subject: [PATCH] support for running cucumber tests from non-ruby modules --- .idea/modules.xml | 6 +- .../cucumber-test-runner.iml | 16 + .../com/intellij/cucumber/CucumberMain.java | 66 ++++ build/scripts/common_tests.gant | 9 +- build/scripts/cucumber-tests.gant | 60 +++ build/scripts/tests.gant | 1 + .../cucumber-jvm-formatter.iml | 13 + .../java/run/CucumberJvmSMFormatter.java | 349 ++++++++++++++++++ 8 files changed, 516 insertions(+), 4 deletions(-) create mode 100644 build/cucumber-test-runner/cucumber-test-runner.iml create mode 100644 build/cucumber-test-runner/src/com/intellij/cucumber/CucumberMain.java create mode 100644 build/scripts/cucumber-tests.gant create mode 100644 plugins/cucumber-jvm-formatter/cucumber-jvm-formatter.iml create mode 100644 plugins/cucumber-jvm-formatter/src/org/jetbrains/plugins/cucumber/java/run/CucumberJvmSMFormatter.java diff --git a/.idea/modules.xml b/.idea/modules.xml index a79a75c66b68..0e7013fe25da 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -19,20 +19,23 @@ + - + + + @@ -118,7 +121,6 @@ - diff --git a/build/cucumber-test-runner/cucumber-test-runner.iml b/build/cucumber-test-runner/cucumber-test-runner.iml new file mode 100644 index 000000000000..dc0cd4905ca3 --- /dev/null +++ b/build/cucumber-test-runner/cucumber-test-runner.iml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/build/cucumber-test-runner/src/com/intellij/cucumber/CucumberMain.java b/build/cucumber-test-runner/src/com/intellij/cucumber/CucumberMain.java new file mode 100644 index 000000000000..f68c30693561 --- /dev/null +++ b/build/cucumber-test-runner/src/com/intellij/cucumber/CucumberMain.java @@ -0,0 +1,66 @@ +/* + * Copyright 2000-2013 JetBrains s.r.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 com.intellij.cucumber; + +import com.intellij.idea.IdeaTestApplication; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Ref; +import cucumber.io.MultiLoader; +import cucumber.runtime.Runtime; +import cucumber.runtime.RuntimeOptions; + +/** + * @author Dennis.Ushakov + */ +public class CucumberMain { + static { + // Radar #5755208: Command line Java applications need a way to launch without a Dock icon. + System.setProperty("apple.awt.UIElement", "true"); + } + + public static void main(final String[] args) { + IdeaTestApplication.getInstance(null); + final Ref errorRef = new Ref(); + final Ref runtimeRef = new Ref(); + ApplicationManager.getApplication().invokeAndWait(new Runnable() { + @Override + public void run() { + try { + final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + RuntimeOptions runtimeOptions = new RuntimeOptions(System.getProperties(), args); + cucumber.runtime.Runtime runtime = new Runtime(new MultiLoader(classLoader), classLoader, runtimeOptions); + runtimeRef.set(runtime); + runtime.writeStepdefsJson(); + runtime.run(); + } catch (Throwable throwable) { + errorRef.set(throwable); + Logger.getInstance(CucumberMain.class).error(throwable); + } + } + }, ApplicationManager.getApplication().getDefaultModalityState()); + final Throwable throwable = errorRef.get(); + if (throwable != null) { + throwable.printStackTrace(); + } + System.err.println("Failed tests :"); + for (Throwable error : runtimeRef.get().getErrors()) { + error.printStackTrace(); + System.err.println("============================="); + } + System.exit(throwable != null ? 1 : 0); + } +} diff --git a/build/scripts/common_tests.gant b/build/scripts/common_tests.gant index c942c974e12b..466446b0bb92 100644 --- a/build/scripts/common_tests.gant +++ b/build/scripts/common_tests.gant @@ -16,8 +16,9 @@ import static org.jetbrains.jps.idea.IdeaProjectLoader.guessHome includeTargets << new File("${guessHome(this)}/build/scripts/utils.gant") +includeTargets << new File("${guessHome(this)}/build/scripts/cucumber-tests.gant") -requireProperty("out", "$home/out/classes") +requireProperty("out", "$home/out") target(compile: "Compile project") { ant.delete(failonerror: false) { @@ -38,7 +39,7 @@ private pass(String prop) { } } -target('default': 'The default target') { +target('run_tests': 'Run java tests') { depends([compile]) ant.junit(fork: "yes", showoutput: "true", logfailedtests:false) { @@ -75,3 +76,7 @@ target('default': 'The default target') { } } } + +target('default' : "Run all tests") { + depends([compile, run_tests, run_cucumber_tests]) +} \ No newline at end of file diff --git a/build/scripts/cucumber-tests.gant b/build/scripts/cucumber-tests.gant new file mode 100644 index 000000000000..7eb4e4bf40b8 --- /dev/null +++ b/build/scripts/cucumber-tests.gant @@ -0,0 +1,60 @@ +/* + * Copyright 2000-2013 JetBrains s.r.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. + */ +target('run_cucumber_tests': "Run cucumber tests") { + if (!isDefined("cucumber_test_dirs")) { + return + } + + cucumber_test_dirs.each { + def m = findModule(it[0]) + def platformPrefix = it[1] + def testsDir = it[2] + def stepDefsPackage = it[3] + + + ant.java(failonerror: "true", classname: "com.intellij.cucumber.CucumberMain", fork: "true", dir: testsDir) { + jvmarg(line: "-Xmx512m") + jvmarg(line: "-XX:MaxPermSize=350m") + jvmarg(line: "-XX:+HeapDumpOnOutOfMemoryError") + jvmarg(line: "-ea") + jvmarg(line: "-Didea.platform.prefix=$platformPrefix") + + System.getProperties().entrySet().each { + if (it.key.startsWith("pass.")) { + def trimmed = it.key.substring("pass.".length()); + jvmarg(value: "-D${trimmed}=${it.value}"); + }; + } + + arg(value: "--format") + arg(value: "org.jetbrains.plugins.cucumber.java.run.CucumberJvmSMFormatter") + arg(value: "--glue") + arg(value: stepDefsPackage) + arg(value: ".") + + classpath() { + projectBuilder.moduleRuntimeClasspath(findModule("cucumber-test-runner"), false).each { + pathelement(location: it) + } + projectBuilder.moduleRuntimeClasspath(m, true).each { + pathelement(location: it) + } + } + } + + } + +} diff --git a/build/scripts/tests.gant b/build/scripts/tests.gant index 9fc565ee0e1c..14bd38a5a2ea 100644 --- a/build/scripts/tests.gant +++ b/build/scripts/tests.gant @@ -3,6 +3,7 @@ import static org.jetbrains.jps.idea.IdeaProjectLoader.guessHome includeTargets << new File("${guessHome(this)}/build/scripts/common_tests.gant") setProperty("testcases", ["com.intellij.AllTests"]) +setProperty("cucumber_test_dirs", [["git4idea", "Idea", "$home/plugins/git4idea/test-features/git4idea", "git4idea"]]) def isMac = System.getProperty("os.name").toLowerCase().startsWith("mac") def args = [ diff --git a/plugins/cucumber-jvm-formatter/cucumber-jvm-formatter.iml b/plugins/cucumber-jvm-formatter/cucumber-jvm-formatter.iml new file mode 100644 index 000000000000..acf56ba72944 --- /dev/null +++ b/plugins/cucumber-jvm-formatter/cucumber-jvm-formatter.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/plugins/cucumber-jvm-formatter/src/org/jetbrains/plugins/cucumber/java/run/CucumberJvmSMFormatter.java b/plugins/cucumber-jvm-formatter/src/org/jetbrains/plugins/cucumber/java/run/CucumberJvmSMFormatter.java new file mode 100644 index 000000000000..6fc3befcb1b2 --- /dev/null +++ b/plugins/cucumber-jvm-formatter/src/org/jetbrains/plugins/cucumber/java/run/CucumberJvmSMFormatter.java @@ -0,0 +1,349 @@ +package org.jetbrains.plugins.cucumber.java.run; + +import gherkin.formatter.Formatter; +import gherkin.formatter.Reporter; +import gherkin.formatter.model.*; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.text.SimpleDateFormat; +import java.util.ArrayDeque; +import java.util.Date; +import java.util.List; +import java.util.Queue; + +/** + * User: Andrey.Vokin + * Date: 8/10/12 + */ +public class CucumberJvmSMFormatter implements Formatter, Reporter { + private int scenarioCount; + private int passedScenarioCount; + private boolean scenarioPassed = true; + + private int stepCount; + private int passedStepCount; + private int skippedStepCount; + private int pendingStepCount; + private int failedStepCount; + private int undefinedStepCount; + + private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSSZ"); + + private static final String TEMPLATE_TEST_STARTED = + "##teamcity[testStarted timestamp = '%s' locationHint = 'file:///%s' captureStandardOutput = 'true' name = '%s']"; + private static final String TEMPLATE_TEST_FAILED = + "##teamcity[testFailed timestamp = '%s' details = '%s' message = '%s' name = '%s' %s]"; + private static final String TEMPLATE_SCENARIO_FAILED = "##teamcity[customProgressStatus timestamp='%s' type='testFailed']"; + private static final String TEMPLATE_TEST_PENDING = + "##teamcity[testIgnored name = '%s' message = 'Skipped step' timestamp = '%s']"; + + private static final String TEMPLATE_TEST_FINISHED = + "##teamcity[testFinished timestamp = '%s' diagnosticInfo = 'cucumber f/s=(1344855950447, 1344855950447), duration=0, time.now=%s' duration = '0' name = '%s']"; + + private static final String TEMPLATE_ENTER_THE_MATRIX = "##teamcity[enteredTheMatrix timestamp = '%s']"; + + private static final String TEMPLATE_TEST_SUITE_STARTED = + "##teamcity[testSuiteStarted timestamp = '%s' locationHint = 'file://%s' name = '%s']"; + private static final String TEMPLATE_TEST_SUITE_FINISHED = "##teamcity[testSuiteFinished timestamp = '%s' name = '%s']"; + + private static final String TEMPLATE_SCENARIO_COUNTING_STARTED = + "##teamcity[customProgressStatus testsCategory = 'Scenarios' count = '0' timestamp = '%s']"; + private static final String TEMPLATE_SCENARIO_COUNTING_FINISHED = + "##teamcity[customProgressStatus testsCategory = '' count = '0' timestamp = '%s']"; + private static final String TEMPLATE_SCENARIO_STARTED = "##teamcity[customProgressStatus type = 'testStarted' timestamp = '%s']"; + + public static final String RESULT_STATUS_PENDING = "pending"; + + private Appendable appendable; + + private Queue queue; + + private String uri; + private String currentFeatureName; + + private boolean beforeExampleSection; + + private ScenarioOutline currentScenarioOutline; + + private Scenario currentScenario; + + private Queue currentSteps; + + @SuppressWarnings("UnusedDeclaration") + public CucumberJvmSMFormatter(Appendable appendable) { + this.appendable = System.err; + queue = new ArrayDeque(); + currentSteps = new ArrayDeque(); + outCommand(String.format(TEMPLATE_ENTER_THE_MATRIX, getCurrentTime())); + outCommand(String.format(TEMPLATE_SCENARIO_COUNTING_STARTED, getCurrentTime())); + } + + @Override + public void feature(Feature feature) { + if (currentFeatureName != null) { + done(); + } + currentFeatureName = "Feature: " + getName(feature); + outCommand(String.format(TEMPLATE_TEST_SUITE_STARTED, getCurrentTime(), uri + ":" + feature.getLine(), currentFeatureName)); + } + + private boolean isRealScenario(final Scenario scenario) { + return scenario.getKeyword().equals("Scenario"); + } + + @Override + public void scenario(Scenario scenario) { + closeScenario(); + if (isRealScenario(scenario)) { + scenarioCount++; + outCommand(String.format(TEMPLATE_SCENARIO_STARTED, getCurrentTime())); + closeScenarioOutline(); + currentSteps.clear(); + } + currentScenario = scenario; + beforeExampleSection = false; + outCommand(String.format(TEMPLATE_TEST_SUITE_STARTED, getCurrentTime(), uri + ":" + scenario.getLine(), getName(currentScenario))); + + while (queue.size() > 0) { + String smMessage = queue.poll(); + outCommand(smMessage); + } + } + + @Override + public void scenarioOutline(ScenarioOutline outline) { + scenarioCount++; + outCommand(String.format(TEMPLATE_SCENARIO_STARTED, getCurrentTime())); + queue.clear(); + currentSteps.clear(); + + closePreviousScenarios(); + currentScenarioOutline = outline; + currentScenario = null; + beforeExampleSection = true; + outCommand( + String.format(TEMPLATE_TEST_SUITE_STARTED, getCurrentTime(), uri + ":" + outline.getLine(), getName(currentScenarioOutline))); + } + + @Override + public void examples(Examples examples) { + beforeExampleSection = false; + outCommand(String.format(TEMPLATE_TEST_SUITE_STARTED, getCurrentTime(), uri + ":" + examples.getLine(), "Examples:")); + } + + @Override + public void step(Step step) { + if (beforeExampleSection) { + return; + } + currentSteps.add(step); + } + + @Override + public void result(Result result) { + stepCount++; + Step currentStep = currentSteps.poll(); + outCommand(String.format(TEMPLATE_TEST_STARTED, getCurrentTime(), uri + ":" + currentStep.getLine(), getName(currentStep)), true); + String stepFullName = getName(currentStep); + if (result.getStatus().equals(Result.FAILED)) { + failedStepCount++; + scenarioPassed = false; + String fullMessage = result.getErrorMessage().replace("\r", "").replace("\t", " "); + String[] messageInfo = fullMessage.split("\n", 2); + final String message; + final String details; + if (messageInfo.length == 2) { + message = messageInfo[0].trim(); + details = messageInfo[1].trim(); + } + else { + message = fullMessage; + details = ""; + } + + outCommand(String.format(TEMPLATE_TEST_FAILED, getCurrentTime(), escape(details), escape(message), stepFullName, ""), true); + outCommand(String.format(TEMPLATE_SCENARIO_FAILED, getCurrentTime()), true); + } + else if (result.getStatus().equals(RESULT_STATUS_PENDING)) { + pendingStepCount++; + scenarioPassed = false; + outCommand(String.format(TEMPLATE_TEST_PENDING, stepFullName, getCurrentTime()), true); + } + else if (result.equals(Result.UNDEFINED)) { + undefinedStepCount++; + scenarioPassed = false; + String message = "Undefined step: " + getName(currentStep); + String details = ""; + outCommand(String.format(TEMPLATE_TEST_FAILED, getCurrentTime(), escape(details), escape(message), stepFullName, "error = 'true'"), true); + outCommand(String.format(TEMPLATE_SCENARIO_FAILED, getCurrentTime()), true); + } + else if (result.equals(Result.SKIPPED)) { + skippedStepCount++; + scenarioPassed = false; + outCommand(String.format(TEMPLATE_TEST_PENDING, stepFullName, getCurrentTime()), true); + } + else { + passedStepCount++; + } + + String currentTime = getCurrentTime(); + outCommand(String.format(TEMPLATE_TEST_FINISHED, currentTime, currentTime, stepFullName), true); + } + + private void closeScenario() { + if (currentScenario != null) { + if (isRealScenario(currentScenario)) { + if (scenarioPassed) { + passedScenarioCount++; + } + } + outCommand(String.format(TEMPLATE_TEST_SUITE_FINISHED, getCurrentTime(), getName(currentScenario))); + } + currentScenario = null; + } + + private void closeScenarioOutline() { + if (currentScenarioOutline != null) { + if (scenarioPassed) { + passedScenarioCount++; + } + if (!beforeExampleSection) { + outCommand(String.format(TEMPLATE_TEST_SUITE_FINISHED, getCurrentTime(), "Examples:")); + } + outCommand(String.format(TEMPLATE_TEST_SUITE_FINISHED, getCurrentTime(), getName(currentScenarioOutline))); + } + currentScenarioOutline = null; + } + + private void closePreviousScenarios() { + closeScenario(); + closeScenarioOutline(); + } + + @Override + public void background(Background background) { + closeScenario(); + currentScenario = null; + } + + @Override + public void done() { + closePreviousScenarios(); + outCommand(String.format(TEMPLATE_TEST_SUITE_FINISHED, getCurrentTime(), currentFeatureName)); + } + + @Override + public void uri(String s) { + String currentDir = System.getenv().get("current_dir"); + if (currentDir != null) { + uri = currentDir + File.separator + s; + } + else { + uri = s; + } + } + + @Override + public void eof() { + } + + @Override + public void syntaxError(String s, String s1, List strings, String s2, Integer integer) { + outCommand("Syntax error\n"); + } + + @Override + public void after(Match match, Result result) { + outCommand("after\n"); + } + + @Override + public void match(Match match) { + } + + @SuppressWarnings("UnusedDeclaration") + public void embedding(String mimeType, byte[] data) { + outCommand("embedding\n"); + } + + @SuppressWarnings("UnusedDeclaration") + public void embedding(String s, InputStream inputStream) { + outCommand("embedding\n"); + } + + @Override + public void write(String s) { + out(s); + } + + @Override + public void close() { + outCommand(String.format(TEMPLATE_SCENARIO_COUNTING_FINISHED, getCurrentTime())); + + outCommand(scenarioCount + " scenario (" + passedScenarioCount + " passed)\n"); + outCommand(stepCount + " steps (" + passedStepCount + " passed)\n"); + } + + @Override + public void before(Match match, Result result) { + outCommand("before\n"); + } + + private String getCurrentTime() { + return DATE_FORMAT.format(new Date()); + } + + private String escape(String source) { + return source.replace("|", "||").replace("\n", "|n").replace("\r", "|r").replace("'", "|'").replace("[", "|[").replace("]", "|]"); + } + + private void outCommand(String s) { + outCommand(s, false); + } + + private void outCommand(String s, boolean waitForScenario) { + if (currentScenario == null && waitForScenario) { + queue.add(s); + } + else { + try { + appendable.append("\n"); + appendable.append(s); + appendable.append("\n"); + } + catch (IOException ignored) { + } + } + } + + private void out(String s) { + try { + appendable.append(s); + } + catch (IOException ignored) { + } + } + + private String getName(Scenario scenario) { + if (scenario.getKeyword().equals("Scenario Outline")) { + return escape("Scenario: Line: " + scenario.getLine()); + } + else { + return escape("Scenario: " + scenario.getName()); + } + } + + private String getName(ScenarioOutline outline) { + return escape("Scenario Outline: " + outline.getName()); + } + + private String getName(Step step) { + return escape(step.getKeyword() + " " + step.getName()); + } + + private String getName(Feature feature) { + return escape(feature.getName()); + } +}