[content shell] implement testRunner.overridePreference
[chromium-blink-merge.git] / testing / android / generate_native_test.py
blob0dbc7d768160519d890177bf531a02e66cbbb4e0
1 #!/usr/bin/env python
3 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file.
7 # On Android we build unit test bundles as shared libraries. To run
8 # tests, we launch a special "test runner" apk which loads the library
9 # then jumps into it. Since java is required for many tests
10 # (e.g. PathUtils.java), a "pure native" test bundle is inadequate.
12 # This script, generate_native_test.py, is used to generate the source
13 # for an apk that wraps a unit test shared library bundle. That
14 # allows us to have a single boiler-plate application be used across
15 # all unit test bundles.
17 import logging
18 import optparse
19 import os
20 import re
21 import subprocess
22 import sys
24 # cmd_helper.py is under ../../build/android/
25 sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..',
26 '..', 'build', 'android')))
27 from pylib import cmd_helper # pylint: disable=F0401
30 class NativeTestApkGenerator(object):
31 """Generate a native test apk source tree.
33 TODO(jrg): develop this more so the activity name is replaced as
34 well. That will allow multiple test runners to be installed at the
35 same time. (The complication is that it involves renaming a java
36 class, which implies regeneration of a jni header, and on and on...)
37 """
39 # Files or directories we need to copy to create a complete apk test shell.
40 _SOURCE_FILES = ['AndroidManifest.xml',
41 'native_test_apk.xml',
42 'res', # res/values/strings.xml
43 'java', # .../ChromeNativeTestActivity.java
46 # Files in the destion directory that have a "replaceme" string
47 # which should be replaced by the basename of the shared library.
48 # Note we also update the filename if 'replaceme' is itself found in
49 # the filename.
50 _REPLACEME_FILES = ['AndroidManifest.xml',
51 'native_test_apk.xml',
52 'res/values/strings.xml']
54 def __init__(self, native_library, strip_binary, output_directory,
55 target_abi):
56 self._native_library = native_library
57 self._strip_binary = strip_binary
58 self._output_directory = os.path.abspath(output_directory)
59 self._target_abi = target_abi
60 self._root_name = None
61 if self._native_library:
62 self._root_name = self._LibraryRoot()
63 logging.info('root name: %s', self._root_name)
65 def _LibraryRoot(self):
66 """Return a root name for a shared library.
68 The root name should be suitable for substitution in apk files
69 like the manifest. For example, blah/foo/libbase_unittests.so
70 becomes base_unittests.
71 """
72 rootfinder = re.match('.?lib(.+).so',
73 os.path.basename(self._native_library))
74 if rootfinder:
75 return rootfinder.group(1)
76 else:
77 return None
79 def _CopyTemplateFilesAndClearDir(self):
80 """Copy files needed to build a new apk.
82 Uses rsync to avoid unnecessary io. This call also clears outstanding
83 files in the directory.
84 """
85 srcdir = os.path.abspath(os.path.dirname(__file__))
86 destdir = self._output_directory
87 if not os.path.exists(destdir):
88 os.makedirs(destdir)
89 elif not '/out/' in destdir:
90 raise Exception('Unbelievable output directory; bailing for safety')
91 logging.info('rsync %s --> %s', self._SOURCE_FILES, destdir)
92 logging.info(cmd_helper.GetCmdOutput(
93 ['rsync', '-aRv', '--delete', '--exclude', '.svn'] +
94 self._SOURCE_FILES + [destdir], cwd=srcdir))
96 def _ReplaceStrings(self):
97 """Replace 'replaceme' strings in generated files with a root libname.
99 If we have no root libname (e.g. no shlib was specified), do nothing.
101 if not self._root_name:
102 return
103 logging.info('Replacing "replaceme" with ' + self._root_name)
104 for f in self._REPLACEME_FILES:
105 dest = os.path.join(self._output_directory, f)
106 contents = open(dest).read()
107 contents = contents.replace('replaceme', self._root_name)
108 dest = dest.replace('replaceme', self._root_name) # update the filename!
109 open(dest, 'w').write(contents)
111 def _CopyLibrary(self):
112 """Copy the shlib into the apk source tree (if relevant)."""
113 if self._native_library:
114 destdir = os.path.join(self._output_directory, 'libs/' + self._target_abi)
115 if not os.path.exists(destdir):
116 os.makedirs(destdir)
117 dest = os.path.join(destdir, os.path.basename(self._native_library))
118 logging.info('strip %s --> %s', self._native_library, dest)
119 cmd_helper.RunCmd(
120 [self._strip_binary, '--strip-unneeded', self._native_library, '-o',
121 dest])
123 def CreateBundle(self):
124 """Create the apk bundle source and assemble components."""
125 self._CopyTemplateFilesAndClearDir()
126 self._ReplaceStrings()
127 self._CopyLibrary()
129 def Compile(self, ant_args):
130 """Build the generated apk with ant.
132 Args:
133 ant_args: extra args to pass to ant
135 cmd = ['ant']
136 if ant_args:
137 cmd.extend(ant_args)
138 cmd.append("-DAPP_ABI=" + self._target_abi)
139 cmd.extend(['-buildfile',
140 os.path.join(self._output_directory, 'native_test_apk.xml')])
141 logging.info(cmd)
142 p = subprocess.Popen(cmd, stderr=subprocess.STDOUT)
143 (stdout, _) = p.communicate()
144 logging.info(stdout)
145 if p.returncode != 0:
146 logging.error('Ant return code %d', p.returncode)
147 sys.exit(p.returncode)
149 def main(argv):
150 parser = optparse.OptionParser()
151 parser.add_option('--verbose',
152 help='Be verbose')
153 parser.add_option('--native_library',
154 help='Full name of native shared library test bundle')
155 parser.add_option('--jars',
156 help='Space separated list of jars to be included')
157 parser.add_option('--output',
158 help='Output directory for generated files.')
159 parser.add_option('--app_abi', default='armeabi',
160 help='ABI for native shared library')
161 parser.add_option('--strip-binary',
162 help='Binary to use for stripping the native libraries.')
163 parser.add_option('--ant-args', action='append',
164 help='extra args for ant')
166 options, _ = parser.parse_args(argv)
168 # It is not an error to specify no native library; the apk should
169 # still be generated and build. It will, however, print
170 # NATIVE_LOADER_FAILED when run.
171 if not options.output:
172 raise Exception('No output directory specified for generated files')
174 if options.verbose:
175 logging.basicConfig(level=logging.DEBUG, format=' %(message)s')
177 if not options.strip_binary:
178 options.strip_binary = os.getenv('STRIP')
179 if not options.strip_binary:
180 raise Exception('No tool for stripping the libraries has been supplied')
182 # Remove all quotes from the jars string and pass the list to ant as
183 # INPUT_JARS_PATHS.
184 # TODO(cjhopman): Remove this when all targets pass the list of jars as an
185 # ant-arg directly.
186 jar_list = []
187 if options.jars:
188 jar_list = options.jars.replace('"', '').split()
189 options.ant_args.append('-DINPUT_JARS_PATHS=' + " ".join(jar_list))
192 ntag = NativeTestApkGenerator(native_library=options.native_library,
193 strip_binary=options.strip_binary,
194 output_directory=options.output,
195 target_abi=options.app_abi)
196 ntag.CreateBundle()
197 ntag.Compile(options.ant_args)
198 logging.info('COMPLETE.')
200 if __name__ == '__main__':
201 sys.exit(main(sys.argv))