summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRaymond Chiu <chiur@google.com>2014-09-23 23:34:56 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2014-09-23 23:34:57 +0000
commit2ee5958b766768b2637ae51c75576c6bc9558a42 (patch)
treeaa83b4521c980526540cf51ca5e2887caafc2890
parent346dce52674589b00ead4e5b59446c95dd130457 (diff)
parent08e866602d910a3b88fc9e9055b1b789777641cc (diff)
downloadbase-2ee5958b766768b2637ae51c75576c6bc9558a42.tar.gz
Merge "Create integration test framework for android gradle plugin." into idea133
-rw-r--r--build-system/.gitignore1
-rw-r--r--build-system/gradle/build.gradle37
-rw-r--r--build-system/gradle/gradle.iml1
-rw-r--r--build-system/gradle/src/integ-test/groovy/com/android/build/gradle/NativeStlTest.groovy111
-rw-r--r--build-system/gradle/src/integ-test/groovy/com/android/build/gradle/NdkPluginIntegrationTest.groovy60
-rw-r--r--build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/category/DeviceTests.java23
-rw-r--r--build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/fixture/GradleProjectTestRule.java269
-rw-r--r--build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/fixture/app/AbstractAndroidTestApp.java79
-rw-r--r--build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/fixture/app/AndroidTestApp.java50
-rw-r--r--build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/fixture/app/HelloWorldJniApp.groovy165
-rw-r--r--build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/fixture/app/TestSourceFile.java65
11 files changed, 861 insertions, 0 deletions
diff --git a/build-system/.gitignore b/build-system/.gitignore
index b09217ab33..7e6354f691 100644
--- a/build-system/.gitignore
+++ b/build-system/.gitignore
@@ -1,5 +1,6 @@
local.properties
.idea
+gradle/build
tests/*/build
tests/api/*/build
tests/applibtest/*/build
diff --git a/build-system/gradle/build.gradle b/build-system/gradle/build.gradle
index 756b6bd990..86bfd56dfc 100644
--- a/build-system/gradle/build.gradle
+++ b/build-system/gradle/build.gradle
@@ -9,12 +9,18 @@ sourceSets {
}
buildTest {
groovy.srcDir file('src/build-test/groovy')
+ groovy.srcDir file('src/integ-test/groovy')
resources.srcDir file('src/build-test/resources')
+ resources.srcDir file('src/integ-test/resources')
}
deviceTest {
groovy.srcDir file('src/device-test/groovy')
resources.srcDir file('src/device-test/resources')
}
+ integTest {
+ groovy.srcDir file('src/integ-test/groovy')
+ resources.srcDir file('src/integ-test/resources')
+ }
}
ext.proguardVersion = "4.11"
@@ -39,6 +45,11 @@ dependencies {
deviceTestCompile sourceSets.buildTest.output
deviceTestCompile configurations.testCompile
deviceTestCompile configurations.testRuntime
+
+ integTestCompile sourceSets.main.output
+ integTestCompile sourceSets.test.output
+ integTestCompile configurations.testCompile
+ integTestCompile configurations.testRuntime
}
// configuration for dependencies provided by the runtime,
@@ -79,6 +90,32 @@ task buildTest(type: Test) {
description = "Runs the project build tests. This requires an SDK either from the Android source tree, under out/..., or an env var ANDROID_HOME."
group = "verification"
systemProperties['jar.path'] = jar.archivePath
+
+ useJUnit {
+ excludeCategories "com.android.build.gradle.internal.test.category.DeviceTests"
+ }
+}
+
+task integTest(type: Test) {
+ testClassesDir = sourceSets.integTest.output.classesDir
+ classpath = sourceSets.integTest.runtimeClasspath
+ description =
+ "Runs the project integration tests. This requires an SDK either from the Android " +
+ "source tree, under out/..., or an env var ANDROID_HOME and a device."
+ group = "verification"
+ systemProperties['jar.path'] = jar.archivePath
+
+ // Allow user to include/exclude categories from the command line.
+ // e.g. add the following to skip device tests:
+ // -DintegTest.excludeCategories=com.android.build.gradle.internal.test.category.DeviceTests
+ useJUnit {
+ if (System.properties[name + ".includeCategories"] != null) {
+ includeCategories System.properties[name + ".includeCategories"]
+ }
+ if (System.properties[name + ".excludeCategories"] != null) {
+ excludeCategories System.properties[name + ".excludeCategories"]
+ }
+ }
}
buildTest.dependsOn ':publishLocal'
diff --git a/build-system/gradle/gradle.iml b/build-system/gradle/gradle.iml
index af6d5ba5ba..2ed4350131 100644
--- a/build-system/gradle/gradle.iml
+++ b/build-system/gradle/gradle.iml
@@ -10,6 +10,7 @@
<sourceFolder url="file://$MODULE_DIR$/src/test/groovy" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/device-test/groovy" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/build-test/groovy" isTestSource="true" />
+ <sourceFolder url="file://$MODULE_DIR$/src/integ-test/groovy" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/../../../../out/host/gradle/tools/base/gradle" />
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
</content>
diff --git a/build-system/gradle/src/integ-test/groovy/com/android/build/gradle/NativeStlTest.groovy b/build-system/gradle/src/integ-test/groovy/com/android/build/gradle/NativeStlTest.groovy
new file mode 100644
index 0000000000..2f86489e50
--- /dev/null
+++ b/build-system/gradle/src/integ-test/groovy/com/android/build/gradle/NativeStlTest.groovy
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2014 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.build.gradle
+
+import com.android.build.gradle.internal.test.fixture.GradleProjectTestRule
+import com.android.build.gradle.internal.test.fixture.app.HelloWorldJniApp
+import org.gradle.tooling.BuildException
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+import java.util.zip.ZipFile
+
+import static org.junit.Assert.assertNotNull
+import static org.junit.Assert.fail
+
+/**
+ * Integration test for STL containers.
+ *
+ * This unit test is parameterized and will be executed for various values of STL.
+ */
+@RunWith(Parameterized.class)
+public class NativeStlTest {
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return [
+ ["system"].toArray(),
+ ["stlport_static"].toArray(),
+ ["stlport_shared"].toArray(),
+ ["gnustl_static"].toArray(),
+ ["gnustl_shared"].toArray(),
+ ["gabi++_static"].toArray(),
+ ["gabi++_shared"].toArray(),
+ ["c++_static"].toArray(),
+ ["c++_shared"].toArray(),
+ ["invalid"].toArray(),
+ ];
+ }
+
+ private String stl;
+
+ NativeStlTest(String stl) {
+ this.stl = stl;
+ }
+
+ @Rule
+ public GradleProjectTestRule fixture = new GradleProjectTestRule();
+
+ @Before
+ public void setup() {
+ new HelloWorldJniApp().writeSources(fixture.getSourceDir())
+ fixture.getBuildFile() << """
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion "19.1.0"
+ useNewNativePlugin true
+ ndk {
+ moduleName "hello-jni"
+ }
+ }
+"""
+ }
+
+ @Test
+ public void buildAppWithStl() {
+ fixture.getBuildFile() << """
+android {
+ ndk {
+ stl "$stl"
+ }
+}
+"""
+ if (!stl.equals("invalid")) {
+ fixture.execute("assembleDebug");
+
+ ZipFile apk = new ZipFile(
+ fixture.file("build/outputs/apk/${fixture.testDir.name}-debug.apk"));
+ assertNotNull(apk.getEntry("lib/x86/libhello-jni.so"));
+ assertNotNull(apk.getEntry("lib/mips/libhello-jni.so"));
+ assertNotNull(apk.getEntry("lib/armeabi/libhello-jni.so"));
+ assertNotNull(apk.getEntry("lib/armeabi-v7a/libhello-jni.so"));
+ } else {
+ // Fail if it's invalid.
+ try {
+ fixture.execute("assemebleDebug");
+ fail();
+ } catch (BuildException ignored) {
+ }
+ }
+ }
+}
+
diff --git a/build-system/gradle/src/integ-test/groovy/com/android/build/gradle/NdkPluginIntegrationTest.groovy b/build-system/gradle/src/integ-test/groovy/com/android/build/gradle/NdkPluginIntegrationTest.groovy
new file mode 100644
index 0000000000..21c93cf01e
--- /dev/null
+++ b/build-system/gradle/src/integ-test/groovy/com/android/build/gradle/NdkPluginIntegrationTest.groovy
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2014 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.build.gradle
+
+import com.android.build.gradle.internal.test.category.DeviceTests
+import com.android.build.gradle.internal.test.fixture.GradleProjectTestRule
+import com.android.build.gradle.internal.test.fixture.app.HelloWorldJniApp
+import org.junit.BeforeClass
+import org.junit.ClassRule
+import org.junit.Test
+import org.junit.experimental.categories.Category
+
+/**
+ * Basic integration test for native plugin.
+ */
+class NdkPluginIntegrationTest {
+ @ClassRule static public GradleProjectTestRule fixture = new GradleProjectTestRule();
+
+ @BeforeClass
+ static public void setup() {
+ new HelloWorldJniApp(true /* useCppSource */).writeSources(fixture.getSourceDir())
+ fixture.getBuildFile() << """
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion "19.1.0"
+ useNewNativePlugin true
+ ndk {
+ moduleName "hello-jni"
+ }
+}
+"""
+ }
+
+ @Test
+ public void basicDebug() {
+ fixture.execute("assembleDebug");
+ }
+
+ @Test
+ @Category(DeviceTests.class)
+ public void connnectedAndroidTest() {
+ fixture.execute("connectedAndroidTest");
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/category/DeviceTests.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/category/DeviceTests.java
new file mode 100644
index 0000000000..6803ff53ff
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/category/DeviceTests.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2014 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.build.gradle.internal.test.category;
+
+/**
+ * JUnit category used to label tests that requires a connected device.
+ */
+public interface DeviceTests {
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/fixture/GradleProjectTestRule.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/fixture/GradleProjectTestRule.java
new file mode 100644
index 0000000000..26770a3a54
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/fixture/GradleProjectTestRule.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2014 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.build.gradle.internal.test.fixture;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.build.gradle.BasePlugin;
+import com.android.io.StreamException;
+import com.android.sdklib.internal.project.ProjectProperties;
+import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+
+import org.gradle.tooling.GradleConnector;
+import org.gradle.tooling.ProjectConnection;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.security.CodeSource;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * JUnit4 test rule for integration test.
+ *
+ * This rule create a gradle project in a temporary directory.
+ * It can be use with the @Rule or @ClassRule annotations. Using this class with @Rule will create
+ * a gradle project in separate directories for each unit test, whereas using it with @ClassRule
+ * creates a single gradle project.
+ *
+ * The test directory is always deleted if it already exist at the start of the test to ensure a
+ * clean environment.
+ */
+public class GradleProjectTestRule implements TestRule {
+
+ private static final String ANDROID_GRADLE_VERSION = "0.13.0";
+
+ private File testDir;
+
+ private File sourceDir;
+
+ private File buildFile;
+
+ private File ndkDir;
+
+ private File sdkDir;
+
+ public GradleProjectTestRule() {
+ sdkDir = findSdkDir();
+ ndkDir = findNdkDir();
+ }
+
+ /**
+ * Recursively delete directory or file.
+ *
+ * @param root directory to delete
+ */
+ private static void deleteRecursive(File root) {
+ if (root.exists()) {
+ if (root.isDirectory()) {
+ for (File file : root.listFiles()) {
+ deleteRecursive(file);
+ }
+ }
+ assertTrue(root.delete());
+ }
+ }
+
+ @Override
+ public Statement apply(final Statement base, Description description) {
+ testDir = new File("build/tmp/tests/" +
+ description.getTestClass().getName());
+
+ // Create separate directory based on test method name if @Rule is used.
+ // getMethodName() is null if this rule is used as a @ClassRule.
+ if (description.getMethodName() != null) {
+ testDir = new File(testDir, description.getMethodName());
+ }
+
+ buildFile = new File(testDir, "build.gradle");
+ sourceDir = new File(testDir, "src");
+
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ if (testDir.exists()) {
+ deleteRecursive(testDir);
+ }
+ assertTrue(testDir.mkdirs());
+ assertTrue(sourceDir.mkdirs());
+
+ Files.write(
+ "buildscript {\n" +
+ " repositories {\n" +
+ " maven { url '" + getRepoDir().toString() + "' }\n" +
+ " }\n" +
+ " dependencies {\n" +
+ " classpath 'com.android.tools.build:gradle:" + ANDROID_GRADLE_VERSION + "'\n" +
+ " }\n" +
+ "}\n",
+ buildFile,
+ Charsets.UTF_8);
+
+ createLocalProp(testDir, sdkDir, ndkDir);
+ base.evaluate();
+ }
+ };
+ }
+
+ /**
+ * Return the directory containing the test project.
+ */
+ public File getTestDir() {
+ return testDir;
+ }
+
+ /**
+ * Return the build.gradle of the test project.
+ */
+ public File getBuildFile() {
+ return buildFile;
+ }
+
+ /**
+ * Return the directory containing the source files of the test project.
+ */
+ public File getSourceDir() {
+ return sourceDir;
+ }
+
+ /**
+ * Return the directory of the repository containing the necessary plugins for testing.
+ */
+ private File getRepoDir() {
+ CodeSource source = getClass().getProtectionDomain().getCodeSource();
+ assert (source != null);
+ URL location = source.getLocation();
+ try {
+ File dir = new File(location.toURI());
+ assertTrue(dir.getPath(), dir.exists());
+
+ File f = dir.getParentFile().getParentFile().getParentFile().getParentFile()
+ .getParentFile().getParentFile().getParentFile();
+ return new File(f, "out" + File.separator + "repo");
+ } catch (URISyntaxException e) {
+ fail(e.getLocalizedMessage());
+ }
+ return null;
+ }
+
+ /**
+ * Runs gradle on the project. Throws exception on failure.
+ *
+ * @param tasks Variadic list of tasks to execute.
+ */
+ public void execute(String ... tasks) {
+ execute(Collections.<String>emptyList(), tasks);
+ }
+
+ /**
+ * Runs gradle on the project. Throws exception on failure.
+ *
+ * @param arguments List of arguments for the gradle command.
+ * @param tasks Variadic list of tasks to execute.
+ */
+ public void execute(List<String> arguments, String ... tasks) {
+ GradleConnector connector = GradleConnector.newConnector();
+
+ ProjectConnection connection = connector
+ .useGradleVersion(BasePlugin.GRADLE_TEST_VERSION)
+ .forProjectDirectory(testDir)
+ .connect();
+ try {
+ List<String> args = Lists.newArrayListWithCapacity(2 + arguments.size());
+ args.add("-i");
+ args.add("-u");
+ args.addAll(arguments);
+
+ connection.newBuild().forTasks(tasks)
+ .withArguments(args.toArray(new String[args.size()])).run();
+ } finally {
+ connection.close();
+ }
+ }
+
+ /**
+ * Create a File object. getTestDir will be the base directory if a relative path is supplied.
+ *
+ * @param path Full path of the file. May be a relative path.
+ */
+ public File file(String path) {
+ File result = new File(path);
+ if (result.isAbsolute()) {
+ return result;
+ } else {
+ return new File(testDir, path);
+ }
+ }
+
+ /**
+ * Returns the SDK folder as built from the Android source tree.
+ */
+ private static File findSdkDir() {
+ String androidHome = System.getenv("ANDROID_HOME");
+ if (androidHome != null) {
+ File f = new File(androidHome);
+ if (f.isDirectory()) {
+ return f;
+ } else {
+ System.out.println("Failed to find SDK in ANDROID_HOME=" + androidHome);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the NDK folder as built from the Android source tree.
+ */
+ private static File findNdkDir() {
+ String androidHome = System.getenv("ANDROID_NDK_HOME");
+ if (androidHome != null) {
+ File f = new File(androidHome);
+ if (f.isDirectory()) {
+ return f;
+ } else {
+ System.out.println("Failed to find NDK in ANDROID_NDK_HOME=" + androidHome);
+ }
+ }
+ return null;
+ }
+
+ private static File createLocalProp(
+ @NonNull File project,
+ @NonNull File sdkDir,
+ @Nullable File ndkDir) throws IOException, StreamException {
+ ProjectPropertiesWorkingCopy localProp = ProjectProperties.create(
+ project.getAbsolutePath(), ProjectProperties.PropertyType.LOCAL);
+ localProp.setProperty(ProjectProperties.PROPERTY_SDK, sdkDir.getAbsolutePath());
+ if (ndkDir != null) {
+ localProp.setProperty(ProjectProperties.PROPERTY_NDK, ndkDir.getAbsolutePath());
+ }
+ localProp.save();
+
+ return (File) localProp.getFile();
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/fixture/app/AbstractAndroidTestApp.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/fixture/app/AbstractAndroidTestApp.java
new file mode 100644
index 0000000000..c620524e05
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/fixture/app/AbstractAndroidTestApp.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2014 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.build.gradle.internal.test.fixture.app;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.NoSuchElementException;
+
+/**
+ * Abstract class implementing AndroidTestApp.
+ */
+public abstract class AbstractAndroidTestApp implements AndroidTestApp {
+ private Multimap<String, TestSourceFile> sourceFiles = ArrayListMultimap.create();
+
+ protected void addFile(TestSourceFile ... files) {
+ for (TestSourceFile file : files) {
+ sourceFiles.put(file.getName(), file);
+ }
+ }
+
+ @Override
+ public TestSourceFile getFile(String filename) {
+ Collection<TestSourceFile> files = sourceFiles.get(filename);
+ if (files.isEmpty()) {
+ throw new NoSuchElementException("Unable to file source file: " + filename + ".");
+ } else if (files.size() > 1) {
+ throw new IllegalArgumentException(
+ "Multiple source files named '" + filename + "'. Specify the path to get one "
+ + "of the following files: \n"
+ + Joiner.on('\n').join(files));
+ }
+ return files.iterator().next();
+ }
+
+ @Override
+ public TestSourceFile getFile(String filename, final String path) {
+ Collection<TestSourceFile> files = sourceFiles.get(filename);
+ return Iterables.find(files, new Predicate<TestSourceFile>() {
+ @Override
+ public boolean apply(TestSourceFile testSourceFile) {
+ return path.equals(testSourceFile.getPath());
+ }
+ });
+ }
+
+ @Override
+ public Collection<TestSourceFile> getAllSourceFiles() {
+ return sourceFiles.values();
+ }
+
+ @Override
+ public void writeSources(File sourceDir) throws IOException {
+ for (TestSourceFile srcFile : getAllSourceFiles()) {
+ srcFile.writeToDir(sourceDir);
+ }
+ }
+
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/fixture/app/AndroidTestApp.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/fixture/app/AndroidTestApp.java
new file mode 100644
index 0000000000..5514e5d5a4
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/fixture/app/AndroidTestApp.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014 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.build.gradle.internal.test.fixture.app;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+
+/**
+ * Interface for an Android test application.
+ *
+ * A test application is a collection of source code that may be reused for multiple tests.
+ */
+public interface AndroidTestApp {
+ /**
+ * Return a source file in the test app with the specified filename.
+ */
+ TestSourceFile getFile(String filename);
+
+ /**
+ * Return a source file in the test app matching the specified filename and path.
+ */
+ TestSourceFile getFile(String filename, String path);
+
+ /**
+ * Return all source files in this test app.
+ */
+ Collection<TestSourceFile> getAllSourceFiles();
+
+ /**
+ * Create all source files in the specified directory.
+ *
+ * @param sourceDir Directory to create the source files in.
+ */
+ void writeSources(File sourceDir) throws IOException;
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/fixture/app/HelloWorldJniApp.groovy b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/fixture/app/HelloWorldJniApp.groovy
new file mode 100644
index 0000000000..ad0abb498b
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/fixture/app/HelloWorldJniApp.groovy
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2014 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.build.gradle.internal.test.fixture.app
+/**
+ * Simple test application that uses JNI to print a "hello world!".
+ *
+ * NOTE: Android project must create an NDK module named "hello-jni".
+ */
+public class HelloWorldJniApp extends AbstractAndroidTestApp implements AndroidTestApp {
+
+ static private final TestSourceFile javaSource =
+ new TestSourceFile("main/java/com/example/hellojni", "HelloJni.java",
+ """
+package com.example.hellojni;
+
+import android.app.Activity;
+import android.widget.TextView;
+import android.os.Bundle;
+
+public class HelloJni extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Create a TextView and set its content from a native function.
+ TextView tv = new TextView(this);
+ tv.setText( stringFromJNI() );
+ setContentView(tv);
+ }
+
+ // A native method that is implemented by the 'hello-jni' native library.
+ public native String stringFromJNI();
+
+ static {
+ System.loadLibrary("hello-jni");
+ }
+}
+""");
+
+ // JNI Implementation in C.
+ static private final TestSourceFile cSource =
+ new TestSourceFile("main/jni", "hello-jni.c",
+"""
+#include <string.h>
+#include <jni.h>
+
+// This is a trivial JNI example where we use a native method
+// to return a new VM String.
+jstring
+Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)
+{
+ return (*env)->NewStringUTF(env, "hello world!");
+}
+""");
+
+ // JNI Implementation in C++.
+ static private final TestSourceFile cppSource =
+ new TestSourceFile("main/jni", "hello-jni.cpp",
+"""
+#include <string.h>
+#include <jni.h>
+#include <cctype>
+
+// This is a trivial JNI example where we use a native method
+// to return a new VM String.
+extern "C"
+jstring
+Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)
+{
+ char greeting[] = "HELLO WORLD!";
+ char* ptr = greeting;
+ while (*ptr) {
+ *ptr = std::tolower(*ptr);
+ ++ptr;
+ }
+ return env->NewStringUTF(greeting);
+}
+""");
+
+
+ static private final TestSourceFile resSource =
+ new TestSourceFile("main/res/values", "strings.xml",
+ """<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">HelloJni</string>
+</resources>
+""");
+
+ static private final TestSourceFile manifest =
+ new TestSourceFile("main", "AndroidManifest.xml",
+"""<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.example.hellojni"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-sdk android:minSdkVersion="3" />
+ <application android:label="@string/app_name">
+ <activity android:name=".HelloJni"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
+""");
+
+ static private final TestSourceFile androidTestSource =
+ new TestSourceFile("androidTest/java/com/example/hellojni", "HelloJniTest.java",
+"""
+package com.example.hellojni;
+
+import android.test.ActivityInstrumentationTestCase;
+
+/**
+ * This is a simple framework for a test of an Application. See
+ * {@link android.test.ApplicationTestCase ApplicationTestCase} for more information on
+ * how to write and extend Application tests.
+ * <p/>
+ * To run this test, you can type:
+ * adb shell am instrument -w \
+ * -e class com.example.hellojni.HelloJniTest \
+ * com.example.hellojni.tests/android.test.InstrumentationTestRunner
+ */
+public class HelloJniTest extends ActivityInstrumentationTestCase<HelloJni> {
+
+ public HelloJniTest() {
+ super("com.example.hellojni", HelloJni.class);
+ }
+
+
+ public void testJniName() {
+ final HelloJni a = getActivity();
+ // ensure a valid handle to the activity has been returned
+ assertNotNull(a);
+
+ assertTrue("hello world!".equals(a.stringFromJNI()));
+ }
+}
+""");
+
+ public HelloWorldJniApp() {
+ this(false);
+ }
+ public HelloWorldJniApp(boolean useCppSource) {
+ addFile(javaSource, useCppSource ? cSource : cppSource, resSource, manifest, androidTestSource);
+ }
+}
diff --git a/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/fixture/app/TestSourceFile.java b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/fixture/app/TestSourceFile.java
new file mode 100644
index 0000000000..0487a6f5e4
--- /dev/null
+++ b/build-system/gradle/src/main/groovy/com/android/build/gradle/internal/test/fixture/app/TestSourceFile.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2014 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.build.gradle.internal.test.fixture.app;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Joiner;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Describes a source file for integration test.
+ */
+public class TestSourceFile {
+ private final String path;
+ private final String name;
+ private final String content;
+
+ public TestSourceFile(String path, String name, String content) {
+ this.path = path;
+ this.name = name;
+ this.content = content;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getContent() {
+ return content;
+ }
+
+ public File writeToDir(File base) throws IOException {
+ File file = new File(base, Joiner.on(File.separatorChar).join(path, name));
+ writeToFile(file);
+ return file;
+ }
+
+ public void writeToFile(File file) throws IOException {
+ if (!file.exists()) {
+ Files.createParentDirs(file);
+ }
+ Files.append(content, file, Charsets.UTF_8);
+ }
+}
+