1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-11-27 11:42:16 -05:00

Replaced android.test.InstrumentationTestRunner with com.zutubi.android.junitreport.JUnitReportTestRunner.

Create javadoc/ directory when doing "ant javadoc". Fixed installing debug builds with ant.

"ant -f tests/build.xml debug && ant -f tests/build.xml installt test test-report" compiles, installs, tests,
and saves output to tests/junit-report.xml.
This commit is contained in:
ashley willis 2012-05-22 22:16:24 -05:00
parent cb357a0de4
commit 2ffc18f224
6 changed files with 467 additions and 7 deletions

View File

@ -162,19 +162,19 @@
<!-- Install the package on the default emulator --> <!-- Install the package on the default emulator -->
<target name="install" depends="debug"> <target name="install" depends="debug">
<echo>Installing ${out.debug.file} onto default emulator...</echo> <echo>Installing ${out.final.file} onto default emulator...</echo>
<exec executable="${adb}" failonerror="true"> <exec executable="${adb}" failonerror="true">
<arg value="install" /> <arg value="install" />
<arg path="${out.debug.file}" /> <arg path="${out.final.file}" />
</exec> </exec>
</target> </target>
<!-- Re-Install the package on the default emulator --> <!-- Re-Install the package on the default emulator -->
<target name="reinstall" depends="debug"> <target name="reinstall" depends="debug">
<echo>Reinstalling ${out.debug.file} onto default emulator...</echo> <echo>Reinstalling ${out.final.file} onto default emulator...</echo>
<exec executable="${adb}" failonerror="true"> <exec executable="${adb}" failonerror="true">
<arg value="install" /> <arg value="install" />
<arg value="-r" /> <arg value="-r" />
<arg path="${out.debug.file}" /> <arg path="${out.final.file}" />
</exec> </exec>
</target> </target>
<target name="astyle"> <target name="astyle">
@ -214,6 +214,7 @@
<property environment="env" /> <property environment="env" />
<target name="javadoc" description="build javadoc"> <target name="javadoc" description="build javadoc">
<mkdir dir="javadoc"/>
<javadoc <javadoc
destdir="javadoc" destdir="javadoc"
doctitle="K-9 Mail" doctitle="K-9 Mail"

View File

@ -15,7 +15,7 @@
the package of com.fsck.k9. To run the tests use the command: the package of com.fsck.k9. To run the tests use the command:
"adb shell am instrument -w com.fsck.k9.tests/android.test.InstrumentationTestRunner" "adb shell am instrument -w com.fsck.k9.tests/android.test.InstrumentationTestRunner"
--> -->
<instrumentation android:name="android.test.InstrumentationTestRunner" <instrumentation android:name="com.zutubi.android.junitreport.JUnitReportTestRunner"
android:targetPackage="com.fsck.k9" android:targetPackage="com.fsck.k9"
android:label="Tests for com.fsck.k9"/> android:label="Tests for com.fsck.k9"/>
</manifest> </manifest>

View File

@ -16,3 +16,4 @@
# The password will be asked during the build when you use the 'release' target. # The password will be asked during the build when you use the 'release' target.
tested.project.dir=../ tested.project.dir=../
test.runner=com.zutubi.android.junitreport.JUnitReportTestRunner

View File

@ -79,7 +79,21 @@
In all cases you must update the value of version-tag below to read 'custom' instead of an integer, In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
in order to avoid having your file be overridden by tools such as "android update project" in order to avoid having your file be overridden by tools such as "android update project"
--> -->
<!-- version-tag: 1 --> <!-- version-tag: custom -->
<import file="${sdk.dir}/tools/ant/build.xml" />
<target name="test-report">
<xpath input="${tested.project.dir}/AndroidManifest.xml"
expression="/manifest/@package" output="tested.manifest.package" />
<xpath input="AndroidManifest.xml"
expression="/manifest/@package" output="manifest.package" />
<echo>Downloading XML test report...</echo>
<exec executable="${adb}" failonerror="true">
<arg line="${adb.device.arg}"/>
<arg value="pull" />
<arg value="/data/data/${tested.manifest.package}/files/junit-report.xml" />
<arg value="junit-report.xml" />
</exec>
</target>
<import file="${sdk.dir}/tools/ant/build.xml" />
</project> </project>

View File

@ -0,0 +1,296 @@
/*
* Copyright (C) 2010-2011 Zutubi Pty Ltd
*
* 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.zutubi.android.junitreport;
import android.content.Context;
import android.util.Log;
import android.util.Xml;
import junit.framework.AssertionFailedError;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestListener;
import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Locale;
/**
* Custom test listener that outputs test results to XML files. The files
* use a similar format to the Ant JUnit task XML formatter, with a few of
* caveats:
* <ul>
* <li>
* By default, multiple suites are all placed in a single file under a root
* &lt;testsuites&gt; element. In multiFile mode a separate file is
* created for each suite, which may be more compatible with existing
* tools.
* </li>
* <li>
* Redundant information about the number of nested cases within a suite is
* omitted.
* </li>
* <li>
* Durations are omitted from suites.
* </li>
* <li>
* Neither standard output nor system properties are included.
* </li>
* </ul>
* The differences mainly revolve around making this reporting as lightweight as
* possible. The report is streamed as the tests run, making it impossible to,
* e.g. include the case count in a &lt;testsuite&gt; element.
*/
public class JUnitReportListener implements TestListener {
private static final String LOG_TAG = "JUnitReportListener";
private static final String ENCODING_UTF_8 = "utf-8";
private static final String TAG_SUITES = "testsuites";
private static final String TAG_SUITE = "testsuite";
private static final String TAG_CASE = "testcase";
private static final String TAG_ERROR = "error";
private static final String TAG_FAILURE = "failure";
private static final String ATTRIBUTE_NAME = "name";
private static final String ATTRIBUTE_CLASS = "classname";
private static final String ATTRIBUTE_TYPE = "type";
private static final String ATTRIBUTE_MESSAGE = "message";
private static final String ATTRIBUTE_TIME = "time";
// With thanks to org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner.
// Trimmed some entries, added others for Android.
private static final String[] DEFAULT_TRACE_FILTERS = new String[] {
"junit.framework.TestCase", "junit.framework.TestResult",
"junit.framework.TestSuite",
"junit.framework.Assert.", // don't filter AssertionFailure
"java.lang.reflect.Method.invoke(", "sun.reflect.",
// JUnit 4 support:
"org.junit.", "junit.framework.JUnit4TestAdapter", " more",
// Added for Android
"android.test.", "android.app.Instrumentation",
"java.lang.reflect.Method.invokeNative",
};
private Context mContext;
private Context mTargetContext;
private String mReportFile;
private String mReportDir;
private boolean mFilterTraces;
private boolean mMultiFile;
private FileOutputStream mOutputStream;
private XmlSerializer mSerializer;
private String mCurrentSuite;
// simple time tracking
private boolean mTimeAlreadyWritten = false;
private long mTestStartTime;
/**
* Creates a new listener.
*
* @param context context of the test application
* @param targetContext context of the application under test
* @param reportFile name of the report file(s) to create
* @param reportDir path of the directory under which to write files
* (may be null in which case files are written under
* the context using {@link Context#openFileOutput(String, int)}).
* @param filterTraces if true, stack traces will have common noise (e.g.
* framework methods) omitted for clarity
* @param multiFile if true, use a separate file for each test suite
*/
public JUnitReportListener(Context context, Context targetContext, String reportFile, String reportDir, boolean filterTraces, boolean multiFile) {
this.mContext = context;
this.mTargetContext = targetContext;
this.mReportFile = reportFile;
this.mReportDir = reportDir;
this.mFilterTraces = filterTraces;
this.mMultiFile = multiFile;
}
@Override
public void startTest(Test test) {
try {
if (test instanceof TestCase) {
TestCase testCase = (TestCase) test;
checkForNewSuite(testCase);
mSerializer.startTag("", TAG_CASE);
mSerializer.attribute("", ATTRIBUTE_CLASS, mCurrentSuite);
mSerializer.attribute("", ATTRIBUTE_NAME, testCase.getName());
mTimeAlreadyWritten = false;
mTestStartTime = System.currentTimeMillis();
}
} catch (IOException e) {
Log.e(LOG_TAG, safeMessage(e));
}
}
private void checkForNewSuite(TestCase testCase) throws IOException {
String suiteName = testCase.getClass().getName();
if (mCurrentSuite == null || !mCurrentSuite.equals(suiteName)) {
if (mCurrentSuite != null) {
if (mMultiFile) {
close();
} else {
mSerializer.endTag("", TAG_SUITE);
}
}
openIfRequired(suiteName);
mSerializer.startTag("", TAG_SUITE);
mSerializer.attribute("", ATTRIBUTE_NAME, suiteName);
mCurrentSuite = suiteName;
}
}
private void openIfRequired(String suiteName) throws IOException {
if (mSerializer == null) {
String fileName = mReportFile;
if (mMultiFile) {
fileName = fileName.replace("$(suite)", suiteName);
}
if (mReportDir == null) {
if (mContext.getFilesDir() != null) {
mOutputStream = mContext.openFileOutput(fileName, 0);
} else {
mOutputStream = mTargetContext.openFileOutput(fileName, 0);
}
} else {
mOutputStream = new FileOutputStream(new File(mReportDir, fileName));
}
mSerializer = Xml.newSerializer();
mSerializer.setOutput(mOutputStream, ENCODING_UTF_8);
mSerializer.startDocument(ENCODING_UTF_8, true);
if (!mMultiFile) {
mSerializer.startTag("", TAG_SUITES);
}
}
}
@Override
public void addError(Test test, Throwable error) {
addProblem(TAG_ERROR, error);
}
@Override
public void addFailure(Test test, AssertionFailedError error) {
addProblem(TAG_FAILURE, error);
}
private void addProblem(String tag, Throwable error) {
try {
recordTestTime();
mSerializer.startTag("", tag);
mSerializer.attribute("", ATTRIBUTE_MESSAGE, safeMessage(error));
mSerializer.attribute("", ATTRIBUTE_TYPE, error.getClass().getName());
StringWriter w = new StringWriter();
error.printStackTrace(mFilterTraces ? new FilteringWriter(w) : new PrintWriter(w));
mSerializer.text(w.toString());
mSerializer.endTag("", tag);
} catch (IOException e) {
Log.e(LOG_TAG, safeMessage(e));
}
}
private void recordTestTime() throws IOException {
if (!mTimeAlreadyWritten) {
mTimeAlreadyWritten = true;
mSerializer.attribute("", ATTRIBUTE_TIME, String.format(Locale.ENGLISH, "%.3f",
(System.currentTimeMillis() - mTestStartTime) / 1000.));
}
}
@Override
public void endTest(Test test) {
try {
if (test instanceof TestCase) {
recordTestTime();
mSerializer.endTag("", TAG_CASE);
}
} catch (IOException e) {
Log.e(LOG_TAG, safeMessage(e));
}
}
/**
* Releases all resources associated with this listener. Must be called
* when the listener is finished with.
*/
public void close() {
if (mSerializer != null) {
try {
if (mCurrentSuite != null) {
mSerializer.endTag("", TAG_SUITE);
}
if (!mMultiFile) {
mSerializer.endTag("", TAG_SUITES);
}
mSerializer.endDocument();
mSerializer = null;
} catch (IOException e) {
Log.e(LOG_TAG, safeMessage(e));
}
}
if (mOutputStream != null) {
try {
mOutputStream.close();
mOutputStream = null;
} catch (IOException e) {
Log.e(LOG_TAG, safeMessage(e));
}
}
}
private String safeMessage(Throwable error) {
String message = error.getMessage();
return error.getClass().getName() + ": " + (message == null ? "<null>" : message);
}
/**
* Wrapper around a print writer that filters out common noise from stack
* traces, making it easier to see the actual failure.
*/
private static class FilteringWriter extends PrintWriter {
public FilteringWriter(Writer out) {
super(out);
}
@Override
public void println(String s) {
for (String filtered : DEFAULT_TRACE_FILTERS) {
if (s.contains(filtered)) {
return;
}
}
super.println(s);
}
}
}

