/* * 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 { public static final int DEFAULT_COMPILE_SDK_VERSION = 19; public static final String DEFAULT_BUILD_TOOL_VERSION = "20.0.0"; private static final String ANDROID_GRADLE_VERSION = "0.14.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(); } }