Add InjectBundle annotation for JUnit4 tests.

This allows tests to receive arguments passed in on the command line.

A future commit will add similar functionality to JUnit3 tests.

DO NOT MERGE

Change-Id: I57fa52b73b8faf1179212778eb82c67ec5eed7b0
diff --git a/androidtestlib/src/com/android/test/InjectBundle.java b/androidtestlib/src/com/android/test/InjectBundle.java
new file mode 100644
index 0000000..f3c2399
--- /dev/null
+++ b/androidtestlib/src/com/android/test/InjectBundle.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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.android.test;
+
+import android.os.Bundle;
+
+import junit.framework.Test;
+
+import org.junit.Before;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Use this to inject a {@link Bundle} containing the command line arguments passed to the
+ * test runner into your JUnit4-style test.
+ * <p/>
+ * To use, just add the correct annotation to an {@link Bundle} field like this:
+ * <pre>
+ *     &#64;InjectBundle public Bundle mMyBundle;
+ * </pre>
+ * The test runner will set the value of this field with the {@link Bundle} after
+ * object construction but before any {@link Before} methods are called.
+ * <p/>
+ * Declaring this in a JUnit3 test (ie a class that is a {@link Test}) will have no effect.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface InjectBundle {
+
+}
diff --git a/androidtestlib/src/com/android/test/runner/AndroidJUnitRunner.java b/androidtestlib/src/com/android/test/runner/AndroidJUnitRunner.java
index a932523..d3bf6bd 100644
--- a/androidtestlib/src/com/android/test/runner/AndroidJUnitRunner.java
+++ b/androidtestlib/src/com/android/test/runner/AndroidJUnitRunner.java
@@ -305,7 +305,7 @@
         if (getBooleanArgument(ARGUMENT_LOG_ONLY)) {
             builder.setSkipExecution(true);
         }
