/* * ### * Android Maven Plugin - android-maven-plugin * * Copyright (C) 1999 - 2012 Photon Infotech Inc. * * 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. * ### */ /* * Copyright (C) 2009 Jayway AB * * 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.photon.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.photon.maven.plugins.android.asm.AndroidTestFinder; import com.photon.maven.plugins.android.common.DeviceHelper; import com.photon.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 org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; 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.TransformerFactoryConfigurationError; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathFactory; import java.io.File; import java.io.FileOutputStream; 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 java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import static com.android.ddmlib.testrunner.ITestRunListener.TestFailure.ERROR; /** * AbstractInstrumentationMojo implements running the instrumentation * tests. * */ 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: * <pre> * <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> * </pre> * * @parameter */ private Test test; /** * Enables or disables integration test related goals. If <code>true</code> they will be skipped; if <code>false</code>, * they will be run. If <code>auto</code>, they will run if any of the classes inherit from any class in * <code>junit.framework.**</code> or <code>android.test.**</code>. * * @parameter expression="${android.test.skip}" default-value="auto" */ private String testSkip; /** * Package name of the apk we wish to instrument. If not specified, it is inferred from * <code>AndroidManifest.xml</code>. * * @optional * @parameter expression="${android.test.instrumentationPackage} */ private String testInstrumentationPackage; /** * Class name of test runner. If not specified, it is inferred from <code>AndroidManifest.xml</code>. * * @optional * @parameter expression="${android.test.instrumentationRunner}" */ private String testInstrumentationRunner; /** * 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 testDebug; /** * Enable or disable code coverage for this instrumentation test * run. * * @optional * @parameter default-value=false expression="${android.test.coverage}" */ private Boolean testCoverage; /** * Enable this flag to run a log only and not execute the tests. * * @optional * @parameter default-value=false expression="${android.test.logonly}" */ private Boolean testLogOnly; /** * 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 testTestSize; /** * Create a junit xml format compatible output file containing * the test results for each device the instrumentation tests run * on. * <br /><br /> * 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. * <br /><br /> * 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. * <br /><br /> * 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 testCreateReport; /** * <p>Whether to execute tests only in given packages as part of the instrumentation tests.</p> * <pre> * <packages> * <package>your.package.name</package> * </packages> * </pre> * or as e.g. -Dandroid.test.packages=package1,package2 * * @optional * @parameter expression="${android.test.packages} */ protected List<String> testPackages; /** * <p>Whether to execute test classes which are specified as part of the instrumentation tests.</p> * <pre> * <classes> * <class>your.package.name.YourTestClass</class> * </classes> * </pre> * or as e.g. -Dandroid.test.classes=class1,class2 * * @optional * @parameter expression="${android.test.classes} */ protected List<String> testClasses; protected boolean classesExists; protected boolean packagesExists; // the parsed parameters from the plugin config or properties from command line or pom or settings private String parsedSkip; protected String parsedInstrumentationPackage; protected String parsedInstrumentationRunner; protected List<String> parsedClasses; protected List<String> parsedPackages; protected String parsedTestSize; protected Boolean parsedCoverage; protected Boolean parsedDebug; protected Boolean parsedLogOnly; protected Boolean parsedCreateReport; protected String packagesList; protected String directory = null; 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(parsedClasses.toArray(new String[parsedClasses.size()])); 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); } } }); } protected void parseConfiguration() { // we got config in pom ... lets use it, if (test != null) { if (StringUtils.isNotEmpty(test.getSkip())) { parsedSkip = test.getSkip(); } else { parsedSkip = testSkip; } if (StringUtils.isNotEmpty(test.getInstrumentationPackage())) { parsedInstrumentationPackage = test.getInstrumentationPackage(); } else { parsedInstrumentationPackage = testInstrumentationPackage; } if (StringUtils.isNotEmpty(test.getInstrumentationRunner())) { parsedInstrumentationRunner = test.getInstrumentationRunner(); } else { parsedInstrumentationRunner = testInstrumentationRunner; } if (test.getClasses() != null && !test.getClasses().isEmpty()) { parsedClasses = test.getClasses(); } else { parsedClasses = testClasses; } if (test.getPackages() != null && !test.getPackages().isEmpty()) { parsedPackages = test.getPackages(); } else { parsedPackages = testPackages; } if (StringUtils.isNotEmpty(test.getTestSize())) { parsedTestSize = test.getTestSize(); } else { parsedTestSize = testTestSize; } if (test.isCoverage() != null) { parsedCoverage= test.isCoverage(); } else { parsedCoverage = testCoverage; } if (test.isDebug() != null) { parsedDebug = test.isDebug(); } else { parsedDebug = testDebug; } if (test.isLogOnly() != null) { parsedLogOnly = test.isLogOnly(); } else { parsedLogOnly = testLogOnly; } if (test.isCreateReport() != null) { parsedCreateReport = test.isCreateReport(); } else { parsedCreateReport = testCreateReport; } } // no pom, we take properties else { parsedSkip = testSkip; parsedInstrumentationPackage = testInstrumentationPackage; parsedInstrumentationRunner = testInstrumentationRunner; parsedClasses = testClasses; parsedPackages = testPackages; parsedTestSize = testTestSize; parsedCoverage= testCoverage; parsedDebug= testDebug; parsedLogOnly = testLogOnly; parsedCreateReport = testCreateReport; } } /** * Whether or not to execute integration test related goals. Reads from configuration parameter * <code>enableIntegrationTest</code>, but can be overridden with <code>-Dmaven.test.skip</code>. * * @return <code>true</code> if integration test goals should be executed, <code>false</code> 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 (parsedSkip == null || "auto".equalsIgnoreCase(parsedSkip)) { if (extractInstrumentationRunnerFromAndroidManifest(androidManifestFile) == null) { getLog().info("No InstrumentationRunner found - skipping tests"); return false; } return AndroidTestFinder.containsAndroidTests(new File(project.getBuild() .getOutputDirectory())); } throw new MojoFailureException("android.test.skip 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<String> lines) { if(lines == null || lines.size() == 0) { return null; } List<String> strings = new ArrayList<String>(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. */ public 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/%3Cdffc72020902241548l4316d645w2e98caf5f0aac770@mail.gmail.com%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"; private static final String TAG_ALLTESTS = "alltests"; private static final String ALLTESTS_XML = "alltests.xml"; /** 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<Object, Object> systemProperty : System.getProperties().entrySet()) { propertyNode = junitReport.createElement(TAG_PROPERTY); propertyAttributes = propertyNode.getAttributes(); propNameAttr = junitReport.createAttribute(ATTR_PROPERTY_NAME); propNameAttr.setValue(systemProperty.getKey().toString()); propertyAttributes.setNamedItem(propNameAttr); propValueAttr = junitReport.createAttribute(ATTR_PROPERTY_VALUE); propValueAttr.setValue(systemProperty.getValue().toString()); propertyAttributes.setNamedItem(propValueAttr); propertiesNode.appendChild(propertyNode); } Map<String, String> deviceProperties = device.getProperties(); for (Map.Entry<String, String> deviceProperty : deviceProperties.entrySet()) { propertyNode = junitReport.createElement(TAG_PROPERTY); propertyAttributes = propertyNode.getAttributes(); propNameAttr = junitReport.createAttribute(ATTR_PROPERTY_NAME); propNameAttr.setValue(deviceProperty.getKey()); propertyAttributes.setNamedItem(propNameAttr); propValueAttr = junitReport.createAttribute(ATTR_PROPERTY_VALUE); propValueAttr.setValue(deviceProperty.getValue()); propertyAttributes.setNamedItem(propValueAttr); propertiesNode.appendChild(propertyNode); } testSuiteNode.appendChild(propertiesNode); testSuitesNode.appendChild(testSuiteNode); } catch (ParserConfigurationException e) { threwException = true; exceptionMessages.append("Failed to create document"); exceptionMessages.append(e.getMessage()); } } } public void testStarted(TestIdentifier test) { getLog().info(INDENT + INDENT +"Start: " + test.toString()); if (parsedCreateReport) { // reset start time for each test run currentTestCaseStartTime = new Date().getTime(); currentTestCaseNode = junitReport.createElement(TAG_TEST_CASE); NamedNodeMap testCaseAttributes = currentTestCaseNode.getAttributes(); Attr classAttr = junitReport.createAttribute(ATTR_TESTCASE_CLASSNAME); classAttr.setValue(test.getClassName()); testCaseAttributes.setNamedItem(classAttr); Attr methodAttr = junitReport.createAttribute(ATTR_TESTCASE_NAME); methodAttr.setValue(test.getTestName()); testCaseAttributes.setNamedItem(methodAttr); } } public void testFailed(TestFailure status, TestIdentifier test, String trace) { if (status==ERROR) { ++testErrorCount; } else { ++testFailureCount; } getLog().info(INDENT + INDENT + status.name() + ":" + test.toString()); getLog().info(INDENT + INDENT + trace); if (parsedCreateReport) { Node errorFailureNode; NamedNodeMap errorfailureAttributes; if (status == ERROR) { errorFailureNode = junitReport.createElement(TAG_ERROR); errorfailureAttributes = errorFailureNode.getAttributes(); } else { errorFailureNode = junitReport.createElement(TAG_FAILURE); errorfailureAttributes= errorFailureNode.getAttributes(); } errorFailureNode.setTextContent(trace); Attr msgAttr = junitReport.createAttribute(ATTR_MESSAGE); msgAttr.setValue(parseForMessage(trace)); errorfailureAttributes.setNamedItem(msgAttr); Attr typeAttr = junitReport.createAttribute(ATTR_TYPE); typeAttr.setValue(parseForException(trace)); errorfailureAttributes.setNamedItem(typeAttr); currentTestCaseNode.appendChild(errorFailureNode); } } public void testEnded(TestIdentifier test, Map<String, String> testMetrics) { getLog().info( INDENT + INDENT +"End: " + test.toString()); logMetrics(testMetrics); if (parsedCreateReport) { testSuiteNode.appendChild(currentTestCaseNode); NamedNodeMap testCaseAttributes = currentTestCaseNode.getAttributes(); Attr timeAttr = junitReport.createAttribute(ATTR_TESTCASE_TIME); long now = new Date().getTime(); double seconds = (now - currentTestCaseStartTime)/1000.0; timeAttr.setValue(timeFormatter.format(seconds)); testCaseAttributes.setNamedItem(timeAttr); } } public void testRunEnded(long elapsedTime, Map<String, String> runMetrics) { getLog().info(INDENT +"Run ended: " + elapsedTime + " ms"); if (hasFailuresOrErrors()) { getLog().error(INDENT + "FAILURES!!!"); } getLog().info(INDENT + "Tests run: " + testCount + ", Failures: " + testFailureCount + ", Errors: " + testErrorCount); if (parsedCreateReport) { NamedNodeMap testSuiteAttributes = testSuiteNode.getAttributes(); Attr testCountAttr = junitReport.createAttribute(ATTR_TESTSUITE_TESTS); testCountAttr.setValue(Integer.toString(testCount)); testSuiteAttributes.setNamedItem(testCountAttr); Attr testFailuresAttr = junitReport.createAttribute(ATTR_TESTSUITE_FAILURES); testFailuresAttr.setValue(Integer.toString(testFailureCount)); testSuiteAttributes.setNamedItem(testFailuresAttr); Attr testErrorsAttr = junitReport.createAttribute(ATTR_TESTSUITE_ERRORS); testErrorsAttr.setValue(Integer.toString(testErrorCount)); testSuiteAttributes.setNamedItem(testErrorsAttr); Attr timeAttr = junitReport.createAttribute(ATTR_TESTSUITE_TIME); timeAttr.setValue(timeFormatter.format(elapsedTime / 1000.0)); testSuiteAttributes.setNamedItem(timeAttr); Attr timeStampAttr = junitReport.createAttribute(ATTR_TESTSUITE_TIMESTAMP); timeStampAttr.setValue(new Date().toString()); testSuiteAttributes.setNamedItem(timeStampAttr); } logMetrics(runMetrics); if (parsedCreateReport) { writeJunitReportToFile(); } } public void testRunFailed(String errorMessage) { testRunFailureCause = errorMessage; getLog().info(INDENT +"Run failed: " + errorMessage); } public void testRunStopped(long elapsedTime) { getLog().info(INDENT +"Run stopped:" + elapsedTime); } /** * Parse a trace string for the message in it. Assumes that the message is located after ":" and before * "\r\n". * @param trace * @return message or empty string */ private String parseForMessage(String trace) { if (StringUtils.isNotBlank(trace)) { String newline = "\r\n"; // if there is message like // junit.junit.framework.AssertionFailedError ... there is no message int messageEnd = trace.indexOf(newline); boolean hasMessage = !trace.startsWith("junit.") && messageEnd > 0; if (hasMessage) { int messageStart = trace.indexOf(":") + 2; if (messageStart > messageEnd) { messageEnd = trace.indexOf(newline+"at"); // match start of stack trace "\r\nat org.junit....." } return trace.substring(messageStart, messageEnd); } else { return StringUtils.EMPTY; } } else { return StringUtils.EMPTY; } } /** * Parse a trace string for the exception class. Assumes that it is the start of the trace and ends at the first * ":". * @param trace * @return Exception class as string or empty string */ private String parseForException(String trace) { if (StringUtils.isNotBlank(trace)) { return trace.substring(0, trace.indexOf(":")); } else { return StringUtils.EMPTY; } } /** * Write the junit report xml file. */ private void writeJunitReportToFile() { TransformerFactory xfactory = TransformerFactory.newInstance(); Transformer xformer = null; try { xformer = xfactory.newTransformer(); } catch (TransformerConfigurationException e) { e.printStackTrace(); } Source source = new DOMSource(junitReport); FileWriter writer = null; try { directory = new StringBuilder() .append(project.getBuild().getDirectory()) .append("/surefire-reports") .toString(); FileUtils.forceMkdir(new File(directory)); String fileName = new StringBuilder() .append(directory) .append("/TEST-") .append(DeviceHelper.getDescriptiveName(device)) .append(".xml") .toString(); File reportFile = new File(fileName); writer = new FileWriter(reportFile); Result result = new StreamResult(writer); xformer.transform(source, result); getLog().info("Report file written to " + reportFile.getAbsolutePath()); } catch (IOException e) { threwException = true; exceptionMessages.append("Failed to write test report file"); exceptionMessages.append(e.getMessage()); } catch (TransformerException e) { threwException = true; exceptionMessages.append("Failed to transform document to write to test report file"); exceptionMessages.append(e.getMessage()); } finally { IOUtils.closeQuietly(writer); } } /** * Log all the metrics out in to key: value lines. * @param metrics */ private void logMetrics(Map<String, String> metrics) { for (Map.Entry<String, String> entry : metrics.entrySet()) { getLog().info(INDENT + INDENT + entry.getKey() + ": " + entry.getValue()); } } /** * @return if any failures or errors occurred in the test run. */ public boolean hasFailuresOrErrors() { return testErrorCount > 0 || testFailureCount > 0; } /** * @return if the test run itself failed - a failure in the test infrastructure, not a test failure. */ public boolean testRunFailed() { return testRunFailureCause != null; } public String getTestRunFailureCause() { return testRunFailureCause; } /** * @return if any exception was thrown during the test run * on the build system (not the Android device or emulator) */ public boolean threwException() { return threwException; } /** * @return all exception messages thrown during test execution * on the test run time (not the Android device or emulator) */ public String getExceptionMessages() { return exceptionMessages.toString(); } /************************ METHODS WRITTEN BY DHIMAN TO CREATE PERFORMANCE TEST REPORT XML FILE (START) *******************/ private Document document; private static final String TAG_TEST_RESULTS = "testResults"; private static final String TAG_SUITE = "testsuite"; private static final String TAG_DEVICE_INFO = "deviceInfo"; private static final String TAG_HOST_NAME = "hostname"; private static final String TAG_TIMESTAMP = "timestamp"; private static final String TAG_TEST_CASE = "testcase"; private static final String TAG_ANDROID_SAMPLE = "androidSample"; private static final String TAG_ID = "id"; private static final String TAG_NAME = "name"; private static final String TAG_BY = "by"; private static final String TAG_LB = "lb"; private static final String TAG_RC = "rc"; private static final String TAG_T = "t"; private static final String TAG_TIME = "time"; private static final String TAG_TN = "tn"; private static final String TAG_TS = "ts"; private static final String TAG_RM = "rm"; private static final String TAG_S = "s"; /** * Write the junit report to alltests.xml file. */ public void writeJunitReportToAllTestsFile(ArrayList<String> xmlReportNameForConnectedDevices) { try { File directoryFile = new File(directory); ArrayList<File> xmlReportForConnectedDevice = getXMLReportForConnectedDevice(directoryFile, xmlReportNameForConnectedDevices); generatePerformanceXMLReport(xmlReportForConnectedDevice); } catch (Exception e) { exceptionMessages.append("Failed to generated " + ALLTESTS_XML); exceptionMessages.append(e.getMessage()); } } /** * Filters the connected device files from all generated report files */ private ArrayList<File> getXMLReportForConnectedDevice(File directoryFile, ArrayList<String> xmlReportNameForConnectedDevices) { ArrayList<File> xmlReportForConnectedDevice = new ArrayList<File>(); try { File[] files = directoryFile.listFiles(); // Adding all files to arraylist for (int i = 0; i < files.length; i++) { xmlReportForConnectedDevice.add(files[i]); } for (int i = xmlReportForConnectedDevice.size() - 1; i >= 0; i--) { if (xmlReportNameForConnectedDevices.contains(xmlReportForConnectedDevice.get(i).getName())) { //getLog().info(xmlReportForConnectedDevice.get(i).getName() + " not removed........."); } else { //getLog().info(xmlReportForConnectedDevice.get(i).getName() + " removed........."); xmlReportForConnectedDevice.remove(i); } } return xmlReportForConnectedDevice; } catch (Exception e) { return null; } } /* Create all test file. */ private void generatePerformanceXMLReport(ArrayList<File> xmlReportForConnectedDevice) { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { DocumentBuilder builder = factory.newDocumentBuilder(); document = builder.newDocument(); } catch (ParserConfigurationException parserException) { parserException.printStackTrace(); } Element root = document.createElement(TAG_TEST_RESULTS); document.appendChild(root); //File directoryFile = new File("xml"); //File[] files = directoryFile.listFiles(); for (File file : xmlReportForConnectedDevice) { createChildNodes(file, root); } try { File allTestXML = new File(directory, ALLTESTS_XML); Source xmlSource = new DOMSource(document); Result result = new StreamResult(new FileOutputStream(allTestXML)); TransformerFactory transformerFactory = TransformerFactory .newInstance(); Transformer transformer = transformerFactory.newTransformer(); transformer.setOutputProperty("indent", "yes"); transformer.transform(xmlSource, result); } catch (TransformerFactoryConfigurationError factoryError) { System.err.println("Error creating " + "TransformerFactory"); factoryError.printStackTrace(); } catch (TransformerException transformerError) { System.err.println("Error transforming document"); transformerError.printStackTrace(); } catch (IOException ioException) { ioException.printStackTrace(); } deleteAllExceptAllTestFile(); } /* Delete all the files except alltest.xml */ public void deleteAllExceptAllTestFile() { File directoryFile = new File(directory); File[] files = directoryFile.listFiles(); for (File file : files) { if (!ALLTESTS_XML.equals(file.getName())) { file.delete(); } } } /* Create each node from each connected devices and add them to alltest file. */ private void createChildNodes(File file, Element root) { Document parseDocument = null; try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); parseDocument = db.parse(file); } catch (Exception e) { return; } try { NodeList testsuiteNodeList = parseDocument.getElementsByTagName(TAG_SUITE); for (int i = 0; i < testsuiteNodeList.getLength(); i++) { Element deviceInfo = document.createElement(TAG_DEVICE_INFO); Node testsuite = testsuiteNodeList.item(i); String deviceName = testsuite.getAttributes().getNamedItem(TAG_HOST_NAME).getNodeValue(); String timestamp = testsuite.getAttributes().getNamedItem(TAG_TIMESTAMP).getNodeValue(); setDeviceInfo(deviceName, deviceInfo); Element eachTest = null; NodeList testCaseNodeList = testsuite.getChildNodes(); for (int j = 0; j < testCaseNodeList.getLength(); j++) { Node testCaseNode = testCaseNodeList.item(j); if (TAG_TESTCASE.equals(testCaseNode.getNodeName())) { eachTest = document.createElement(TAG_ANDROID_SAMPLE); setAndroidSampleElement(deviceName, timestamp, testCaseNode, eachTest); deviceInfo.appendChild(eachTest); } } root.appendChild(deviceInfo); } } catch (Exception e) { } } /* Sets the device info and add them to alltest file. */ private void setDeviceInfo(final String deviceName, Element element) { try { String arr[] = deviceName.split("_"); Attr attr = document.createAttribute(TAG_ID); attr.setValue(arr[0]); element.setAttributeNode(attr); attr = document.createAttribute(TAG_NAME); String name = ""; for (int i = 1; i < arr.length; i++) { name += arr[i]+"_"; } attr.setValue(name.substring(0, name.length() - 1)); element.setAttributeNode(attr); } catch (Exception e) { } } /* Sets AndroidSample tag and add them to alltest file. */ private void setAndroidSampleElement(final String deviceName, final String timestamp, final Node testCaseNode, Element element) { try { Attr attr = document.createAttribute(TAG_BY); attr.setValue("0"); element.setAttributeNode(attr); attr = document.createAttribute(TAG_LB); attr.setValue(testCaseNode.getAttributes().getNamedItem(TAG_NAME).getNodeValue()); element.setAttributeNode(attr); attr = document.createAttribute(TAG_RC); attr.setValue("0"); element.setAttributeNode(attr); setResponseMessageAndS(testCaseNode, element); attr = document.createAttribute(TAG_T); attr.setValue(getTestingTime(testCaseNode.getAttributes().getNamedItem(TAG_TIME).getNodeValue())); element.setAttributeNode(attr); attr = document.createAttribute(TAG_TN); String arr[] = deviceName.split("_"); attr.setValue(arr[0]); element.setAttributeNode(attr); attr = document.createAttribute(TAG_TS); attr.setValue(getDateStringToMilis(timestamp)); element.setAttributeNode(attr); } catch (Exception e) { } } /* Converts test running time duration from seconds to mili seconds */ private String getTestingTime(final String time) { double milis = 0; try { milis = 1000 * Double.parseDouble(time.trim()); } catch (NumberFormatException nfe) { milis = 0; } return String.valueOf((int) milis); } /* Converts test running time from date string to mili seconds */ private String getDateStringToMilis(final String dateString) { long milis = 0; try { DateFormat formatter = DateFormat.getDateInstance(); Date date; formatter = new SimpleDateFormat("E MMM dd HH:mm:ss z yyyy"); date = (Date) formatter.parse("Fri May 18 12:51:58 IST 2012"); milis = date.getTime(); } catch (ParseException e) { System.out.println("Exception :" + e); } return String.valueOf(milis); } /* Sets rm and s attribute and add them to alltest file. */ private void setResponseMessageAndS(final Node testCaseNode, final Element element) { try { boolean errorPresent = true; String rm = ""; NodeList nodeList = testCaseNode.getChildNodes(); for (int i = 0; i < nodeList.getLength(); i++) { errorPresent = true; Node node = nodeList.item(i); if ("error".equals(node.getNodeName())) { errorPresent = false; rm = node.getTextContent().trim(); break; } } Attr attr = document.createAttribute(TAG_RM); attr.setValue(rm); element.setAttributeNode(attr); attr = document.createAttribute(TAG_S); attr.setValue(""+errorPresent); element.setAttributeNode(attr); } catch (Exception e) { } } /************************ METHODS WRITTEN BY DHIMAN TO CREATE PERFORMANCE TEST REPORT XML FILE (END) *******************/ } }