Index: testing/android/generate_native_test.py |
diff --git a/testing/android/generate_native_test.py b/testing/android/generate_native_test.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..7b7853d2894e2f55f43421ee829813cbc271d520 |
--- /dev/null |
+++ b/testing/android/generate_native_test.py |
@@ -0,0 +1,194 @@ |
+#!/usr/bin/python |
+# Copyright (c) 2012 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+# On Android we build unit test bundles as shared libraries. To run |
+# tests, we launch a special "test runner" apk which loads the library |
+# then jumps into it. Since java is required for many tests |
+# (e.g. PathUtils.java), a "pure native" test bundle is inadequate. |
+# |
+# This script, generate_native_test.py, is used to generate the source |
+# for an apk that wraps a unit test shared library bundle. That |
+# allows us to have a single boiler-plate application be used across |
+# all unit test bundles. |
+ |
+import logging |
+import optparse |
+import os |
+import re |
+import shutil |
+import subprocess |
+import sys |
+ |
+ |
+class NativeTestApkGenerator(object): |
+ """Generate a native test apk source tree. |
+ |
+ TODO(jrg): develop this more so the activity name is replaced as |
+ well. That will allow multiple test runners to be installed at the |
+ same time. (The complication is that it involves renaming a java |
+ class, which implies regeneration of a jni header, and on and on...) |
+ """ |
+ |
+ # Files or directories we need to copy to create a complete apk test shell. |
+ _SOURCE_FILES = ['AndroidManifest.xml', |
+ 'native_test_apk.xml', |
+ 'res', # res/values/strings.xml |
+ 'java', # .../ChromeNativeTestActivity.java |
+ ] |
+ |
+ # Files in the destion directory that have a "replaceme" string |
+ # which should be replaced by the basename of the shared library. |
+ # Note we also update the filename if 'replaceme' is itself found in |
+ # the filename. |
+ _REPLACEME_FILES = ['AndroidManifest.xml', |
+ 'native_test_apk.xml', |
+ 'res/values/strings.xml'] |
+ |
+ def __init__(self, native_library, jars, output_directory): |
+ self._native_library = native_library |
+ self._jars = jars |
+ self._output_directory = output_directory |
+ self._root_name = None |
+ if self._native_library: |
+ self._root_name = self._LibraryRoot() |
+ logging.warn('root name: %s' % self._root_name) |
+ |
+ |
+ def _LibraryRoot(self): |
+ """Return a root name for a shared library. |
+ |
+ The root name should be suitable for substitution in apk files |
+ like the manifest. For example, blah/foo/libbase_unittests.so |
+ becomes base_unittests. |
+ """ |
+ rootfinder = re.match('.?lib(.+).so', |
+ os.path.basename(self._native_library)) |
+ if rootfinder: |
+ return rootfinder.group(1) |
+ else: |
+ return None |
+ |
+ def _CopyTemplateFiles(self): |
+ """Copy files needed to build a new apk. |
+ |
+ TODO(jrg): add more smarts so we don't change file timestamps if |
+ the files don't change? |
+ """ |
+ srcdir = os.path.dirname(os.path.realpath( __file__)) |
+ if not os.path.exists(self._output_directory): |
+ os.makedirs(self._output_directory) |
+ for f in self._SOURCE_FILES: |
+ src = os.path.join(srcdir, f) |
+ dest = os.path.join(self._output_directory, f) |
+ if os.path.isfile(src): |
+ if os.path.exists(dest): |
+ os.remove(dest) |
+ logging.warn('%s --> %s' % (src, dest)) |
+ shutil.copyfile(src, dest) |
+ else: # directory |
+ if os.path.exists(dest): |
+ # One more sanity check since we're deleting a directory... |
+ if not '/out/' in dest: |
+ raise Exception('Unbelievable output directory; bailing for safety') |
+ shutil.rmtree(dest) |
+ logging.warn('%s --> %s' % (src, dest)) |
+ shutil.copytree(src, dest) |
+ |
+ def _ReplaceStrings(self): |
+ """Replace 'replaceme' strings in generated files with a root libname. |
+ |
+ If we have no root libname (e.g. no shlib was specified), do nothing. |
+ """ |
+ if not self._root_name: |
+ return |
+ logging.warn('Replacing "replaceme" with ' + self._root_name) |
+ for f in self._REPLACEME_FILES: |
+ dest = os.path.join(self._output_directory, f) |
+ contents = open(dest).read() |
+ contents = contents.replace('replaceme', self._root_name) |
+ dest = dest.replace('replaceme', self._root_name) # update the filename! |
+ open(dest, "w").write(contents) |
+ |
+ def _CopyLibraryAndJars(self): |
+ """Copy the shlib and jars into the apk source tree (if relevant)""" |
+ if self._native_library: |
+ destdir = os.path.join(self._output_directory, 'libs/armeabi') |
+ if not os.path.exists(destdir): |
+ os.makedirs(destdir) |
+ dest = os.path.join(destdir, os.path.basename(self._native_library)) |
+ logging.warn('%s --> %s' % (self._native_library, dest)) |
+ shutil.copyfile(self._native_library, dest) |
+ if self._jars: |
+ destdir = os.path.join(self._output_directory, 'libs') |
+ if not os.path.exists(destdir): |
+ os.makedirs(destdir) |
+ for jar in self._jars: |
+ dest = os.path.join(destdir, os.path.basename(jar)) |
+ logging.warn('%s --> %s' % (jar, dest)) |
+ shutil.copyfile(jar, dest) |
+ |
+ def CreateBundle(self): |
+ """Create the apk bundle source and assemble components.""" |
+ self._CopyTemplateFiles() |
+ self._ReplaceStrings() |
+ self._CopyLibraryAndJars() |
+ |
+ def Compile(self, ant_args): |
+ """Build the generated apk with ant. |
+ |
+ Args: |
+ ant_args: extra args to pass to ant |
+ """ |
+ cmd = ['ant'] |
+ if ant_args: |
+ cmd.append(ant_args) |
+ cmd.extend(['-buildfile', |
+ os.path.join(self._output_directory, 'native_test_apk.xml')]) |
+ logging.warn(cmd) |
+ p = subprocess.Popen(cmd, stderr=subprocess.STDOUT) |
+ (stdout, _) = p.communicate() |
+ logging.warn(stdout) |
+ if p.returncode != 0: |
+ logging.error('Ant return code %d' % p.returncode) |
+ sys.exit(p.returncode) |
+ |
+ |
+def main(argv): |
+ parser = optparse.OptionParser() |
+ parser.add_option('--verbose', |
+ help='Be verbose') |
+ parser.add_option('--native_library', |
+ help='Full name of native shared library test bundle') |
+ parser.add_option('--jar', action='append', |
+ help='Include this jar; can be specified multiple times') |
+ parser.add_option('--output', |
+ help='Output directory for generated files.') |
+ parser.add_option('--ant-compile', action='store_true', |
+ help='If specified, build the generated apk with ant') |
+ parser.add_option('--ant-args', |
+ help='extra args for ant') |
+ |
+ options, _ = parser.parse_args(argv) |
+ |
+ # It is not an error to specify no native library; the apk should |
+ # still be generated and build. It will, however, print |
+ # NATIVE_LOADER_FAILED when run. |
+ if not options.output: |
+ raise Exception('No output directory specified for generated files') |
+ |
+ if options.verbose: |
+ logging.basicConfig(level=logging.DEBUG, format=' %(message)s') |
+ |
+ ntag = NativeTestApkGenerator(native_library=options.native_library, |
+ jars=options.jar, |
+ output_directory=options.output) |
+ ntag.CreateBundle() |
+ if options.ant_compile: |
+ ntag.Compile(options.ant_args) |
+ |
+ logging.warn('COMPLETE.') |
+ |
+if __name__ == '__main__': |
+ sys.exit(main(sys.argv)) |