-        return builder.build(this);
+        return builder.build(this, arguments);
     }
 
     /**
diff --git a/androidtestlib/src/com/android/test/runner/AndroidRunnerBuilder.java b/androidtestlib/src/com/android/test/runner/AndroidRunnerBuilder.java
index d56861a..328d413 100644
--- a/androidtestlib/src/com/android/test/runner/AndroidRunnerBuilder.java
+++ b/androidtestlib/src/com/android/test/runner/AndroidRunnerBuilder.java
@@ -16,6 +16,7 @@
 package com.android.test.runner;
 
 import android.app.Instrumentation;
+import android.os.Bundle;
 
 import com.android.test.runner.junit3.AndroidJUnit3Builder;
 import com.android.test.runner.junit4.AndroidJUnit4Builder;
@@ -29,16 +30,14 @@
  */
 class AndroidRunnerBuilder extends AllDefaultPossibilitiesBuilder {
 
-    private final Instrumentation mInstr;
     private final AndroidJUnit3Builder mAndroidJUnit3Builder;
     private final AndroidJUnit4Builder mAndroidJUnit4Builder;
 
-    public AndroidRunnerBuilder(boolean canUseSuiteMethod, Instrumentation instr,
+    public AndroidRunnerBuilder(boolean canUseSuiteMethod, Instrumentation instr, Bundle bundle,
             boolean skipExecution) {
         super(canUseSuiteMethod);
-        mInstr = instr;
-        mAndroidJUnit3Builder = new AndroidJUnit3Builder(mInstr, skipExecution);
-        mAndroidJUnit4Builder = new AndroidJUnit4Builder(mInstr, skipExecution);
+        mAndroidJUnit3Builder = new AndroidJUnit3Builder(instr, skipExecution);
+        mAndroidJUnit4Builder = new AndroidJUnit4Builder(instr, bundle, skipExecution);
     }
 
     @Override
diff --git a/androidtestlib/src/com/android/test/runner/TestRequestBuilder.java b/androidtestlib/src/com/android/test/runner/TestRequestBuilder.java
index a15b70e..7077517 100644
--- a/androidtestlib/src/com/android/test/runner/TestRequestBuilder.java
+++ b/androidtestlib/src/com/android/test/runner/TestRequestBuilder.java
@@ -16,6 +16,7 @@
 package com.android.test.runner;
 
 import android.app.Instrumentation;
+import android.os.Bundle;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -207,13 +208,13 @@
      * If no classes have been explicitly added, will scan the classpath for all tests.
      *
      */
-    public TestRequest build(Instrumentation instr) {
+    public TestRequest build(Instrumentation instr, Bundle bundle) {
         if (mTestLoader.isEmpty()) {
             // no class restrictions have been specified. Load all classes
             loadClassesFromClassPath();
         }
 
-        Request request = classes(instr, mSkipExecution, new Computer(),
+        Request request = classes(instr, bundle, mSkipExecution, new Computer(),
                 mTestLoader.getLoadedClasses().toArray(new Class[0]));
         return new TestRequest(mTestLoader.getLoadFailures(), request.filterWith(mFilter));
     }
@@ -223,14 +224,17 @@
      * in a set of classes.
      *
      * @param instr the {@link Instrumentation} to inject into any tests that require it
+     * @param bundle the {@link Bundle} of command line args to inject into any tests that require
+     *         it
      * @param computer Helps construct Runners from classes
      * @param classes the classes containing the tests
      * @return a <code>Request</code> that will cause all tests in the classes to be run
      */
-    private static Request classes(Instrumentation instr, boolean skipExecution,
+    private static Request classes(Instrumentation instr, Bundle bundle, boolean skipExecution,
             Computer computer, Class<?>... classes) {
         try {
-            AndroidRunnerBuilder builder = new AndroidRunnerBuilder(true, instr, skipExecution);
+            AndroidRunnerBuilder builder = new AndroidRunnerBuilder(true, instr, bundle,
+                    skipExecution);
             Runner suite = computer.getSuite(builder, classes);
             return Request.runner(suite);
         } catch (InitializationError e) {
diff --git a/androidtestlib/src/com/android/test/runner/junit4/AndroidJUnit4Builder.java b/androidtestlib/src/com/android/test/runner/junit4/AndroidJUnit4Builder.java
index 1b14a9a..6a08b6a 100644
--- a/androidtestlib/src/com/android/test/runner/junit4/AndroidJUnit4Builder.java
+++ b/androidtestlib/src/com/android/test/runner/junit4/AndroidJUnit4Builder.java
@@ -16,6 +16,7 @@
 package com.android.test.runner.junit4;
 
 import android.app.Instrumentation;
+import android.os.Bundle;
 
 import com.android.test.InjectContext;
 import com.android.test.InjectInstrumentation;
@@ -32,10 +33,12 @@
 public class AndroidJUnit4Builder extends RunnerBuilder {
 
     private final Instrumentation mInstrumentation;
+    private final Bundle mBundle;
     private boolean mSkipExecution;
 
-    public AndroidJUnit4Builder(Instrumentation instr, boolean skipExecution) {
+    public AndroidJUnit4Builder(Instrumentation instr, Bundle bundle, boolean skipExecution) {
         mInstrumentation = instr;
+        mBundle = bundle;
         mSkipExecution = skipExecution;
     }
 
@@ -45,7 +48,7 @@
             return new NonExecutingJUnit4ClassRunner(testClass);
         }
         if (hasInjectedFields(testClass)) {
-            return new AndroidJUnit4ClassRunner(testClass, mInstrumentation);
+            return new AndroidJUnit4ClassRunner(testClass, mInstrumentation, mBundle);
         }
         return null;
     }
diff --git a/androidtestlib/src/com/android/test/runner/junit4/AndroidJUnit4ClassRunner.java b/androidtestlib/src/com/android/test/runner/junit4/AndroidJUnit4ClassRunner.java
index 41d5ad4..4b6e00c 100644
--- a/androidtestlib/src/com/android/test/runner/junit4/AndroidJUnit4ClassRunner.java
+++ b/androidtestlib/src/com/android/test/runner/junit4/AndroidJUnit4ClassRunner.java
@@ -17,8 +17,10 @@
 
 import android.app.Instrumentation;
 import android.content.Context;
+import android.os.Bundle;
 import android.util.Log;
 
+import com.android.test.InjectBundle;
 import com.android.test.InjectContext;
 import com.android.test.InjectInstrumentation;
 
@@ -37,6 +39,7 @@
 
     private static final String LOG_TAG = "AndroidJUnit4ClassRunner";
     private final Instrumentation mInstr;
+    private final Bundle mBundle;
 
     @SuppressWarnings("serial")
     private static class InvalidInjectException extends Exception {
@@ -45,10 +48,11 @@
         }
     }
 
-    public AndroidJUnit4ClassRunner(Class<?> klass, Instrumentation instr)
+    public AndroidJUnit4ClassRunner(Class<?> klass, Instrumentation instr, Bundle bundle)
             throws InitializationError {
         super(klass);
         mInstr = instr;
+        mBundle = bundle;
     }
 
     @Override
@@ -76,6 +80,11 @@
         for (FrameworkField contextField : contextFields) {
             validateInjectField(errors, contextField, Context.class);
         }
+        List<FrameworkField> bundleFields = getTestClass().getAnnotatedFields(
+                InjectBundle.class);
+        for (FrameworkField bundleField : bundleFields) {
+            validateInjectField(errors, bundleField, Context.class);
+        }
     }
 
     private void validateInjectField(List<Throwable> errors, FrameworkField instrField,
@@ -104,6 +113,11 @@
         for (FrameworkField contextField : contextFields) {
             setFieldValue(test, contextField.getField(), mInstr.getTargetContext());
         }
+        List<FrameworkField> bundleFields = getTestClass().getAnnotatedFields(
+                InjectBundle.class);
+        for (FrameworkField bundleField : bundleFields) {
+            setFieldValue(test, bundleField.getField(), mBundle);
+        }
     }
 
     private void setFieldValue(Object test, Field field, Object value) {
diff --git a/androidtestlib/tests/src/com/android/test/runner/TestRequestBuilderTest.java b/androidtestlib/tests/src/com/android/test/runner/TestRequestBuilderTest.java
index 4d7d9bb..9c4ae12 100644
--- a/androidtestlib/tests/src/com/android/test/runner/TestRequestBuilderTest.java
+++ b/androidtestlib/tests/src/com/android/test/runner/TestRequestBuilderTest.java
@@ -16,8 +16,10 @@
 package com.android.test.runner;
 
 import android.app.Instrumentation;
+import android.os.Bundle;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.test.InjectBundle;
 import com.android.test.InjectInstrumentation;
 
 import org.junit.Assert;
@@ -60,6 +62,10 @@
     @InjectInstrumentation
     public Instrumentation mInstr;
 
+    @InjectBundle
+    public Bundle mBundle;
+
+
     /**
      * Test initial condition for size filtering - that all tests run when no filter is attached
      */
@@ -67,7 +73,7 @@
     public void testNoSize() {
         TestRequestBuilder b = new TestRequestBuilder(new PrintStream(new ByteArrayOutputStream()));
         b.addTestClass(SampleTest.class.getName());
-        TestRequest request = b.build(mInstr);
+        TestRequest request = b.build(mInstr, mBundle);
         JUnitCore testRunner = new JUnitCore();
         Result result = testRunner.run(request.getRequest());
         Assert.assertEquals(2, result.getRunCount());
@@ -81,7 +87,7 @@
         TestRequestBuilder b = new TestRequestBuilder(new PrintStream(new ByteArrayOutputStream()));
         b.addTestClass(SampleTest.class.getName());
         b.addTestSizeFilter("small");
-        TestRequest request = b.build(mInstr);
+        TestRequest request = b.build(mInstr, mBundle);
         JUnitCore testRunner = new JUnitCore();
         Result result = testRunner.run(request.getRequest());
         Assert.assertEquals(1, result.getRunCount());
@@ -96,7 +102,7 @@
         b.addTestClass(SampleTest.class.getName());
         b.addTestClass(SampleClassSize.class.getName());
         b.addTestSizeFilter("small");
-        TestRequest request = b.build(mInstr);
+        TestRequest request = b.build(mInstr, mBundle);
         JUnitCore testRunner = new JUnitCore();
         Result result = testRunner.run(request.getRequest());
         Assert.assertEquals(3, result.getRunCount());
@@ -111,7 +117,7 @@
         b.addAnnotationInclusionFilter(SmallTest.class.getName());
         b.addTestClass(SampleTest.class.getName());
         b.addTestClass(SampleClassSize.class.getName());
-        TestRequest request = b.build(mInstr);
+        TestRequest request = b.build(mInstr, mBundle);
         JUnitCore testRunner = new JUnitCore();
         Result result = testRunner.run(request.getRequest());
         Assert.assertEquals(3, result.getRunCount());
@@ -126,7 +132,7 @@
         b.addAnnotationExclusionFilter(SmallTest.class.getName());
         b.addTestClass(SampleTest.class.getName());
         b.addTestClass(SampleClassSize.class.getName());
-        TestRequest request = b.build(mInstr);
+        TestRequest request = b.build(mInstr, mBundle);
         JUnitCore testRunner = new JUnitCore();
         Result result = testRunner.run(request.getRequest());
         Assert.assertEquals(1, result.getRunCount());