diff --git a/python/mozbuild/mozbuild/mach_commands.py b/python/mozbuild/mozbuild/mach_commands.py index 03d6c49c5d61..86049d86be6f 100644 --- a/python/mozbuild/mozbuild/mach_commands.py +++ b/python/mozbuild/mozbuild/mach_commands.py @@ -597,6 +597,25 @@ class GTestCommands(MachCommandBase): @CommandArgument('--shuffle', '-s', action='store_true', help='Randomize the execution order of tests.') + @CommandArgument('--package', + default='org.mozilla.geckoview.test', + help='(Android only) Package name of test app.') + @CommandArgument('--adbpath', + dest='adb_path', + help='(Android only) Path to adb binary.') + @CommandArgument('--deviceSerial', + dest='device_serial', + help="(Android only) adb serial number of remote device. " + "Required when more than one device is connected to the host. " + "Use 'adb devices' to see connected devices.") + @CommandArgument('--remoteTestRoot', + dest='remote_test_root', + help='(Android only) Remote directory to use as test root ' + '(eg. /mnt/sdcard/tests or /data/local/tests).') + @CommandArgument('--libxul', + dest='libxul_path', + help='(Android only) Path to gtest libxul.so.') + @CommandArgumentGroup('debugging') @CommandArgument('--debug', action='store_true', group='debugging', help='Enable the debugger. Not specifying a --debugger option will result in the default debugger being used.') @@ -606,8 +625,9 @@ class GTestCommands(MachCommandBase): group='debugging', help='Command-line arguments to pass to the debugger itself; split as the Bourne shell would.') - def gtest(self, shuffle, jobs, gtest_filter, tbpl_parser, debug, debugger, - debugger_args): + def gtest(self, shuffle, jobs, gtest_filter, tbpl_parser, + package, adb_path, device_serial, remote_test_root, libxul_path, + debug, debugger, debugger_args): # We lazy build gtest because it's slow to link try: @@ -632,6 +652,17 @@ class GTestCommands(MachCommandBase): self._run_make(directory='browser/app', target='repackage', ensure_exit_code=True) + if conditions.is_android(self): + if jobs != 1: + print("--jobs is not supported on Android and will be ignored") + if debug or debugger or debugger_args: + print("--debug options are not supported on Android and will be ignored") + return self.android_gtest(shuffle, gtest_filter, + package, adb_path, device_serial, remote_test_root, libxul_path) + + if package or adb_path or device_serial or remote_test_root or libxul_path: + print("One or more Android-only options will be ignored") + app_path = self.get_binary_path('app') args = [app_path, '-unittest', '--gtest_death_test_style=threadsafe']; @@ -708,6 +739,36 @@ class GTestCommands(MachCommandBase): return exit_code + def android_gtest(self, shuffle, gtest_filter, + package, adb_path, device_serial, remote_test_root, libxul_path): + # setup logging for mozrunner + from mozlog.commandline import setup_logging + format_args = {'level': self._mach_context.settings['test']['level']} + default_format = self._mach_context.settings['test']['format'] + log = setup_logging('mach-gtest', {}, {default_format: sys.stdout}, format_args) + + # ensure that a device is available and test app is installed + from mozrunner.devices.android_device import (verify_android_device, get_adb_path) + verify_android_device(self, install=True, app=package, device_serial=device_serial) + + if not adb_path: + adb_path = get_adb_path(self) + if not libxul_path: + libxul_path = os.path.join(self.topobjdir, "dist", "bin", "gtest", "libxul.so") + + # run gtest via remotegtests.py + import imp + path = os.path.join('testing', 'gtest', 'remotegtests.py') + with open(path, 'r') as fh: + imp.load_module('remotegtests', fh, path, + ('.py', 'r', imp.PY_SOURCE)) + import remotegtests + tester = remotegtests.RemoteGTests() + tester.run_gtest(shuffle, gtest_filter, package, adb_path, device_serial, + remote_test_root, libxul_path, None) + + return 0 + def prepend_debugger_args(self, args, debugger, debugger_args): ''' Given an array with program arguments, prepend arguments to run it under a diff --git a/testing/gtest/remotegtests.py b/testing/gtest/remotegtests.py index ee3da8f92a91..52caaa71ce93 100644 --- a/testing/gtest/remotegtests.py +++ b/testing/gtest/remotegtests.py @@ -30,7 +30,7 @@ class RemoteGTests(object): A test harness to run gtest on Android. """ - def build_environment(self, options, test_filter): + def build_environment(self, shuffle, test_filter): """ Create and return a dictionary of all the appropriate env variables and values. @@ -40,32 +40,35 @@ class RemoteGTests(object): env["MOZ_CRASHREPORTER_NO_REPORT"] = "1" env["MOZ_CRASHREPORTER"] = "1" env["MOZ_RUN_GTEST"] = "1" + # custom output parser is mandatory on Android env["MOZ_TBPL_PARSER"] = "1" env["MOZ_GTEST_LOG_PATH"] = self.remote_log env["MOZ_GTEST_MINIDUMPS_PATH"] = self.remote_minidumps env["MOZ_IN_AUTOMATION"] = "1" - if options.shuffle: + if shuffle: env["GTEST_SHUFFLE"] = "True" if test_filter: env["GTEST_FILTER"] = test_filter return env - def run_gtest(self, options, test_filter): + def run_gtest(self, shuffle, test_filter, package, adb_path, device_serial, + remote_test_root, libxul_path, symbols_path): """ Launch the test app, run gtest, collect test results and wait for completion. Return False if a crash or other failure is detected, else True. """ - self.device = mozdevice.ADBDevice(adb=options.adb_path, - device=options.device_serial, - test_root=options.test_root, + update_mozinfo() + self.device = mozdevice.ADBDevice(adb=adb_path, + device=device_serial, + test_root=remote_test_root, logger_name=LOGGER_NAME, verbose=True) root = self.device.test_root self.remote_profile = posixpath.join(root, 'gtest-profile') self.remote_minidumps = posixpath.join(root, 'gtest-minidumps') self.remote_log = posixpath.join(root, 'gtest.log') - self.package = options.package + self.package = package self.cleanup() self.device.mkdir(self.remote_profile, parents=True) self.device.mkdir(self.remote_minidumps, parents=True) @@ -78,9 +81,9 @@ class RemoteGTests(object): # TODO -- consider packaging the gtest libxul.so in an apk remote = "/data/app/%s-1/lib/x86_64/" % self.package - self.device.push(options.libxul_path, remote) + self.device.push(libxul_path, remote) - env = self.build_environment(options, test_filter) + env = self.build_environment(shuffle, test_filter) args = ["-unittest", "--gtest_death_test_style=threadsafe", "-profile %s" % self.remote_profile] if 'geckoview' in self.package: @@ -93,7 +96,7 @@ class RemoteGTests(object): waiter = AppWaiter(self.device, self.remote_log) timed_out = waiter.wait(self.package) self.shutdown(use_kill=True if timed_out else False) - if self.check_for_crashes(options.symbols_path): + if self.check_for_crashes(symbols_path): return False return True @@ -313,7 +316,7 @@ class remoteGtestOptions(OptionParser): self.add_option("--remoteTestRoot", action="store", type=str, - dest="test_root", + dest="remote_test_root", help="Remote directory to use as test root " "(eg. /mnt/sdcard/tests or /data/local/tests).") self.add_option("--libxul", @@ -357,12 +360,14 @@ def main(): parser.error("only one test_filter is allowed") sys.exit(1) test_filter = args[0] if args else None - update_mozinfo() tester = RemoteGTests() result = False try: device_exception = False - result = tester.run_gtest(options, test_filter) + result = tester.run_gtest(options.shuffle, test_filter, options.package, + options.adb_path, options.device_serial, + options.remote_test_root, options.libxul_path, + options.symbols_path) except KeyboardInterrupt: log.info("gtest | Received keyboard interrupt") except Exception as e: