From a1d247832d8d4e7d3adc34584ba403a5d35f64cf Mon Sep 17 00:00:00 2001 From: Frieder Bluemle Date: Sun, 20 Oct 2013 23:40:46 -0700 Subject: [PATCH 01/10] Remove trailing whitespace --- .../cucumber/api/android/CucumberInstrumentation.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java b/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java index 5f4ea66dc7..6b01d5d83a 100644 --- a/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java +++ b/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java @@ -47,9 +47,9 @@ public void onCreate(Bundle arguments) { if (arguments == null) { throw new CucumberException("No arguments"); } - + debug = getBooleanArgument(arguments, "debug"); - + Context context = getContext(); classLoader = context.getClassLoader(); @@ -93,7 +93,7 @@ private DexFile newDexFile(String apkPath) { @Override public void onStart() { Looper.prepare(); - + if (debug) { Debug.waitForDebugger(); } @@ -128,7 +128,7 @@ private void printSummary() { Log.w(TAG, s); } } - + private boolean getBooleanArgument(Bundle arguments, String tag) { String tagString = arguments.getString(tag); return tagString != null && Boolean.parseBoolean(tagString); From f64b1c3cbb36eec89858b1e8a55cd47fd8287d9a Mon Sep 17 00:00:00 2001 From: Frieder Bluemle Date: Thu, 26 Sep 2013 23:37:28 -0700 Subject: [PATCH 02/10] Add command line option support for Android --- .../api/android/CucumberInstrumentation.java | 10 +++ .../android/InstrumentationArguments.java | 82 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 android/src/main/java/cucumber/runtime/android/InstrumentationArguments.java diff --git a/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java b/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java index 6b01d5d83a..82843bbcf9 100644 --- a/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java +++ b/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java @@ -18,6 +18,7 @@ import cucumber.runtime.android.AndroidLogcatReporter; import cucumber.runtime.android.AndroidObjectFactory; import cucumber.runtime.android.AndroidResourceLoader; +import cucumber.runtime.android.InstrumentationArguments; import cucumber.runtime.android.DexClassFinder; import cucumber.runtime.android.TestCaseCounter; import cucumber.runtime.io.ResourceLoader; @@ -50,6 +51,8 @@ public void onCreate(Bundle arguments) { debug = getBooleanArgument(arguments, "debug"); + InstrumentationArguments instrumentationArguments = new InstrumentationArguments(arguments); + Context context = getContext(); classLoader = context.getClassLoader(); @@ -60,6 +63,7 @@ public void onCreate(Bundle arguments) { for (Class clazz : classFinder.getDescendants(Object.class, context.getPackageName())) { if (clazz.isAnnotationPresent(CucumberOptions.class)) { Log.d(TAG, "Found CucumberOptions in class " + clazz.getName()); + Log.d(TAG, clazz.getAnnotations()[0].toString()); optionsAnnotatedClass = clazz; break; // We assume there is only one CucumberOptions annotated class. } @@ -68,6 +72,12 @@ public void onCreate(Bundle arguments) { throw new CucumberException("No CucumberOptions annotation"); } + String cucumberOptions = instrumentationArguments.getCucumberOptionsString(); + if (!cucumberOptions.isEmpty()) { + Log.d(TAG, "Setting cucumber.options from arguments: '" + cucumberOptions + "'"); + System.setProperty("cucumber.options", cucumberOptions); + } + @SuppressWarnings("unchecked") RuntimeOptionsFactory factory = new RuntimeOptionsFactory(optionsAnnotatedClass, new Class[]{CucumberOptions.class}); runtimeOptions = factory.create(); diff --git a/android/src/main/java/cucumber/runtime/android/InstrumentationArguments.java b/android/src/main/java/cucumber/runtime/android/InstrumentationArguments.java new file mode 100644 index 0000000000..41cb55c270 --- /dev/null +++ b/android/src/main/java/cucumber/runtime/android/InstrumentationArguments.java @@ -0,0 +1,82 @@ +package cucumber.runtime.android; + +import android.os.Bundle; + +/** + * This is a wrapper class around the command line arguments that were supplied + * when the instrumentation was started. + */ +public final class InstrumentationArguments { + private static final String VALUE_SEPARATOR = "--"; + + private Bundle arguments; + + public InstrumentationArguments(Bundle arguments) { + this.arguments = arguments != null ? arguments : new Bundle(); + } + + private boolean getBooleanArgument(String tag) { + String tagString = arguments.getString(tag); + return tagString != null && Boolean.parseBoolean(tagString); + } + + private void appendOption(StringBuilder sb, String optionKey, String optionValue) { + for (String value : optionValue.split(VALUE_SEPARATOR)) { + sb.append(sb.length() == 0 || optionKey.isEmpty() ? "" : " ").append(optionKey).append(optionValue.isEmpty() ? "" : " " + value); + } + } + + /** + * Returns a Cucumber options compatible string based on the argument extras found. + *

+ * The bundle cannot contain multiple entries for the same key, + * however certain Cucumber options can be passed multiple times (e.g. + * {@code --tags}). The solution is to pass values separated by + * {@link InstrumentationArguments#VALUE_SEPARATOR} which will result + * in multiple {@code --key value} pairs being created. + * + * @return the cucumber options string + */ + public String getCucumberOptionsString() { + String cucumberOptions = arguments.getString("cucumberOptions"); + if (cucumberOptions != null) { + return cucumberOptions; + } + + StringBuilder sb = new StringBuilder(); + String features = ""; + for (String key : arguments.keySet()) { + if ("glue".equals(key)) { + appendOption(sb, "--glue", arguments.getString(key)); + } else if ("format".equals(key)) { + appendOption(sb, "--format", arguments.getString(key)); + } else if ("tags".equals(key)) { + appendOption(sb, "--tags", arguments.getString(key)); + } else if ("name".equals(key)) { + appendOption(sb, "--name", arguments.getString(key)); + } else if ("dryRun".equals(key) && getBooleanArgument(key)) { + appendOption(sb, "--dry-run", ""); + } else if ("noDryRun".equals(key) && getBooleanArgument(key)) { + appendOption(sb, "--no-dry-run", ""); + } else if ("monochrome".equals(key) && getBooleanArgument(key)) { + appendOption(sb, "--monochrome", ""); + } else if ("noMonochrome".equals(key) && getBooleanArgument(key)) { + appendOption(sb, "--no-monochrome", ""); + } else if ("strict".equals(key) && getBooleanArgument(key)) { + appendOption(sb, "--strict", ""); + } else if ("noStrict".equals(key) && getBooleanArgument(key)) { + appendOption(sb, "--no-strict", ""); + } else if ("snippets".equals(key)) { + appendOption(sb, "--snippets", arguments.getString(key)); + } else if ("dotcucumber".equals(key)) { + appendOption(sb, "--dotcucumber", arguments.getString(key)); + } else if ("features".equals(key)) { + features = arguments.getString(key); + } + } + // Even though not strictly required, wait until everything else + // has been added before adding any feature references + appendOption(sb, "", features); + return sb.toString(); + } +} From 08a99c57ed26df4a5b0615b973634043306bf2c8 Mon Sep 17 00:00:00 2001 From: Frieder Bluemle Date: Tue, 15 Oct 2013 02:05:07 -0700 Subject: [PATCH 03/10] Add tests for InstrumentationArguments --- .../android/InstrumentationArgumentsTest.java | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 android/src/test/java/cucumber/runtime/android/InstrumentationArgumentsTest.java diff --git a/android/src/test/java/cucumber/runtime/android/InstrumentationArgumentsTest.java b/android/src/test/java/cucumber/runtime/android/InstrumentationArgumentsTest.java new file mode 100644 index 0000000000..5e510fa710 --- /dev/null +++ b/android/src/test/java/cucumber/runtime/android/InstrumentationArgumentsTest.java @@ -0,0 +1,102 @@ +package cucumber.runtime.android; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +import android.os.Bundle; + +@RunWith(RobolectricTestRunner.class) +public class InstrumentationArgumentsTest { + @Test + public void nullArguments() { + InstrumentationArguments parser = new InstrumentationArguments(null); + String cucumberOptions = parser.getCucumberOptionsString(); + assertThat(cucumberOptions, is("")); + } + + @Test + public void emptyArguments() { + InstrumentationArguments parser = new InstrumentationArguments(new Bundle()); + String cucumberOptions = parser.getCucumberOptionsString(); + assertThat(cucumberOptions, is("")); + } + + @Test + public void booleanCucumberOptionArgument() { + Bundle arguments = new Bundle(); + arguments.putString("dryRun", "true"); + InstrumentationArguments parser = new InstrumentationArguments(arguments); + String cucumberOptions = parser.getCucumberOptionsString(); + assertThat(cucumberOptions, is("--dry-run")); + } + + @Test + public void stringCucumberOptionArgument() { + Bundle arguments = new Bundle(); + arguments.putString("name", "SomeFeature"); + InstrumentationArguments parser = new InstrumentationArguments(arguments); + String cucumberOptions = parser.getCucumberOptionsString(); + assertThat(cucumberOptions, is("--name SomeFeature")); + } + + @Test + public void multiCucumberOptionArgument() { + Bundle arguments = new Bundle(); + arguments.putString("name", "Feature1--Feature2"); + InstrumentationArguments parser = new InstrumentationArguments(arguments); + String cucumberOptions = parser.getCucumberOptionsString(); + assertThat(cucumberOptions, is("--name Feature1 --name Feature2")); + } + + @Test + public void cucumberOptionsSingleString() { + String cucumberOptions = "--tags @mytag --monochrome --name MyFeature --dry-run --glue com.someglue.Glue --format pretty --snippets underscore --strict --dotcucumber test features"; + Bundle arguments = new Bundle(); + arguments.putString("cucumberOptions", cucumberOptions); + InstrumentationArguments parser = new InstrumentationArguments(arguments); + assertThat(parser.getCucumberOptionsString(), is(cucumberOptions)); + } + + @Test + public void cucumberOptionsSingleStringPrecedence() { + String cucumberOptions = "--tags @mytag1"; + Bundle arguments = new Bundle(); + arguments.putString("cucumberOptions", cucumberOptions); + arguments.putString("tags", "@mytag2"); + InstrumentationArguments parser = new InstrumentationArguments(arguments); + assertThat(parser.getCucumberOptionsString(), is(cucumberOptions)); + } + + @Test + public void allArguments() { + Bundle arguments = new Bundle(); + arguments.putString("glue", "com.package.Glue"); + arguments.putString("format", "pretty"); + arguments.putString("tags", "@mytag"); + arguments.putString("name", "MyFeature"); + arguments.putString("dryRun", "true"); + arguments.putString("monochrome", "true"); + arguments.putString("strict", "true"); + arguments.putString("snippets", "underscore"); + arguments.putString("dotcucumber", "test"); + arguments.putString("features", "features"); + InstrumentationArguments parser = new InstrumentationArguments(arguments); + + String cucumberOptions = parser.getCucumberOptionsString(); + + assertThat(cucumberOptions, is("--tags @mytag --monochrome --name MyFeature --dry-run --glue com.package.Glue --format pretty --snippets underscore --strict --dotcucumber test features")); + } + + @Test + public void argumentValueWithSpaces() { + Bundle arguments = new Bundle(); + arguments.putString("name", "'Name with spaces'"); + InstrumentationArguments parser = new InstrumentationArguments(arguments); + String cucumberOptions = parser.getCucumberOptionsString(); + assertThat(cucumberOptions, is("--name 'Name with spaces'")); + } +} From f1bfa94c6bcbbbfe94defa3b3b19b713dba73d1d Mon Sep 17 00:00:00 2001 From: Frieder Bluemle Date: Fri, 11 Oct 2013 17:54:41 -0700 Subject: [PATCH 04/10] Add support for 'count' option This adds support to only count the number of scenarios by setting the 'count' argument to true. --- .../api/android/CucumberInstrumentation.java | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java b/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java index 82843bbcf9..a9f5c948c4 100644 --- a/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java +++ b/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java @@ -34,19 +34,27 @@ import java.util.List; public class CucumberInstrumentation extends Instrumentation { + public static final String REPORT_VALUE_ID = "CucumberInstrumentation"; + public static final String REPORT_KEY_NUM_TOTAL = "numtests"; public static final String TAG = "cucumber-android"; + + private final Bundle results = new Bundle(); + private boolean justCount; + private int testCount; + private RuntimeOptions runtimeOptions; private ResourceLoader resourceLoader; private ClassLoader classLoader; private Runtime runtime; private boolean debug; + private List cucumberFeatures; @Override public void onCreate(Bundle arguments) { super.onCreate(arguments); - if (arguments == null) { - throw new CucumberException("No arguments"); + if (arguments != null) { + justCount = getBooleanArgument(arguments, "count"); } debug = getBooleanArgument(arguments, "debug"); @@ -88,6 +96,8 @@ public void onCreate(Bundle arguments) { AndroidObjectFactory objectFactory = new AndroidObjectFactory(delegateObjectFactory, this); backends.add(new JavaBackend(objectFactory, classFinder)); runtime = new Runtime(resourceLoader, classLoader, backends, runtimeOptions); + cucumberFeatures = runtimeOptions.cucumberFeatures(resourceLoader); + testCount = TestCaseCounter.countTestCasesOf(cucumberFeatures); start(); } @@ -108,25 +118,28 @@ public void onStart() { Debug.waitForDebugger(); } - final List cucumberFeatures = runtimeOptions.cucumberFeatures(resourceLoader); - final int numberOfTests = TestCaseCounter.countTestCasesOf(cucumberFeatures); - - runtimeOptions.getFormatters().add(new AndroidInstrumentationReporter(runtime, this, numberOfTests)); - runtimeOptions.getFormatters().add(new AndroidLogcatReporter(TAG)); + if (justCount) { + results.putString(Instrumentation.REPORT_KEY_IDENTIFIER, REPORT_VALUE_ID); + results.putInt(REPORT_KEY_NUM_TOTAL, testCount); + finish(Activity.RESULT_OK, results); + } else { + runtimeOptions.getFormatters().add(new AndroidInstrumentationReporter(runtime, this, testCount)); + runtimeOptions.getFormatters().add(new AndroidLogcatReporter(TAG)); - final Reporter reporter = runtimeOptions.reporter(classLoader); - final Formatter formatter = runtimeOptions.formatter(classLoader); + final Reporter reporter = runtimeOptions.reporter(classLoader); + final Formatter formatter = runtimeOptions.formatter(classLoader); - for (final CucumberFeature cucumberFeature : cucumberFeatures) { - cucumberFeature.run(formatter, reporter, runtime); - } + for (final CucumberFeature cucumberFeature : cucumberFeatures) { + cucumberFeature.run(formatter, reporter, runtime); + } - formatter.done(); - formatter.close(); + formatter.done(); + formatter.close(); - printSummary(); + printSummary(); - finish(Activity.RESULT_OK, new Bundle()); + finish(Activity.RESULT_OK, results); + } } private void printSummary() { From c55f0d039a115ab3ae3712cf520f9d6e95f47bb8 Mon Sep 17 00:00:00 2001 From: Frieder Bluemle Date: Fri, 11 Oct 2013 19:00:37 -0700 Subject: [PATCH 05/10] Add support for 'debug' option This adds support to wait for a debugger to attach at instrumentation start. 'debug' can either be set to true or a number which represents the timeout in milliseconds. Default timeout is 10 seconds. --- .../api/android/CucumberInstrumentation.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java b/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java index a9f5c948c4..9731851d1e 100644 --- a/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java +++ b/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java @@ -36,9 +36,11 @@ public class CucumberInstrumentation extends Instrumentation { public static final String REPORT_VALUE_ID = "CucumberInstrumentation"; public static final String REPORT_KEY_NUM_TOTAL = "numtests"; + public static final int DEFAULT_DEBUGGER_TIMEOUT = 10000; public static final String TAG = "cucumber-android"; private final Bundle results = new Bundle(); + private int debuggerTimeout; private boolean justCount; private int testCount; @@ -54,6 +56,16 @@ public void onCreate(Bundle arguments) { super.onCreate(arguments); if (arguments != null) { + String debug = arguments.getString("debug"); + if (debug != null) { + try { + debuggerTimeout = Integer.parseInt(debug); + } catch (NumberFormatException e) { + if (Boolean.parseBoolean(debug)) { + debuggerTimeout = DEFAULT_DEBUGGER_TIMEOUT; + } + } + } justCount = getBooleanArgument(arguments, "count"); } @@ -123,6 +135,10 @@ public void onStart() { results.putInt(REPORT_KEY_NUM_TOTAL, testCount); finish(Activity.RESULT_OK, results); } else { + if (debuggerTimeout != 0) { + waitForDebugger(debuggerTimeout); + } + runtimeOptions.getFormatters().add(new AndroidInstrumentationReporter(runtime, this, testCount)); runtimeOptions.getFormatters().add(new AndroidLogcatReporter(TAG)); @@ -142,6 +158,37 @@ public void onStart() { } } + /** + * Waits the specified time for a debugger to attach. + *

+ * For some reason {@link Debug#waitForDebugger()} is not blocking and thinks a debugger is + * attached when there isn't. + * + * @param timeout the time in milliseconds to wait + */ + private void waitForDebugger(int timeout) { + System.out.println("waiting " + timeout + "ms for debugger to attach."); + long elapsed = 0; + while (!Debug.isDebuggerConnected() && elapsed < timeout) { + try { + System.out.println("waiting for debugger to attach..."); + Thread.sleep(1000); + elapsed += 1000; + } catch (InterruptedException ie) { + } + } + if (Debug.isDebuggerConnected()) { + System.out.println("waiting for debugger to settle..."); + try { + Thread.sleep(1300); + } catch (InterruptedException e) { + } + System.out.println("debugger connected."); + } else { + System.out.println("no debugger connected."); + } + } + private void printSummary() { // TODO move this stuff into the AndroidLogcatReporter for (Throwable t : runtime.getErrors()) { From be024b83211589f088b941a82c4ffa1e85b8a004 Mon Sep 17 00:00:00 2001 From: Frieder Bluemle Date: Mon, 21 Oct 2013 00:15:26 -0700 Subject: [PATCH 06/10] Remove duplicate code for 'debug' option 'debug' support has been added through PR #613 --- .../api/android/CucumberInstrumentation.java | 54 ++----------------- 1 file changed, 3 insertions(+), 51 deletions(-) diff --git a/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java b/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java index 9731851d1e..150f4dc2a5 100644 --- a/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java +++ b/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java @@ -36,11 +36,9 @@ public class CucumberInstrumentation extends Instrumentation { public static final String REPORT_VALUE_ID = "CucumberInstrumentation"; public static final String REPORT_KEY_NUM_TOTAL = "numtests"; - public static final int DEFAULT_DEBUGGER_TIMEOUT = 10000; public static final String TAG = "cucumber-android"; private final Bundle results = new Bundle(); - private int debuggerTimeout; private boolean justCount; private int testCount; @@ -56,21 +54,10 @@ public void onCreate(Bundle arguments) { super.onCreate(arguments); if (arguments != null) { - String debug = arguments.getString("debug"); - if (debug != null) { - try { - debuggerTimeout = Integer.parseInt(debug); - } catch (NumberFormatException e) { - if (Boolean.parseBoolean(debug)) { - debuggerTimeout = DEFAULT_DEBUGGER_TIMEOUT; - } - } - } + debug = getBooleanArgument(arguments, "debug"); justCount = getBooleanArgument(arguments, "count"); } - debug = getBooleanArgument(arguments, "debug"); - InstrumentationArguments instrumentationArguments = new InstrumentationArguments(arguments); Context context = getContext(); @@ -126,17 +113,13 @@ private DexFile newDexFile(String apkPath) { public void onStart() { Looper.prepare(); - if (debug) { - Debug.waitForDebugger(); - } - if (justCount) { results.putString(Instrumentation.REPORT_KEY_IDENTIFIER, REPORT_VALUE_ID); results.putInt(REPORT_KEY_NUM_TOTAL, testCount); finish(Activity.RESULT_OK, results); } else { - if (debuggerTimeout != 0) { - waitForDebugger(debuggerTimeout); + if (debug) { + Debug.waitForDebugger(); } runtimeOptions.getFormatters().add(new AndroidInstrumentationReporter(runtime, this, testCount)); @@ -158,37 +141,6 @@ public void onStart() { } } - /** - * Waits the specified time for a debugger to attach. - *

- * For some reason {@link Debug#waitForDebugger()} is not blocking and thinks a debugger is - * attached when there isn't. - * - * @param timeout the time in milliseconds to wait - */ - private void waitForDebugger(int timeout) { - System.out.println("waiting " + timeout + "ms for debugger to attach."); - long elapsed = 0; - while (!Debug.isDebuggerConnected() && elapsed < timeout) { - try { - System.out.println("waiting for debugger to attach..."); - Thread.sleep(1000); - elapsed += 1000; - } catch (InterruptedException ie) { - } - } - if (Debug.isDebuggerConnected()) { - System.out.println("waiting for debugger to settle..."); - try { - Thread.sleep(1300); - } catch (InterruptedException e) { - } - System.out.println("debugger connected."); - } else { - System.out.println("no debugger connected."); - } - } - private void printSummary() { // TODO move this stuff into the AndroidLogcatReporter for (Throwable t : runtime.getErrors()) { From b51830099c7c3b478d5dc1291b867de62c5dfeaa Mon Sep 17 00:00:00 2001 From: Frieder Bluemle Date: Fri, 11 Oct 2013 19:17:46 -0700 Subject: [PATCH 07/10] Add support for 'coverage' option This adds support to generate EMMA code coverage reports when using CucumberInstrumentation. It is pretty much a 1:1 copy from InstrumentationTestRunner. --- .../api/android/CucumberInstrumentation.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java b/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java index 150f4dc2a5..9006027769 100644 --- a/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java +++ b/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java @@ -29,18 +29,25 @@ import gherkin.formatter.Formatter; import gherkin.formatter.Reporter; +import java.io.File; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; public class CucumberInstrumentation extends Instrumentation { public static final String REPORT_VALUE_ID = "CucumberInstrumentation"; public static final String REPORT_KEY_NUM_TOTAL = "numtests"; + private static final String REPORT_KEY_COVERAGE_PATH = "coverageFilePath"; + private static final String DEFAULT_COVERAGE_FILE_NAME = "coverage.ec"; public static final String TAG = "cucumber-android"; private final Bundle results = new Bundle(); private boolean justCount; private int testCount; + private boolean coverage; + private String coverageFilePath; private RuntimeOptions runtimeOptions; private ResourceLoader resourceLoader; @@ -56,6 +63,8 @@ public void onCreate(Bundle arguments) { if (arguments != null) { debug = getBooleanArgument(arguments, "debug"); justCount = getBooleanArgument(arguments, "count"); + coverage = getBooleanArgument(arguments, "coverage"); + coverageFilePath = arguments.getString("coverageFile"); } InstrumentationArguments instrumentationArguments = new InstrumentationArguments(arguments); @@ -137,6 +146,10 @@ public void onStart() { printSummary(); + if (coverage) { + generateCoverageReport(); + } + finish(Activity.RESULT_OK, results); } } @@ -155,4 +168,58 @@ private boolean getBooleanArgument(Bundle arguments, String tag) { String tagString = arguments.getString(tag); return tagString != null && Boolean.parseBoolean(tagString); } + + private void generateCoverageReport() { + // use reflection to call emma dump coverage method, to avoid + // always statically compiling against emma jar + String coverageFilePath = getCoverageFilePath(); + java.io.File coverageFile = new java.io.File(coverageFilePath); + try { + Class emmaRTClass = Class.forName("com.vladium.emma.rt.RT"); + Method dumpCoverageMethod = emmaRTClass.getMethod("dumpCoverageData", + coverageFile.getClass(), boolean.class, boolean.class); + + dumpCoverageMethod.invoke(null, coverageFile, false, false); + // output path to generated coverage file so it can be parsed by a test harness if + // needed + results.putString(REPORT_KEY_COVERAGE_PATH, coverageFilePath); + // also output a more user friendly msg + final String currentStream = results.getString( + Instrumentation.REPORT_KEY_STREAMRESULT); + results.putString(Instrumentation.REPORT_KEY_STREAMRESULT, + String.format("%s\nGenerated code coverage data to %s", currentStream, + coverageFilePath)); + } catch (ClassNotFoundException e) { + reportEmmaError("Is emma jar on classpath?", e); + } catch (SecurityException e) { + reportEmmaError(e); + } catch (NoSuchMethodException e) { + reportEmmaError(e); + } catch (IllegalArgumentException e) { + reportEmmaError(e); + } catch (IllegalAccessException e) { + reportEmmaError(e); + } catch (InvocationTargetException e) { + reportEmmaError(e); + } + } + + private String getCoverageFilePath() { + if (coverageFilePath == null) { + return getTargetContext().getFilesDir().getAbsolutePath() + File.separator + + DEFAULT_COVERAGE_FILE_NAME; + } else { + return coverageFilePath; + } + } + + private void reportEmmaError(Exception e) { + reportEmmaError("", e); + } + + private void reportEmmaError(String hint, Exception e) { + String msg = "Failed to generate emma coverage. " + hint; + Log.e(TAG, msg, e); + results.putString(Instrumentation.REPORT_KEY_STREAMRESULT, "\nError: " + msg); + } } From 72500a0cf298f802de7e48b3c58f397273030559 Mon Sep 17 00:00:00 2001 From: Frieder Bluemle Date: Fri, 11 Oct 2013 19:33:21 -0700 Subject: [PATCH 08/10] Add support for 'log' option This has the same effect as providing the Cucumber option '--dry-run'. --- .../java/cucumber/api/android/CucumberInstrumentation.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java b/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java index 9006027769..2df377282a 100644 --- a/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java +++ b/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java @@ -62,6 +62,10 @@ public void onCreate(Bundle arguments) { if (arguments != null) { debug = getBooleanArgument(arguments, "debug"); + boolean logOnly = getBooleanArgument(arguments, "log"); + if (logOnly && arguments.getString("dryRun") == null) { + arguments.putString("dryRun", "true"); + } justCount = getBooleanArgument(arguments, "count"); coverage = getBooleanArgument(arguments, "coverage"); coverageFilePath = arguments.getString("coverageFile"); From 88a04513eddd07b4c015addc3caf5561ca2c8f10 Mon Sep 17 00:00:00 2001 From: Frieder Bluemle Date: Mon, 21 Oct 2013 01:06:15 -0700 Subject: [PATCH 09/10] Move option parsing to InstrumentationArguments This removes some code duplication in CucumberInstrumentation and InstrumentationArguments. --- .../api/android/CucumberInstrumentation.java | 30 ++++--------------- .../android/InstrumentationArguments.java | 27 +++++++++++++++++ 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java b/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java index 2df377282a..12565377f7 100644 --- a/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java +++ b/android/src/main/java/cucumber/api/android/CucumberInstrumentation.java @@ -44,34 +44,20 @@ public class CucumberInstrumentation extends Instrumentation { public static final String TAG = "cucumber-android"; private final Bundle results = new Bundle(); - private boolean justCount; private int testCount; - private boolean coverage; - private String coverageFilePath; private RuntimeOptions runtimeOptions; private ResourceLoader resourceLoader; private ClassLoader classLoader; private Runtime runtime; - private boolean debug; private List cucumberFeatures; + InstrumentationArguments instrumentationArguments; @Override public void onCreate(Bundle arguments) { super.onCreate(arguments); - if (arguments != null) { - debug = getBooleanArgument(arguments, "debug"); - boolean logOnly = getBooleanArgument(arguments, "log"); - if (logOnly && arguments.getString("dryRun") == null) { - arguments.putString("dryRun", "true"); - } - justCount = getBooleanArgument(arguments, "count"); - coverage = getBooleanArgument(arguments, "coverage"); - coverageFilePath = arguments.getString("coverageFile"); - } - - InstrumentationArguments instrumentationArguments = new InstrumentationArguments(arguments); + instrumentationArguments = new InstrumentationArguments(arguments); Context context = getContext(); classLoader = context.getClassLoader(); @@ -126,12 +112,12 @@ private DexFile newDexFile(String apkPath) { public void onStart() { Looper.prepare(); - if (justCount) { + if (instrumentationArguments.isCountEnabled()) { results.putString(Instrumentation.REPORT_KEY_IDENTIFIER, REPORT_VALUE_ID); results.putInt(REPORT_KEY_NUM_TOTAL, testCount); finish(Activity.RESULT_OK, results); } else { - if (debug) { + if (instrumentationArguments.isDebugEnabled()) { Debug.waitForDebugger(); } @@ -150,7 +136,7 @@ public void onStart() { printSummary(); - if (coverage) { + if (instrumentationArguments.isCoverageEnabled()) { generateCoverageReport(); } @@ -168,11 +154,6 @@ private void printSummary() { } } - private boolean getBooleanArgument(Bundle arguments, String tag) { - String tagString = arguments.getString(tag); - return tagString != null && Boolean.parseBoolean(tagString); - } - private void generateCoverageReport() { // use reflection to call emma dump coverage method, to avoid // always statically compiling against emma jar @@ -209,6 +190,7 @@ private void generateCoverageReport() { } private String getCoverageFilePath() { + String coverageFilePath = instrumentationArguments.getCoverageFilePath(); if (coverageFilePath == null) { return getTargetContext().getFilesDir().getAbsolutePath() + File.separator + DEFAULT_COVERAGE_FILE_NAME; diff --git a/android/src/main/java/cucumber/runtime/android/InstrumentationArguments.java b/android/src/main/java/cucumber/runtime/android/InstrumentationArguments.java index 41cb55c270..ea3c54292a 100644 --- a/android/src/main/java/cucumber/runtime/android/InstrumentationArguments.java +++ b/android/src/main/java/cucumber/runtime/android/InstrumentationArguments.java @@ -7,6 +7,11 @@ * when the instrumentation was started. */ public final class InstrumentationArguments { + private static final String KEY_DEBUG = "debug"; + private static final String KEY_LOG = "log"; + private static final String KEY_COUNT = "count"; + private static final String KEY_COVERAGE = "coverage"; + private static final String KEY_COVERAGE_FILE_PATH = "coverageFile"; private static final String VALUE_SEPARATOR = "--"; private Bundle arguments; @@ -56,6 +61,8 @@ public String getCucumberOptionsString() { appendOption(sb, "--name", arguments.getString(key)); } else if ("dryRun".equals(key) && getBooleanArgument(key)) { appendOption(sb, "--dry-run", ""); + } else if ("log".equals(key) && getBooleanArgument(key)) { + appendOption(sb, "--dry-run", ""); } else if ("noDryRun".equals(key) && getBooleanArgument(key)) { appendOption(sb, "--no-dry-run", ""); } else if ("monochrome".equals(key) && getBooleanArgument(key)) { @@ -79,4 +86,24 @@ public String getCucumberOptionsString() { appendOption(sb, "", features); return sb.toString(); } + + public boolean isDebugEnabled() { + return getBooleanArgument(KEY_DEBUG); + } + + public boolean isLogEnabled() { + return getBooleanArgument(KEY_LOG); + } + + public boolean isCountEnabled() { + return getBooleanArgument(KEY_COUNT); + } + + public boolean isCoverageEnabled() { + return getBooleanArgument(KEY_COVERAGE); + } + + public String getCoverageFilePath() { + return arguments.getString(KEY_COVERAGE_FILE_PATH); + } } From ce52f3036901c4d4b006d0cad6a642df7c1c1292 Mon Sep 17 00:00:00 2001 From: Frieder Bluemle Date: Mon, 21 Oct 2013 01:08:52 -0700 Subject: [PATCH 10/10] Add tests for new argument options --- .../android/InstrumentationArgumentsTest.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/android/src/test/java/cucumber/runtime/android/InstrumentationArgumentsTest.java b/android/src/test/java/cucumber/runtime/android/InstrumentationArgumentsTest.java index 5e510fa710..f9fb2d76ec 100644 --- a/android/src/test/java/cucumber/runtime/android/InstrumentationArgumentsTest.java +++ b/android/src/test/java/cucumber/runtime/android/InstrumentationArgumentsTest.java @@ -3,6 +3,7 @@ import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; +import org.apache.tools.ant.taskdefs.condition.IsTrue; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -99,4 +100,46 @@ public void argumentValueWithSpaces() { String cucumberOptions = parser.getCucumberOptionsString(); assertThat(cucumberOptions, is("--name 'Name with spaces'")); } + + @Test + public void debugOptionEnabled() { + Bundle arguments = new Bundle(); + arguments.putString("debug", "true"); + InstrumentationArguments args = new InstrumentationArguments(arguments); + assertThat(args.isDebugEnabled(), is(true)); + } + + @Test + public void logOptionEnabled() { + Bundle arguments = new Bundle(); + arguments.putString("log", "true"); + InstrumentationArguments args = new InstrumentationArguments(arguments); + String cucumberOptions = args.getCucumberOptionsString(); + assertThat(args.isLogEnabled(), is(true)); + assertThat(cucumberOptions, is("--dry-run")); + } + + @Test + public void countOptionEnabled() { + Bundle arguments = new Bundle(); + arguments.putString("count", "true"); + InstrumentationArguments args = new InstrumentationArguments(arguments); + assertThat(args.isCountEnabled(), is(true)); + } + + @Test + public void coverageOptionEnabled() { + Bundle arguments = new Bundle(); + arguments.putString("coverage", "true"); + InstrumentationArguments args = new InstrumentationArguments(arguments); + assertThat(args.isCoverageEnabled(), is(true)); + } + + @Test + public void coverageFilePath() { + Bundle arguments = new Bundle(); + arguments.putString("coverageFile", "some-coverage-file.ec"); + InstrumentationArguments args = new InstrumentationArguments(arguments); + assertThat(args.getCoverageFilePath(), is("some-coverage-file.ec")); + } }