| 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))
|
|
|