├── .classpath.template ├── .gitignore ├── .project ├── LICENSE ├── README.md ├── build.xml ├── example ├── AndroidManifest.xml ├── README ├── ant.properties ├── build.xml ├── proguard-project.txt ├── project.properties ├── res │ ├── drawable-hdpi │ │ └── ic_launcher.png │ ├── drawable-ldpi │ │ └── ic_launcher.png │ ├── drawable-mdpi │ │ └── ic_launcher.png │ ├── layout │ │ └── main.xml │ └── values │ │ └── strings.xml ├── src │ └── com │ │ └── zutubi │ │ └── android │ │ └── junitreport │ │ └── example │ │ └── AJRExampleActivity.java └── tests │ ├── AndroidManifest.xml │ ├── ant.properties │ ├── build.xml │ ├── custom_rules.xml │ ├── proguard-project.txt │ ├── project.properties │ └── src │ └── com │ └── zutubi │ └── android │ └── junitreport │ └── example │ └── AJRExampleActivityTest.java └── src └── com └── zutubi └── android └── junitreport ├── Compatibility.java ├── JUnitReportListener.java └── JUnitReportTestRunner.java /.classpath.template: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | local.properties 3 | .classpath 4 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | android-junit-report 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Android JUnit Report Test Runner 2 | ================================ 3 | 4 | Introduction 5 | ------------ 6 | 7 | The Android JUnit report test runner is a custom instrumentation test 8 | runner for Android that creates XML test reports. These reports are 9 | in a similar format to those created by the Ant JUnit task's XML 10 | formatter, allowing them to be integrated with tools that support that 11 | format (e.g. continuous integration servers). 12 | 13 | Home Page 14 | --------- 15 | 16 | Android JUnit report has home on the web at: 17 | 18 | http://zutubi.com/source/projects/android-junit-report/ 19 | 20 | Note 21 | ---- 22 | 23 | This project is no longer required with the new, Gradle-based build 24 | system that ships with modern versions of the ADT. 25 | 26 | License 27 | ------- 28 | 29 | This code is licensed under the Apache License, Version 2.0. See the 30 | LICENSE file for details. 31 | 32 | Quick Start 33 | ----------- 34 | 35 | This is a quick overview of how to integrate the runner with Ant 36 | builds. Note all modifications are made to your test project, i.e. the 37 | project which implements the JUnit tests: 38 | 39 | * Grab the latest jar from: 40 | http://github.com/jsankey/android-junit-report/downloads 41 | and add it to your libs/ directory. 42 | * Edit AndroidManifest.xml to set android:name in the 43 | instrumentation tag to: 44 | com.zutubi.android.junitreport.JUnitReportTestRunner. 45 | * Edit ant.properties to add the line: 46 | test.runner=com.zutubi.android.junitreport.JUnitReportTestRunner 47 | * Run your tests as you would normally: 48 | $ ant debug install test 49 | * Pull the resulting XML report from the device (from the application 50 | under test's internal storage directory): 51 | $ adb pull /data/data/main app package/files/junit-report.xml 52 | * Integrate the XML with your chosen build tool. 53 | 54 | Customising Via Arguments 55 | ------------------------- 56 | 57 | The runner supports the following arguments: 58 | 59 | * multiFile: if set to true, a new report file is generated for each 60 | test suite. Defaults to false (a single file contains all suites). 61 | * reportFile: the name of the report file to generate (single file 62 | mode) or a pattern for the name of the files to generate (multiple 63 | file mode). In the latter case the string \_\_suite\_\_ will be 64 | substituted with the test suite name. Defaults to junit-report.xml 65 | in single file mode, junit-report-\_\_suite\_\_.xml in multiple file 66 | mode. 67 | * reportDir: path to a directory in which to write the report 68 | file(s). May start with \_\_external\_\_ which will be replaced with 69 | the external storage directory for the application under test. 70 | This requires external storage to be available and 71 | WRITE_EXTERNAL_STORAGE permission in the application under test. 72 | Defaults to unspecified, in which case the internal storage 73 | directory of the application under test is used. 74 | * filterTraces: if true, stack traces in the report will be filtered 75 | to remove common noise (e.g. framework methods). Defaults to true. 76 | 77 | To specify arguments, use the -e flag to adb shell am instrument, for 78 | example: 79 | 80 | adb shell am instrument -w -e reportFile my-report.xml \ 81 | com.example.test/com.zutubi.android.junitreport.JUnitReportTestRunner 82 | 83 | See the example and/or full documentation for how to set arguments in 84 | you Ant build. 85 | 86 | More Information 87 | ---------------- 88 | 89 | Check out the following resources for more details: 90 | 91 | * The project home page (with full documentation): 92 | http://zutubi.com/source/projects/android-junit-report/ 93 | * The example project in the example/ subdirectory. 94 | * The GitHub project page: 95 | https://github.com/jsankey/android-junit-report/ 96 | 97 | Building From Source 98 | -------------------- 99 | 100 | If you would like to modify the runner, or build it yourself for any 101 | other reason, you will need: 102 | 103 | * A JDK, version 1.5 or later. 104 | * The Android SDK (or at least a stub android.jar as provided in the 105 | SDK). 106 | * Apache Ant version 1.7 or later. 107 | 108 | To run a build: 109 | 110 | * Create a file local.properties in the directory containing this 111 | README. In this file, define the location of an android.jar to 112 | build against, for example: 113 | 114 | android.jar=/opt/android/platforms/android-14/android.jar 115 | 116 | where /opt/android is the root of an Android SDK. 117 | 118 | * Run ant in this same directory: 119 | 120 | $ ant 121 | 122 | The jar will be created at build/android-junit-report-dev.jar. 123 | 124 | Feedback 125 | ------- 126 | 127 | If you have any thoughts, questions etc about the runner, you can 128 | contact me at: 129 | 130 | jason@zutubi.com 131 | 132 | All feedback is welcome. 133 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /example/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /example/README: -------------------------------------------------------------------------------- 1 | Android JUnit Report Example Project 2 | ==================================== 3 | 4 | Introduction 5 | ------------ 6 | 7 | This directory contains a sample project that illustrates how to use 8 | Android JUnit Report with Ant. The project itself is a default one 9 | create by the android tool, with a simple test project under the tests/ 10 | subdirectory. 11 | 12 | Files Of Interest 13 | ----------------- 14 | 15 | The only source files of interest are: 16 | 17 | * tests/ant.properties: sets the test.runner property to use the 18 | custom runner. 19 | * tests/custom_rules.xml: defines targets that use the custom test 20 | runner. 21 | 22 | Once you run some tests you will see reports in: 23 | 24 | * tests/bin/reports 25 | 26 | Running 27 | ------- 28 | 29 | To try out the sample, first build the main application, then run the 30 | test-and-fetch target in the tests/subdirectory: 31 | 32 | $ ant debug install 33 | $ cd tests 34 | $ ant test-and-fetch 35 | 36 | You should see the report at tests/bin/reports/junit-report.xml. You 37 | can also try a target with custom arguments: 38 | 39 | $ ant custom-location 40 | 41 | which fetches a report to the same directory, but with the test suite 42 | name included. This latter example requires the device to have external 43 | storage mounted. It also assumes that such storage is mounted at 44 | /sdcard, which is not always true (you can simply edit the path in the 45 | custom rules file to match your device if necessary). 46 | 47 | More Information 48 | ---------------- 49 | 50 | For more information, refer to: 51 | 52 | * The top-level README (in the directory above this one). 53 | * The official project home page: 54 | http://zutubi.com/source/projects/android-junit-report/ 55 | 56 | -------------------------------------------------------------------------------- /example/ant.properties: -------------------------------------------------------------------------------- 1 | # This file is used to override default values used by the Ant build system. 2 | # 3 | # This file must be checked into Version Control Systems, as it is 4 | # integral to the build system of your project. 5 | 6 | # This file is only used by the Ant script. 7 | 8 | # You can use this to override default values such as 9 | # 'source.dir' for the location of your java source folder and 10 | # 'out.dir' for the location of your output folder. 11 | 12 | # You can also use it define how the release builds are signed by declaring 13 | # the following properties: 14 | # 'key.store' for the location of your keystore and 15 | # 'key.alias' for the name of the key to use. 16 | # The password will be asked during the build when you use the 'release' target. 17 | 18 | -------------------------------------------------------------------------------- /example/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 29 | 30 | 31 | 35 | 36 | 37 | 38 | 39 | 40 | 49 | 50 | 51 | 52 | 56 | 57 | 69 | 70 | 71 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /example/proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /example/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-15 15 | -------------------------------------------------------------------------------- /example/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsankey/android-junit-report/e7297611ae8321faacb65f693b0ba6259e2b08cb/example/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/res/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsankey/android-junit-report/e7297611ae8321faacb65f693b0ba6259e2b08cb/example/res/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /example/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsankey/android-junit-report/e7297611ae8321faacb65f693b0ba6259e2b08cb/example/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | AJRExampleActivity 4 | 5 | -------------------------------------------------------------------------------- /example/src/com/zutubi/android/junitreport/example/AJRExampleActivity.java: -------------------------------------------------------------------------------- 1 | package com.zutubi.android.junitreport.example; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | 6 | public class AJRExampleActivity extends Activity 7 | { 8 | /** Called when the activity is first created. */ 9 | @Override 10 | public void onCreate(Bundle savedInstanceState) 11 | { 12 | super.onCreate(savedInstanceState); 13 | setContentView(R.layout.main); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/tests/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 10 | 11 | 12 | 13 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /example/tests/ant.properties: -------------------------------------------------------------------------------- 1 | # This file is used to override default values used by the Ant build system. 2 | # 3 | # This file must be checked into Version Control Systems, as it is 4 | # integral to the build system of your project. 5 | 6 | # This file is only used by the Ant script. 7 | 8 | # You can use this to override default values such as 9 | # 'source.dir' for the location of your java source folder and 10 | # 'out.dir' for the location of your output folder. 11 | 12 | # You can also use it define how the release builds are signed by declaring 13 | # the following properties: 14 | # 'key.store' for the location of your keystore and 15 | # 'key.alias' for the name of the key to use. 16 | # The password will be asked during the build when you use the 'release' target. 17 | 18 | tested.project.dir=.. 19 | test.runner=com.zutubi.android.junitreport.JUnitReportTestRunner 20 | -------------------------------------------------------------------------------- /example/tests/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 29 | 30 | 31 | 35 | 36 | 37 | 38 | 39 | 40 | 49 | 50 | 51 | 52 | 56 | 57 | 69 | 70 | 71 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /example/tests/custom_rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | Downloading XML test report... 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | Running tests... 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | Downloading XML test report... 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /example/tests/proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /example/tests/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-15 15 | -------------------------------------------------------------------------------- /example/tests/src/com/zutubi/android/junitreport/example/AJRExampleActivityTest.java: -------------------------------------------------------------------------------- 1 | package com.zutubi.android.junitreport.example; 2 | 3 | import android.test.ActivityInstrumentationTestCase2; 4 | 5 | /** 6 | * This is a simple framework for a test of an Application. See 7 | * {@link android.test.ApplicationTestCase ApplicationTestCase} for more information on 8 | * how to write and extend Application tests. 9 | *

10 | * To run this test, you can type: 11 | * adb shell am instrument -w \ 12 | * -e class com.zutubi.android.junitreport.example.AJRExampleActivityTest \ 13 | * com.zutubi.android.junitreport.example.tests/android.test.InstrumentationTestRunner 14 | */ 15 | public class AJRExampleActivityTest extends ActivityInstrumentationTestCase2 { 16 | 17 | public AJRExampleActivityTest() { 18 | super("com.zutubi.android.junitreport.example", AJRExampleActivity.class); 19 | } 20 | 21 | public void testSanity() { 22 | assertEquals(2, 1 + 1); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/com/zutubi/android/junitreport/Compatibility.java: -------------------------------------------------------------------------------- 1 | package com.zutubi.android.junitreport; 2 | 3 | import java.io.File; 4 | import java.lang.reflect.Method; 5 | 6 | import android.content.Context; 7 | import android.os.Environment; 8 | import android.util.Log; 9 | 10 | /** 11 | * Utilities for backwards-compatibility with early Android versions. 12 | */ 13 | public final class Compatibility { 14 | private static final String LOG_TAG = Compatibility.class.getSimpleName(); 15 | 16 | private static final Method METHOD_GET_EXTERNAL_FILES_DIR; 17 | static { 18 | Method method = null; 19 | try { 20 | method = Context.class.getMethod("getExternalFilesDir", String.class); 21 | } catch (Exception e) { 22 | // Expected for API 7 and below. Fall back will be engaged. 23 | } 24 | METHOD_GET_EXTERNAL_FILES_DIR = method; 25 | } 26 | 27 | /** 28 | * Do not instantiate. 29 | */ 30 | private Compatibility() { 31 | } 32 | 33 | /** 34 | * A backwards-compatible version of {@link Context#getExternalFilesDir(String)} 35 | * which falls back to using {@link Environment#getExternalStorageDirectory()} 36 | * on API 7 and below. 37 | * 38 | * @param context context to get the external files directory for 39 | * @param type the type of files directory to return (may be null) 40 | * @return the path of the directory holding application files on external 41 | * storage, or null if external storage cannot be accessed 42 | */ 43 | public static File getExternalFilesDir(final Context context, final String type) { 44 | if (METHOD_GET_EXTERNAL_FILES_DIR == null) { 45 | final File externalRoot = Environment.getExternalStorageDirectory(); 46 | if (externalRoot == null) { 47 | return null; 48 | } 49 | 50 | final String packageName = context.getApplicationContext().getPackageName(); 51 | return new File(externalRoot, "Android/data/" + packageName + "/files"); 52 | } else { 53 | try { 54 | return (File) METHOD_GET_EXTERNAL_FILES_DIR.invoke(context, type); 55 | } catch (Exception e) { 56 | Log.e(LOG_TAG, "Could not invoke getExternalFilesDir: " + e.getMessage(), e); 57 | return null; 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/com/zutubi/android/junitreport/JUnitReportListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2012 Zutubi Pty Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.zutubi.android.junitreport; 18 | 19 | import java.io.File; 20 | import java.io.FileOutputStream; 21 | import java.io.IOException; 22 | import java.io.PrintWriter; 23 | import java.io.StringWriter; 24 | import java.io.Writer; 25 | import java.util.Locale; 26 | 27 | import junit.framework.AssertionFailedError; 28 | import junit.framework.Test; 29 | import junit.framework.TestCase; 30 | import junit.framework.TestListener; 31 | 32 | import org.xmlpull.v1.XmlSerializer; 33 | 34 | import android.content.Context; 35 | import android.util.Log; 36 | import android.util.Xml; 37 | 38 | /** 39 | * Custom test listener that outputs test results to XML files. The files 40 | * use a similar format to the Ant JUnit task XML formatter, with a few of 41 | * caveats: 42 | *

60 | * The differences mainly revolve around making this reporting as lightweight as 61 | * possible. The report is streamed as the tests run, making it impossible to, 62 | * e.g. include the case count in a <testsuite> element. 63 | */ 64 | public class JUnitReportListener implements TestListener { 65 | private static final String LOG_TAG = JUnitReportListener.class.getSimpleName(); 66 | 67 | private static final String ENCODING_UTF_8 = "utf-8"; 68 | 69 | public static final String TOKEN_SUITE = "__suite__"; 70 | public static final String TOKEN_EXTERNAL = "__external__"; 71 | 72 | private static final String TAG_SUITES = "testsuites"; 73 | private static final String TAG_SUITE = "testsuite"; 74 | private static final String TAG_CASE = "testcase"; 75 | private static final String TAG_ERROR = "error"; 76 | private static final String TAG_FAILURE = "failure"; 77 | 78 | private static final String ATTRIBUTE_NAME = "name"; 79 | private static final String ATTRIBUTE_CLASS = "classname"; 80 | private static final String ATTRIBUTE_TYPE = "type"; 81 | private static final String ATTRIBUTE_MESSAGE = "message"; 82 | private static final String ATTRIBUTE_TIME = "time"; 83 | 84 | // With thanks to org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner. 85 | // Trimmed some entries, added others for Android. 86 | private static final String[] DEFAULT_TRACE_FILTERS = new String[] { 87 | "junit.framework.TestCase", "junit.framework.TestResult", 88 | "junit.framework.TestSuite", 89 | "junit.framework.Assert.", // don't filter AssertionFailure 90 | "java.lang.reflect.Method.invoke(", "sun.reflect.", 91 | // JUnit 4 support: 92 | "org.junit.", "junit.framework.JUnit4TestAdapter", " more", 93 | // Added for Android 94 | "android.test.", "android.app.Instrumentation", 95 | "java.lang.reflect.Method.invokeNative", 96 | }; 97 | 98 | private Context mTargetContext; 99 | private String mReportFile; 100 | private String mReportDir; 101 | private boolean mFilterTraces; 102 | private boolean mMultiFile; 103 | private FileOutputStream mOutputStream; 104 | private XmlSerializer mSerializer; 105 | private String mCurrentSuite; 106 | 107 | // simple time tracking 108 | private boolean mTimeAlreadyWritten = false; 109 | private long mTestStartTime; 110 | 111 | /** 112 | * Creates a new listener. 113 | * 114 | * @param context context of the test application 115 | * @param targetContext context of the application under test 116 | * @param reportFile name of the report file(s) to create 117 | * @param reportDir path of the directory under which to write files 118 | * (may be null in which case files are written under 119 | * the context using {@link Context#openFileOutput(String, int)}). 120 | * @param filterTraces if true, stack traces will have common noise (e.g. 121 | * framework methods) omitted for clarity 122 | * @param multiFile if true, use a separate file for each test suite 123 | */ 124 | public JUnitReportListener(Context context, Context targetContext, String reportFile, String reportDir, boolean filterTraces, boolean multiFile) { 125 | Log.i(LOG_TAG, "Listener created with arguments:\n" + 126 | " report file : '" + reportFile + "'\n" + 127 | " report dir : '" + reportDir + "'\n" + 128 | " filter traces: " + filterTraces + "\n" + 129 | " multi file : " + multiFile); 130 | 131 | this.mTargetContext = targetContext; 132 | this.mReportFile = reportFile; 133 | this.mReportDir = reportDir; 134 | this.mFilterTraces = filterTraces; 135 | this.mMultiFile = multiFile; 136 | } 137 | 138 | @Override 139 | public void startTest(Test test) { 140 | try { 141 | if (test instanceof TestCase) { 142 | TestCase testCase = (TestCase) test; 143 | checkForNewSuite(testCase); 144 | mSerializer.startTag("", TAG_CASE); 145 | mSerializer.attribute("", ATTRIBUTE_CLASS, mCurrentSuite); 146 | mSerializer.attribute("", ATTRIBUTE_NAME, testCase.getName()); 147 | 148 | mTimeAlreadyWritten = false; 149 | mTestStartTime = System.currentTimeMillis(); 150 | } 151 | } catch (IOException e) { 152 | Log.e(LOG_TAG, safeMessage(e)); 153 | } 154 | } 155 | 156 | private void checkForNewSuite(TestCase testCase) throws IOException { 157 | String suiteName = testCase.getClass().getName(); 158 | if (mCurrentSuite == null || !mCurrentSuite.equals(suiteName)) { 159 | if (mCurrentSuite != null) { 160 | if (mMultiFile) { 161 | close(); 162 | } else { 163 | mSerializer.endTag("", TAG_SUITE); 164 | mSerializer.flush(); 165 | } 166 | } 167 | 168 | openIfRequired(suiteName); 169 | 170 | mSerializer.startTag("", TAG_SUITE); 171 | mSerializer.attribute("", ATTRIBUTE_NAME, suiteName); 172 | mCurrentSuite = suiteName; 173 | } 174 | } 175 | 176 | private void openIfRequired(String suiteName) { 177 | try { 178 | if (mSerializer == null) { 179 | mOutputStream = openOutputStream(resolveFileName(suiteName)); 180 | mSerializer = Xml.newSerializer(); 181 | mSerializer.setOutput(mOutputStream, ENCODING_UTF_8); 182 | mSerializer.startDocument(ENCODING_UTF_8, true); 183 | if (!mMultiFile) { 184 | mSerializer.startTag("", TAG_SUITES); 185 | } 186 | } 187 | } catch (IOException e) { 188 | Log.e(LOG_TAG, safeMessage(e)); 189 | throw new RuntimeException("Unable to open serializer: " + e.getMessage(), e); 190 | } 191 | } 192 | 193 | private String resolveFileName(String suiteName) { 194 | String fileName = mReportFile; 195 | if (mMultiFile) { 196 | fileName = fileName.replace(TOKEN_SUITE, suiteName); 197 | } 198 | return fileName; 199 | } 200 | 201 | private FileOutputStream openOutputStream(String fileName) throws IOException { 202 | if (mReportDir == null) { 203 | Log.d(LOG_TAG, "No reportDir specified. Opening report file '" + fileName + "' in internal storage of app under test"); 204 | return mTargetContext.openFileOutput(fileName, Context.MODE_PRIVATE); 205 | } else { 206 | if (mReportDir.contains(TOKEN_EXTERNAL)) { 207 | File externalDir = Compatibility.getExternalFilesDir(mTargetContext, null); 208 | if (externalDir == null) { 209 | Log.e(LOG_TAG, "reportDir references external storage, but external storage is not available (check mounting and permissions)"); 210 | throw new IOException("Cannot access external storage"); 211 | } 212 | 213 | String externalPath = externalDir.getAbsolutePath(); 214 | if (externalPath.endsWith("/")) { 215 | externalPath = externalPath.substring(0, externalPath.length() - 1); 216 | } 217 | 218 | mReportDir = mReportDir.replace(TOKEN_EXTERNAL, externalPath); 219 | } 220 | 221 | ensureDirectoryExists(mReportDir); 222 | 223 | File outputFile = new File(mReportDir, fileName); 224 | Log.d(LOG_TAG, "Opening report file '" + outputFile.getAbsolutePath() + "'"); 225 | return new FileOutputStream(outputFile); 226 | } 227 | } 228 | 229 | private void ensureDirectoryExists(String path) throws IOException { 230 | File dir = new File(path); 231 | if (!dir.isDirectory() && !dir.mkdirs()) { 232 | final String message = "Cannot create directory '" + path + "'"; 233 | Log.e(LOG_TAG, message); 234 | throw new IOException(message); 235 | } 236 | } 237 | 238 | @Override 239 | public void addError(Test test, Throwable error) { 240 | addProblem(TAG_ERROR, error); 241 | } 242 | 243 | @Override 244 | public void addFailure(Test test, AssertionFailedError error) { 245 | addProblem(TAG_FAILURE, error); 246 | } 247 | 248 | private void addProblem(String tag, Throwable error) { 249 | try { 250 | recordTestTime(); 251 | 252 | mSerializer.startTag("", tag); 253 | mSerializer.attribute("", ATTRIBUTE_MESSAGE, safeMessage(error)); 254 | mSerializer.attribute("", ATTRIBUTE_TYPE, error.getClass().getName()); 255 | StringWriter w = new StringWriter(); 256 | error.printStackTrace(mFilterTraces ? new FilteringWriter(w) : new PrintWriter(w)); 257 | mSerializer.text(w.toString()); 258 | mSerializer.endTag("", tag); 259 | mSerializer.flush(); 260 | } catch (IOException e) { 261 | Log.e(LOG_TAG, safeMessage(e)); 262 | } 263 | } 264 | 265 | private void recordTestTime() throws IOException { 266 | if (!mTimeAlreadyWritten) { 267 | mTimeAlreadyWritten = true; 268 | mSerializer.attribute("", ATTRIBUTE_TIME, String.format(Locale.ENGLISH, "%.3f", 269 | (System.currentTimeMillis() - mTestStartTime) / 1000.)); 270 | } 271 | } 272 | 273 | @Override 274 | public void endTest(Test test) { 275 | try { 276 | if (test instanceof TestCase) { 277 | recordTestTime(); 278 | mSerializer.endTag("", TAG_CASE); 279 | mSerializer.flush(); 280 | } 281 | } catch (IOException e) { 282 | Log.e(LOG_TAG, safeMessage(e)); 283 | } 284 | } 285 | 286 | /** 287 | * Releases all resources associated with this listener. Must be called 288 | * when the listener is finished with. 289 | */ 290 | public void close() { 291 | if (mSerializer != null) { 292 | try { 293 | // Do this just in case endTest() was not called due to a crash in native code. 294 | if (TAG_CASE.equals(mSerializer.getName())) { 295 | mSerializer.endTag("", TAG_CASE); 296 | } 297 | 298 | if (mCurrentSuite != null) { 299 | mSerializer.endTag("", TAG_SUITE); 300 | } 301 | 302 | if (!mMultiFile) { 303 | mSerializer.endTag("", TAG_SUITES); 304 | } 305 | mSerializer.endDocument(); 306 | mSerializer.flush(); 307 | mSerializer = null; 308 | } catch (IOException e) { 309 | Log.e(LOG_TAG, safeMessage(e)); 310 | } 311 | } 312 | 313 | if (mOutputStream != null) { 314 | try { 315 | mOutputStream.close(); 316 | mOutputStream = null; 317 | } catch (IOException e) { 318 | Log.e(LOG_TAG, safeMessage(e)); 319 | } 320 | } 321 | } 322 | 323 | private String safeMessage(Throwable error) { 324 | String message = error.getMessage(); 325 | return error.getClass().getName() + ": " + (message == null ? "" : message); 326 | } 327 | 328 | /** 329 | * Wrapper around a print writer that filters out common noise from stack 330 | * traces, making it easier to see the actual failure. 331 | */ 332 | private static class FilteringWriter extends PrintWriter { 333 | public FilteringWriter(Writer out) { 334 | super(out); 335 | } 336 | 337 | @Override 338 | public void println(String s) { 339 | for (String filtered : DEFAULT_TRACE_FILTERS) { 340 | if (s.contains(filtered)) { 341 | return; 342 | } 343 | } 344 | 345 | super.println(s); 346 | } 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /src/com/zutubi/android/junitreport/JUnitReportTestRunner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010-2012 Zutubi Pty Ltd 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.zutubi.android.junitreport; 18 | 19 | import android.os.Bundle; 20 | import android.test.AndroidTestRunner; 21 | import android.test.InstrumentationTestRunner; 22 | import android.util.Log; 23 | 24 | /** 25 | * Custom test runner that adds a {@link JUnitReportListener} to the underlying 26 | * test runner in order to capture test results in an XML report. You may use 27 | * this class in place of {@link InstrumentationTestRunner} in your test 28 | * project's manifest, and/or specify it to your Ant build using the test.runner 29 | * property. 30 | *

31 | * This runner behaves identically to the default, with the added side-effect of 32 | * producing JUnit XML reports. The report format is similar to that produced 33 | * by the Ant JUnit task's XML formatter, making it compatible with existing 34 | * tools that can process that format. See {@link JUnitReportListener} for 35 | * further details. 36 | *

37 | * This runner accepts arguments specified by the ARG_* constants. For details 38 | * refer to the README. 39 | */ 40 | public class JUnitReportTestRunner extends InstrumentationTestRunner { 41 | /** 42 | * Name of the report file(s) to write, may contain __suite__ in multiFile mode. 43 | */ 44 | private static final String ARG_REPORT_FILE = "reportFile"; 45 | /** 46 | * If specified, path of the directory to write report files to. May start with __external__. 47 | * If not set files are written to the internal storage directory of the app under test. 48 | */ 49 | private static final String ARG_REPORT_DIR = "reportDir"; 50 | /** 51 | * If true, stack traces in the report will be filtered to remove common noise (e.g. framework 52 | * methods). 53 | */ 54 | private static final String ARG_FILTER_TRACES = "filterTraces"; 55 | /** 56 | * If true, produce a separate file for each test suite. By default a single report is created 57 | * for all suites. 58 | */ 59 | private static final String ARG_MULTI_FILE = "multiFile"; 60 | /** 61 | * Default name of the single report file. 62 | */ 63 | private static final String DEFAULT_SINGLE_REPORT_FILE = "junit-report.xml"; 64 | /** 65 | * Default name pattern for multiple report files. 66 | */ 67 | private static final String DEFAULT_MULTI_REPORT_FILE = "junit-report-" + JUnitReportListener.TOKEN_SUITE + ".xml"; 68 | 69 | private static final String LOG_TAG = JUnitReportTestRunner.class.getSimpleName(); 70 | 71 | private JUnitReportListener mListener; 72 | private String mReportFile; 73 | private String mReportDir; 74 | private boolean mFilterTraces = true; 75 | private boolean mMultiFile = false; 76 | 77 | @Override 78 | public void onCreate(Bundle arguments) { 79 | if (arguments != null) { 80 | Log.i(LOG_TAG, "Created with arguments: " + arguments.keySet()); 81 | mReportFile = arguments.getString(ARG_REPORT_FILE); 82 | mReportDir = arguments.getString(ARG_REPORT_DIR); 83 | mFilterTraces = getBooleanArgument(arguments, ARG_FILTER_TRACES, true); 84 | mMultiFile = getBooleanArgument(arguments, ARG_MULTI_FILE, false); 85 | } else { 86 | Log.i(LOG_TAG, "No arguments provided"); 87 | } 88 | 89 | if (mReportFile == null) { 90 | mReportFile = mMultiFile ? DEFAULT_MULTI_REPORT_FILE : DEFAULT_SINGLE_REPORT_FILE; 91 | Log.i(LOG_TAG, "Defaulted report file to '" + mReportFile + "'"); 92 | } 93 | 94 | super.onCreate(arguments); 95 | } 96 | 97 | private boolean getBooleanArgument(Bundle arguments, String name, boolean defaultValue) 98 | { 99 | String value = arguments.getString(name); 100 | if (value == null) { 101 | return defaultValue; 102 | } else { 103 | return Boolean.parseBoolean(value); 104 | } 105 | } 106 | 107 | /** 108 | * Subclass and override this if you want to use a different TestRunner type. 109 | * 110 | * @return the test runner to use 111 | */ 112 | protected AndroidTestRunner makeAndroidTestRunner() { 113 | return new AndroidTestRunner(); 114 | } 115 | 116 | @Override 117 | protected AndroidTestRunner getAndroidTestRunner() { 118 | AndroidTestRunner runner = makeAndroidTestRunner(); 119 | mListener = new JUnitReportListener(getContext(), getTargetContext(), mReportFile, mReportDir, mFilterTraces, mMultiFile); 120 | runner.addTestListener(mListener); 121 | return runner; 122 | } 123 | 124 | @Override 125 | public void finish(int resultCode, Bundle results) { 126 | if (mListener != null) { 127 | mListener.close(); 128 | } 129 | 130 | super.finish(resultCode, results); 131 | } 132 | } 133 | --------------------------------------------------------------------------------