blob: fcb429114e99d4a3f057318c22a6245e693d88d2 [file] [log] [blame]
/*
* Copyright (C) 2012 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.tasks;
import static com.android.builder.core.BuilderConstants.CONNECTED;
import static com.android.builder.core.BuilderConstants.DEVICE;
import static com.android.builder.core.BuilderConstants.FD_ANDROID_RESULTS;
import static com.android.builder.core.BuilderConstants.FD_ANDROID_TESTS;
import static com.android.builder.core.BuilderConstants.FD_FLAVORS;
import static com.android.builder.core.BuilderConstants.FD_REPORTS;
import static com.android.builder.model.AndroidProject.FD_OUTPUTS;
import static com.android.sdklib.BuildToolInfo.PathId.SPLIT_SELECT;
import com.android.build.gradle.internal.scope.ConventionMappingHelper;
import com.android.build.gradle.internal.scope.TaskConfigAction;
import com.android.build.gradle.internal.scope.VariantScope;
import com.android.build.gradle.internal.test.report.ReportType;
import com.android.build.gradle.internal.test.report.TestReport;
import com.android.build.gradle.internal.variant.TestVariantData;
import com.android.builder.internal.testing.SimpleTestCallable;
import com.android.builder.sdk.SdkInfo;
import com.android.builder.sdk.TargetInfo;
import com.android.builder.testing.ConnectedDeviceProvider;
import com.android.builder.testing.SimpleTestRunner;
import com.android.builder.testing.TestData;
import com.android.builder.testing.TestRunner;
import com.android.builder.testing.api.DeviceException;
import com.android.builder.testing.api.DeviceProvider;
import com.android.builder.testing.api.TestException;
import com.android.ide.common.process.ProcessExecutor;
import com.android.utils.FileUtils;
import com.android.utils.StringHelper;
import com.google.common.collect.ImmutableList;
import org.gradle.api.GradleException;
import org.gradle.api.Nullable;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.tasks.TaskAction;
import org.gradle.logging.ConsoleRenderer;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.concurrent.Callable;
/**
* Run instrumentation tests for a given variant
*/
public class DeviceProviderInstrumentTestTask extends BaseTask implements AndroidTestTask {
private File reportsDir;
private File resultsDir;
private File coverageDir;
private String flavorName;
@Nullable
private Collection<String> installOptions;
private DeviceProvider deviceProvider;
private TestData testData;
private File adbExec;
@Nullable
private File splitSelectExec;
private ProcessExecutor processExecutor;
private boolean ignoreFailures;
private boolean testFailed;
@TaskAction
protected void runTests() throws DeviceException, IOException, InterruptedException,
TestRunner.NoAuthorizedDeviceFoundException, TestException {
File resultsOutDir = getResultsDir();
FileUtils.emptyFolder(resultsOutDir);
File coverageOutDir = getCoverageDir();
FileUtils.emptyFolder(coverageOutDir);
boolean success = false;
// If there are tests to run, and the test runner returns with no results, we fail (since
// this is most likely a problem with the device setup). If no, the task will succeed.
if (!testsFound()) {
getLogger().info("No tests found, nothing to do.");
// If we don't create the coverage file, createXxxCoverageReport task will fail.
File emptyCoverageFile = new File(coverageOutDir, SimpleTestCallable.FILE_COVERAGE_EC);
emptyCoverageFile.createNewFile();
success = true;
} else {
File testApk = testData.getTestApk();
String flavor = getFlavorName();
TestRunner testRunner = new SimpleTestRunner(
getSplitSelectExec(),
getProcessExecutor());
deviceProvider.init();
Collection<String> extraArgs = installOptions == null || installOptions.isEmpty()
? ImmutableList.<String>of() : installOptions;
try {
success = testRunner.runTests(getProject().getName(), flavor,
testApk,
testData,
deviceProvider.getDevices(),
deviceProvider.getMaxThreads(),
deviceProvider.getTimeoutInMs(),
extraArgs,
resultsOutDir,
coverageOutDir,
getILogger());
} finally {
deviceProvider.terminate();
}
}
// run the report from the results.
File reportOutDir = getReportsDir();
FileUtils.emptyFolder(reportOutDir);
TestReport report = new TestReport(ReportType.SINGLE_FLAVOR, resultsOutDir, reportOutDir);
report.generateReport();
if (!success) {
testFailed = true;
String reportUrl = new ConsoleRenderer().asClickableFileUrl(
new File(reportOutDir, "index.html"));
String message = "There were failing tests. See the report at: " + reportUrl;
if (getIgnoreFailures()) {
getLogger().warn(message);
return;
} else {
throw new GradleException(message);
}
}
testFailed = false;
}
/**
* Determines if there are any tests to run.
*
* @return true if there are some tests to run, false otherwise
*/
private boolean testsFound() {
// For now we check if there are any test sources. We could inspect the test classes and
// apply JUnit logic to see if there's something to run, but that would not catch the case
// where user makes a typo in a test name or forgets to inherit from a JUnit class
return !getProject().files(testData.getTestDirectories()).getAsFileTree().isEmpty();
}
public File getReportsDir() {
return reportsDir;
}
public void setReportsDir(File reportsDir) {
this.reportsDir = reportsDir;
}
@Override
public File getResultsDir() {
return resultsDir;
}
public void setResultsDir(File resultsDir) {
this.resultsDir = resultsDir;
}
public File getCoverageDir() {
return coverageDir;
}
public void setCoverageDir(File coverageDir) {
this.coverageDir = coverageDir;
}
public String getFlavorName() {
return flavorName;
}
public void setFlavorName(String flavorName) {
this.flavorName = flavorName;
}
public Collection<String> getInstallOptions() {
return installOptions;
}
public void setInstallOptions(Collection<String> installOptions) {
this.installOptions = installOptions;
}
public DeviceProvider getDeviceProvider() {
return deviceProvider;
}
public void setDeviceProvider(DeviceProvider deviceProvider) {
this.deviceProvider = deviceProvider;
}
public TestData getTestData() {
return testData;
}
public void setTestData(TestData testData) {
this.testData = testData;
}
public File getAdbExec() {
return adbExec;
}
public void setAdbExec(File adbExec) {
this.adbExec = adbExec;
}
public File getSplitSelectExec() {
return splitSelectExec;
}
public void setSplitSelectExec(File splitSelectExec) {
this.splitSelectExec = splitSelectExec;
}
public ProcessExecutor getProcessExecutor() {
return processExecutor;
}
public void setProcessExecutor(ProcessExecutor processExecutor) {
this.processExecutor = processExecutor;
}
@Override
public boolean getIgnoreFailures() {
return ignoreFailures;
}
@Override
public void setIgnoreFailures(boolean ignoreFailures) {
this.ignoreFailures = ignoreFailures;
}
@Override
public boolean getTestFailed() {
return testFailed;
}
public static class ConfigAction implements TaskConfigAction<DeviceProviderInstrumentTestTask> {
private final VariantScope scope;
private final DeviceProvider deviceProvider;
private final TestData testData;
public ConfigAction(VariantScope scope, DeviceProvider deviceProvider, TestData testData) {
this.scope = scope;
this.deviceProvider = deviceProvider;
this.testData = testData;
}
@Override
public String getName() {
return scope.getTaskName(deviceProvider.getName());
}
@Override
public Class<DeviceProviderInstrumentTestTask> getType() {
return DeviceProviderInstrumentTestTask.class;
}
@Override
public void execute(DeviceProviderInstrumentTestTask task) {
final boolean connected = deviceProvider instanceof ConnectedDeviceProvider;
String variantName = scope.getTestedVariantData() != null ?
scope.getTestedVariantData().getName() : scope.getVariantData().getName();
if (connected) {
task.setDescription("Installs and runs the tests for " + variantName +
" on connected devices.");
} else {
task.setDescription("Installs and runs the tests for " + variantName +
" using provider: " + StringHelper.capitalize(deviceProvider.getName()));
}
task.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
task.setAndroidBuilder(scope.getGlobalScope().getAndroidBuilder());
task.setVariantName(variantName);
task.setTestData(testData);
task.setFlavorName(testData.getFlavorName());
task.setDeviceProvider(deviceProvider);
task.setInstallOptions(scope.getGlobalScope().getExtension().getAdbOptions().getInstallOptions());
task.setProcessExecutor(scope.getGlobalScope().getAndroidBuilder().getProcessExecutor());
String flavorFolder = testData.getFlavorName();
if (!flavorFolder.isEmpty()) {
flavorFolder = FD_FLAVORS + "/" + flavorFolder;
}
String providerFolder = connected ? CONNECTED : DEVICE + "/" + deviceProvider.getName();
final String subFolder = "/" + providerFolder + "/" + flavorFolder;
ConventionMappingHelper.map(task, "adbExec", new Callable<File>() {
@Override
public File call() {
final SdkInfo info = scope.getGlobalScope().getSdkHandler()
.getSdkInfo();
return (info == null ? null : info.getAdb());
}
});
ConventionMappingHelper.map(task, "splitSelectExec", new Callable<File>() {
@Override
public File call() throws Exception {
final TargetInfo info = scope.getGlobalScope().getAndroidBuilder().getTargetInfo();
String path = info == null ? null : info.getBuildTools().getPath(SPLIT_SELECT);
if (path != null) {
File splitSelectExe = new File(path);
return splitSelectExe.exists() ? splitSelectExe : null;
} else {
return null;
}
}
});
ConventionMappingHelper.map(task, "resultsDir", new Callable<File>() {
@Override
public File call() {
String rootLocation = scope.getGlobalScope().getExtension().getTestOptions().getResultsDir();
if (rootLocation == null) {
rootLocation = scope.getGlobalScope().getBuildDir() + "/" +
FD_OUTPUTS + "/" + FD_ANDROID_RESULTS;
}
return scope.getGlobalScope().getProject().file(rootLocation + subFolder);
}
});
ConventionMappingHelper.map(task, "reportsDir", new Callable<File>() {
@Override
public File call() {
String rootLocation = scope.getGlobalScope().getExtension().getTestOptions().getReportDir();
if (rootLocation == null) {
rootLocation = scope.getGlobalScope().getBuildDir() + "/" +
FD_REPORTS + "/" + FD_ANDROID_TESTS;
}
return scope.getGlobalScope().getProject().file(rootLocation + subFolder);
}
});
String rootLocation = scope.getGlobalScope().getBuildDir() + "/" +
FD_OUTPUTS + "/code-coverage";
task.setCoverageDir(scope.getGlobalScope().getProject().file(rootLocation + subFolder));
if (scope.getVariantData() instanceof TestVariantData) {
TestVariantData testVariantData = (TestVariantData) scope.getVariantData();
if (connected) {
testVariantData.connectedTestTask = task;
} else {
testVariantData.providerTestTaskList.add(task);
}
}
task.setEnabled(deviceProvider.isConfigured());
}
}
}