View File

@ -0,0 +1,148 @@
/*
* Copyright (C) 2010-2011 Zutubi Pty Ltd
*
* 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.zutubi.android.junitreport;
import android.os.Bundle;
import android.test.AndroidTestRunner;
import android.test.InstrumentationTestRunner;
/**
* Custom test runner that adds a {@link JUnitReportListener} to the underlying
* test runner in order to capture test results in an XML report. You may use
* this class in place of {@link InstrumentationTestRunner} in your test
* project's manifest, and/or specify it to your Ant build using the test.runner
* property.
* <p/>
* This runner behaves identically to the default, with the added side-effect of
* producing JUnit XML reports. The report format is similar to that produced
* by the Ant JUnit task's XML formatter, making it compatible with existing
* tools that can process that format. See {@link JUnitReportListener} for
* further details.
* <p/>
* This runner accepts the following arguments:
* <ul>
* <li>
* reportFile: name of the file(s) to write the XML report to (default:
* junit-report.xml or junit-report-$(suite).xml depending on the value of
* multiFile). May contain $(suite), which will be replaced with the test
* suite name when using multiFile mode. See the reportDir argument for
* discussion of the file location.
* </li>
* <li>
* reportDir: if specified, absolute path to a directory in which to write
* the report file(s) (default: unset, in which case files are written to
* the test application's data area if possible, or the application under
* test's data area if that fails).
* </li>
* <li>
* multiFile: if true, write a separate XML file for each test suite;
* otherwise include all suites in a single XML file (default: false).
* </li>
* <li>
* filterTraces: if true, stack traces in test failure reports will be
* filtered to remove noise such as framework methods (default: true)
* </li>
* </ul>
* These arguments may be specified as follows:
*
* <pre>
* {@code adb shell am instrument -w -e reportFile my-report-file.xml}
* </pre>
*/
public class JUnitReportTestRunner extends InstrumentationTestRunner {
/**
* Name of the report file(s) to write, may contain $(suite) in multiFile mode.
*/
private static final String ARG_REPORT_FILE = "reportFile";
/**
* If specified, path of the directory to write report files to. If not set the files are
* written to the test application's data area.
*/
private static final String ARG_REPORT_DIR = "reportDir";
/**
* If true, stack traces in the report will be filtered to remove common noise (e.g. framework
* methods).
*/
private static final String ARG_FILTER_TRACES = "filterTraces";
/**
* If true, produce a separate file for each test suite. By default a single report is created
* for all suites.
*/
private static final String ARG_MULTI_FILE = "multiFile";
/**
* Default name of the single report file.
*/
private static final String DEFAULT_SINGLE_REPORT_FILE = "junit-report.xml";
/**
* Default name pattern for multiple report files.
*/
private static final String DEFAULT_MULTI_REPORT_FILE = "junit-report-$(suite).xml";
private JUnitReportListener mListener;
private String mReportFile;
private String mReportDir;
private boolean mFilterTraces = true;
private boolean mMultiFile = false;
@Override
public void onCreate(Bundle arguments) {
if (arguments != null) {
mReportFile = arguments.getString(ARG_REPORT_FILE);
mReportDir = arguments.getString(ARG_REPORT_DIR);
mFilterTraces = getBooleanArgument(arguments, ARG_FILTER_TRACES, true);
mMultiFile = getBooleanArgument(arguments, ARG_MULTI_FILE, false);
}
if (mReportFile == null) {
mReportFile = mMultiFile ? DEFAULT_MULTI_REPORT_FILE : DEFAULT_SINGLE_REPORT_FILE;
}
super.onCreate(arguments);
}
private boolean getBooleanArgument(Bundle arguments, String name, boolean defaultValue)
{
String value = arguments.getString(name);
if (value == null) {
return defaultValue;
} else {
return Boolean.parseBoolean(value);
}
}
/** you can subclass and override this if you want to use a different TestRunner */
protected AndroidTestRunner makeAndroidTestRunner() {
return new AndroidTestRunner();
}
@Override
protected AndroidTestRunner getAndroidTestRunner() {
AndroidTestRunner runner = makeAndroidTestRunner();
mListener = new JUnitReportListener(getContext(), getTargetContext(), mReportFile, mReportDir, mFilterTraces, mMultiFile);
runner.addTestListener(mListener);
return runner;
}
@Override
public void finish(int resultCode, Bundle results) {
if (mListener != null) {
mListener.close();
}
super.finish(resultCode, results);
}
}