com.jayway.maven.plugins.android.AbstractInstrumentationMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of android-maven-plugin Show documentation
Show all versions of android-maven-plugin Show documentation
Maven Plugin for Android Development
/*
* Copyright (C) 2009-2011 Jayway AB
* Copyright (C) 2007-2008 JVending Masa
*
* 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.jayway.maven.plugins.android;
import com.android.ddmlib.AdbCommandRejectedException;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.ShellCommandUnresponsiveException;
import com.android.ddmlib.TimeoutException;
import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner;
import com.android.ddmlib.testrunner.ITestRunListener;
import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
import com.android.ddmlib.testrunner.TestIdentifier;
import com.jayway.maven.plugins.android.asm.AndroidTestFinder;
import com.jayway.maven.plugins.android.common.DeviceHelper;
import com.jayway.maven.plugins.android.configuration.Test;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import static com.android.ddmlib.testrunner.ITestRunListener.TestFailure.ERROR;
/**
* AbstractInstrumentationMojo implements running the instrumentation
* tests.
*
* @author [email protected]
* @author Manfred Moser
*/
public abstract class AbstractInstrumentationMojo extends AbstractAndroidMojo {
/**
* -Dmaven.test.skip is commonly used with Maven to skip tests. We honor it too.
*
* @parameter expression="${maven.test.skip}" default-value=false
* @readonly
*/
private boolean mavenTestSkip;
/**
* -DskipTests is commonly used with Maven to skip tests. We honor it too.
*
* @parameter expression="${skipTests}" default-value=false
* @readonly
*/
private boolean mavenSkipTests;
/**
* The configuration to use for running instrumentation tests. Complete configuration
* is possible in the plugin configuration:
*
* <test>
* <skip>true|false|auto</skip>
* <instrumentationPackage>packageName</instrumentationPackage>
* <instrumentationRunner>className</instrumentationRunner>
* <debug>true|false</debug>
* <coverage>true|false</coverage>
* <logonly>true|false</logonly> avd
* <testsize>small|medium|large</testsize>
* <createreport>true|false</createreport>
* <classes>
* <class>your.package.name.YourTestClass</class>
* </classes>
* <packages>
* <package>your.package.name</package>
* </packages>
* </test>
*
*
* @parameter
*/
private Test test;
/**
* Enables or disables integration test related goals. If true
they will be run; if false
,
* they will be skipped. If auto
, they will run if any of the classes inherit from any class in
* junit.framework.**
or android.test.**
.
*
* @parameter expression="${android.test.skip}" default-value="auto"
*/
private String skip;
/**
* Package name of the apk we wish to instrument. If not specified, it is inferred from
* AndroidManifest.xml
.
*
* @optional
* @parameter expression="${android.test.instrumentationPackage}
*/
private String instrumentationPackage;
/**
* Class name of test runner. If not specified, it is inferred from AndroidManifest.xml
.
*
* @optional
* @parameter expression="${android.test.instrumentationRunner}"
*/
private String instrumentationRunner;
/**
* Enable debug causing the test runner to wait until debugger is
* connected with the Android debug bridge (adb).
*
* @optional
* @parameter default-value=false expression="${android.test.debug}"
*/
private boolean debug;
/**
* Enable or disable code coverage for this instrumentation test
* run.
*
* @optional
* @parameter default-value=false expression="${android.test.coverage}"
*/
private boolean coverage;
/**
* Enable this flag to run a log only and not execute the tests.
*
* @optional
* @parameter default-value=false expression="${android.test.logonly}"
*/
private boolean logOnly;
/**
* If specified only execute tests of certain size as defined by
* the Android instrumentation testing SmallTest, MediumTest and
* LargeTest annotations. Use "small", "medium" or "large" as values.
*
* @see com.android.ddmlib.testrunner.IRemoteAndroidTestRunner
*
* @optional
* @parameter expression="${android.test.testsize}"
*/
private String testSize;
/**
* Create a junit xml format compatible output file containing
* the test results for each device the instrumentation tests run
* on.
*
* The files are stored in target/surefire-reports and named TEST-deviceid.xml.
* The deviceid for an emulator is deviceSerialNumber_avdName_manufacturer_model.
* The serial number is commonly emulator-5554 for the first emulator started
* with numbers increasing. avdName is as defined in the SDK tool. The
* manufacturer is typically "unknown" and the model is typically "sdk".
* The deviceid for an actual devices is
* deviceSerialNumber_manufacturer_model.
*
* The file contains system properties from the system running
* the Android Maven Plugin (JVM) and device properties from the
* device/emulator the tests are running on.
*
* The file contains a single TestSuite for all tests and a
* TestCase for each test method. Errors and failures are logged
* in the file and the system log with full stack traces and other
* details available.
*
* @optional
* @parameter default-value=true expression="${android.test.createreport}"
*/
private boolean createReport;
/**
* Whether to execute tests only in given packages
*
* <packages>
* <package>your.package.name</package>
* </packages>
*
*
* @parameter
*/
protected List packages;
/**
* Whether to execute test classes which are specified.
*
* <classes>
* <class>your.package.name.YourTestClass</class>
* </classes>
*
*
* @parameter
*/
protected List classes;
private boolean classesExists;
private boolean packagesExists;
// the parsed parameters from the plugin config or properties from command line or pom or settings
private String parsedSkip;
private String parsedInstrumentationPackage;
private String parsedInstrumentationRunner;
private List parsedClasses;
private List parsedPackages;
private String parsedTestSize;
private boolean parsedCoverage;
private boolean parsedDebug;
private boolean parsedLogOnly;
private boolean parsedCreateReport;
private String packagesList;
protected void instrument() throws MojoExecutionException, MojoFailureException {
parseConfiguration();
if (parsedInstrumentationPackage == null) {
parsedInstrumentationPackage = extractPackageNameFromAndroidManifest(androidManifestFile);
}
if (parsedInstrumentationRunner == null) {
parsedInstrumentationRunner = extractInstrumentationRunnerFromAndroidManifest(androidManifestFile);
}
// only run Tests in specific package
packagesList = buildCommaSeparatedString(parsedPackages);
packagesExists = StringUtils.isNotBlank(packagesList);
if (parsedClasses != null) {
classesExists = parsedClasses.size() > 0;
} else {
classesExists = false;
}
if(classesExists && packagesExists) {
// if both packages and classes are specified --> ERROR
throw new MojoFailureException("packages and classes are mutually exclusive. They cannot be specified at " +
"the same time. Please specify either packages or classes. For details, " +
"see http://developer.android.com/guide/developing/testing/testing_otheride.html");
}
doWithDevices(new DeviceCallback() {
public void doWithDevice(final IDevice device) throws MojoExecutionException, MojoFailureException {
RemoteAndroidTestRunner remoteAndroidTestRunner =
new RemoteAndroidTestRunner(parsedInstrumentationPackage, parsedInstrumentationRunner, device);
if(packagesExists) {
remoteAndroidTestRunner.setTestPackageName(packagesList);
getLog().info("Running tests for specified test packages: " + packagesList);
}
if(classesExists) {
remoteAndroidTestRunner.setClassNames((String[]) parsedClasses.toArray());
getLog().info("Running tests for specified test classes/methods: " + parsedClasses);
}
remoteAndroidTestRunner.setDebug(parsedDebug);
remoteAndroidTestRunner.setCoverage(parsedCoverage);
remoteAndroidTestRunner.setLogOnly(parsedLogOnly);
if (StringUtils.isNotBlank(parsedTestSize)) {
IRemoteAndroidTestRunner.TestSize validSize =
IRemoteAndroidTestRunner.TestSize.getTestSize(parsedTestSize);
remoteAndroidTestRunner.setTestSize(validSize);
}
getLog().info("Running instrumentation tests in " + parsedInstrumentationPackage + " on " +
device.getSerialNumber() + " (avdName=" + device.getAvdName() + ")");
try {
AndroidTestRunListener testRunListener = new AndroidTestRunListener(project, device);
remoteAndroidTestRunner.run(testRunListener);
if (testRunListener.hasFailuresOrErrors()) {
throw new MojoFailureException("Tests failed on device.");
}
if (testRunListener.testRunFailed()) {
throw new MojoFailureException("Test run failed to complete: "+testRunListener.getTestRunFailureCause());
}
if (testRunListener.threwException()) {
throw new MojoFailureException(testRunListener.getExceptionMessages());
}
} catch (TimeoutException e) {
throw new MojoExecutionException("timeout", e);
} catch (AdbCommandRejectedException e) {
throw new MojoExecutionException("adb command rejected", e);
} catch (ShellCommandUnresponsiveException e) {
throw new MojoExecutionException("shell command " +
"unresponsive", e);
} catch (IOException e) {
throw new MojoExecutionException("IO problem", e);
}
}
});
}
private void parseConfiguration() {
// we got config in pom ... lets use it,
if (test != null) {
parsedSkip = test.getSkip();
parsedInstrumentationPackage = test.getInstrumentationPackage();
parsedInstrumentationRunner = test.getInstrumentationRunner();
parsedClasses = test.getClasses();
parsedPackages = test.getPackages();
parsedTestSize = test.getTestSize();
parsedCoverage= test.isCoverage();
parsedDebug= test.isDebug();
parsedLogOnly = test.isLogOnly();
parsedCreateReport = test.isCreateReport();
}
// no pom, we take properties
else {
parsedSkip = skip;
parsedInstrumentationPackage = instrumentationPackage;
parsedInstrumentationRunner = instrumentationRunner;
parsedClasses = classes;
parsedPackages = packages;
parsedTestSize = testSize;
parsedCoverage= coverage;
parsedDebug= debug;
parsedLogOnly = logOnly;
parsedCreateReport = createReport;
}
}
/**
* Whether or not to execute integration test related goals. Reads from configuration parameter
* enableIntegrationTest
, but can be overridden with -Dmaven.test.skip
.
*
* @return true
if integration test goals should be executed, false
otherwise.
*/
protected boolean isEnableIntegrationTest() throws MojoFailureException, MojoExecutionException {
parseConfiguration();
if (mavenTestSkip) {
getLog().info("maven.test.skip set - skipping tests");
return false;
}
if (mavenSkipTests) {
getLog().info("maven.skip.tests set - skipping tests");
return false;
}
if ("true".equalsIgnoreCase(parsedSkip)) {
getLog().info("android.test.skip set - skipping tests");
return false;
}
if ("false".equalsIgnoreCase(parsedSkip)) {
return true;
}
if ("auto".equalsIgnoreCase(parsedSkip)) {
if (extractInstrumentationRunnerFromAndroidManifest(androidManifestFile) == null) {
getLog().info("No InstrumentetationRunner found - skipping tests");
return false;
}
return AndroidTestFinder.containsAndroidTests(new File(project.getBuild().getDirectory(), "android-classes"));
}
throw new MojoFailureException("enableIntegrationTest must be configured as 'true', 'false' or 'auto'.");
}
/**
* Helper method to build a comma separated string from a list.
* Blank strings are filtered out
*
* @param lines A list of strings
* @return Comma separated String from given list
*/
protected static String buildCommaSeparatedString(List lines) {
if(lines == null || lines.size() == 0) {
return null;
}
List strings = new ArrayList(lines.size());
for(String str : lines) { // filter out blank strings
if(StringUtils.isNotBlank(str)) {
strings.add(StringUtils.trimToEmpty(str));
}
}
return StringUtils.join(strings, ",");
}
/**
* AndroidTestRunListener produces a nice output for the log for the test
* run as well as an xml file compatible with the junit xml report file
* format understood by many tools.
*
* It will do so for each device/emulator the tests run on.
*/
private class AndroidTestRunListener implements ITestRunListener {
/** the indent used in the log to group items that belong together visually **/
private static final String INDENT = " ";
/**
* Junit report schema documentation is sparse. Here are some hints
* @see "http://mail-archives.apache.org/mod_mbox/ant-dev/200902.mbox/%[email protected]%3E"
* @see "http://junitpdfreport.sourceforge.net/managedcontent/PdfTranslation"
*/
private static final String TAG_TESTSUITES = "testsuites";
private static final String TAG_TESTSUITE = "testsuite";
private static final String ATTR_TESTSUITE_ERRORS = "errors";
private static final String ATTR_TESTSUITE_FAILURES = "failures";
private static final String ATTR_TESTSUITE_HOSTNAME = "hostname";
private static final String ATTR_TESTSUITE_NAME = "name";
private static final String ATTR_TESTSUITE_TESTS = "tests";
private static final String ATTR_TESTSUITE_TIME = "time";
private static final String ATTR_TESTSUITE_TIMESTAMP = "timestamp";
private static final String TAG_PROPERTIES = "properties";
private static final String TAG_PROPERTY = "property";
private static final String ATTR_PROPERTY_NAME = "name";
private static final String ATTR_PROPERTY_VALUE = "value";
private static final String TAG_TESTCASE = "testcase";
private static final String ATTR_TESTCASE_NAME = "name";
private static final String ATTR_TESTCASE_CLASSNAME = "classname";
private static final String ATTR_TESTCASE_TIME = "time";
private static final String TAG_ERROR = "error";
private static final String TAG_FAILURE = "failure";
private static final String ATTR_MESSAGE = "message";
private static final String ATTR_TYPE = "type";
/** time format for the output of milliseconds in seconds in the xml file **/
private final NumberFormat timeFormatter = new DecimalFormat("#0.0000");
private int testCount = 0;
private int testFailureCount = 0;
private int testErrorCount = 0;
private String testRunFailureCause = null;
private final MavenProject project;
/** the emulator or device we are running the tests on **/
private final IDevice device;
// junit xml report related fields
private Document junitReport;
private Node testSuiteNode;
/** node for the current test case for junit report */
private Node currentTestCaseNode;
/** start time of current test case in millis, reset with each test start */
private long currentTestCaseStartTime;
// we track if we have problems and then report upstream
private boolean threwException = false;
private final StringBuilder exceptionMessages = new StringBuilder();
public AndroidTestRunListener(MavenProject project, IDevice device) {
this.project = project;
this.device = device;
}
public void testRunStarted(String runName, int testCount) {
this.testCount = testCount;
getLog().info(INDENT + "Run started: " + runName + ", " + testCount + " tests:");
if (parsedCreateReport) {
try {
DocumentBuilderFactory fact = DocumentBuilderFactory.newInstance();
DocumentBuilder parser = null;
parser = fact.newDocumentBuilder();
junitReport = parser.newDocument();
Node testSuitesNode = junitReport.createElement(TAG_TESTSUITES);
junitReport.appendChild(testSuitesNode);
testSuiteNode = junitReport.createElement(TAG_TESTSUITE);
NamedNodeMap testSuiteAttributes = testSuiteNode.getAttributes();
Attr nameAttr = junitReport.createAttribute(ATTR_TESTSUITE_NAME);
nameAttr.setValue(runName);
testSuiteAttributes.setNamedItem(nameAttr);
Attr hostnameAttr = junitReport.createAttribute(ATTR_TESTSUITE_HOSTNAME);
hostnameAttr.setValue(DeviceHelper.getDescriptiveName(device));
testSuiteAttributes.setNamedItem(hostnameAttr);
Node propertiesNode = junitReport.createElement(TAG_PROPERTIES);
Node propertyNode;
NamedNodeMap propertyAttributes;
Attr propNameAttr;
Attr propValueAttr;
for (Map.Entry
© 2015 - 2024 Weber Informatics LLC | Privacy Policy