Bug 1548555 - For android gtest, push support files to device; r=bc
authorGeoff Brown <gbrown@mozilla.com>
Fri, 03 May 2019 17:45:21 +0000
changeset 531360 4e998aa9c04bf1dbce2087bfcf91ce8292fb7855
parent 531359 914bcc5d82716ce7ae91b90202b754ead0db06aa
child 531361 349bb2c0cfac61fc2485865fcf1ecc30874c90ce
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbc
bugs1548555
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1548555 - For android gtest, push support files to device; r=bc Differential Revision: https://phabricator.services.mozilla.com/D29837
python/mozbuild/mozbuild/mach_commands.py
testing/gtest/mozilla/GTestRunner.cpp
testing/gtest/remotegtests.py
testing/mozharness/configs/android/android_common.py
testing/mozharness/scripts/android_emulator_unittest.py
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -647,42 +647,42 @@ class GTestCommands(MachCommandBase):
         if res:
             print("Could not build xul-gtest")
             return res
 
         if self.substs.get('MOZ_WIDGET_TOOLKIT') == 'cocoa':
             self._run_make(directory='browser/app', target='repackage',
                            ensure_exit_code=True)
 
+        cwd = os.path.join(self.topobjdir, '_tests', 'gtest')
+
+        if not os.path.isdir(cwd):
+            os.makedirs(cwd)
+
         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,
+            return self.android_gtest(cwd, 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'];
 
         if sys.platform.startswith('win') and \
             'MOZ_LAUNCHER_PROCESS' in self.defines:
             args.append('--wait-for-browser')
 
         if debug or debugger or debugger_args:
             args = self.prepend_debugger_args(args, debugger, debugger_args)
 
-        cwd = os.path.join(self.topobjdir, '_tests', 'gtest')
-
-        if not os.path.isdir(cwd):
-            os.makedirs(cwd)
-
         # Use GTest environment variable to control test execution
         # For details see:
         # https://code.google.com/p/googletest/wiki/AdvancedGuide#Running_Test_Programs:_Advanced_Options
         gtest_env = {b'GTEST_FILTER': gtest_filter}
 
         # Note: we must normalize the path here so that gtest on Windows sees
         # a MOZ_GMP_PATH which has only Windows dir seperators, because
         # nsIFile cannot open the paths with non-Windows dir seperators.
@@ -734,17 +734,17 @@ class GTestCommands(MachCommandBase):
 
         # Clamp error code to 255 to prevent overflowing multiple of
         # 256 into 0
         if exit_code > 255:
             exit_code = 255
 
         return exit_code
 
-    def android_gtest(self, shuffle, gtest_filter,
+    def android_gtest(self, test_dir, 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
@@ -759,18 +759,19 @@ class GTestCommands(MachCommandBase):
         # 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,
+        tester.run_gtest(test_dir, shuffle, gtest_filter, package, adb_path, device_serial,
                          remote_test_root, libxul_path, None)
+        tester.cleanup()
 
         return 0
 
     def prepend_debugger_args(self, args, debugger, debugger_args):
         '''
         Given an array with program arguments, prepend arguments to run it under a
         debugger.
 
--- a/testing/gtest/mozilla/GTestRunner.cpp
+++ b/testing/gtest/mozilla/GTestRunner.cpp
@@ -110,16 +110,27 @@ int RunGTestFunc(int* argc, char** argv)
 
   PR_SetEnv("XPCOM_DEBUG_BREAK=stack-and-abort");
 
   ScopedXPCOM xpcom("GTest");
 
 #ifdef XP_WIN
   mozilla::ipc::windows::InitUIThread();
 #endif
+#ifdef ANDROID
+  // On Android, gtest is running in an application, which uses a
+  // current working directory of '/' by default. Desktop tests
+  // sometimes assume that support files are in the current
+  // working directory. For compatibility with desktop, the Android
+  // harness pushes test support files to the device at the location
+  // specified by MOZ_GTEST_CWD and gtest changes the cwd to that
+  // location.
+  char* path = PR_GetEnv("MOZ_GTEST_CWD");
+  chdir(path);
+#endif
   nsCOMPtr<nsICrashReporter> crashreporter;
   char* crashreporterStr = PR_GetEnv("MOZ_CRASHREPORTER");
   if (crashreporterStr && !strcmp(crashreporterStr, "1")) {
     // TODO: move this to an even-more-common location to use in all
     // C++ unittests
     crashreporter = do_GetService("@mozilla.org/toolkit/crash-reporter;1");
     if (crashreporter) {
       printf_stderr("Setting up crash reporting\n");
--- a/testing/gtest/remotegtests.py
+++ b/testing/gtest/remotegtests.py
@@ -4,16 +4,17 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import with_statement
 
 from optparse import OptionParser
 
 import datetime
+import glob
 import os
 import posixpath
 import shutil
 import sys
 import tempfile
 import time
 
 import mozcrash
@@ -38,26 +39,27 @@ class RemoteGTests(object):
         env = {}
         env["XPCOM_DEBUG_BREAK"] = "stack-and-abort"
         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_CWD"] = self.remote_profile
         env["MOZ_GTEST_MINIDUMPS_PATH"] = self.remote_minidumps
         env["MOZ_IN_AUTOMATION"] = "1"
         if shuffle:
             env["GTEST_SHUFFLE"] = "True"
         if test_filter:
             env["GTEST_FILTER"] = test_filter
 
         return env
 
-    def run_gtest(self, shuffle, test_filter, package, adb_path, device_serial,
+    def run_gtest(self, test_dir, 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.
         """
         update_mozinfo()
         self.device = mozdevice.ADBDevice(adb=adb_path,
                                           device=device_serial,
@@ -78,16 +80,22 @@ class RemoteGTests(object):
             raise Exception("%s is not installed on this device" % self.package)
         if not self.device._have_root_shell:
             raise Exception("a device with a root shell is required to run Android gtest")
 
         # TODO -- consider packaging the gtest libxul.so in an apk
         remote = "/data/app/%s-1/lib/x86_64/" % self.package
         self.device.push(libxul_path, remote)
 
+        # Push support files to device. Avoid sub-directories so that libxul.so
+        # is not included.
+        for f in glob.glob(os.path.join(test_dir, "*")):
+            if not os.path.isdir(f):
+                self.device.push(f, self.remote_profile)
+
         env = self.build_environment(shuffle, test_filter)
         args = ["-unittest", "--gtest_death_test_style=threadsafe",
                 "-profile %s" % self.remote_profile]
         if 'geckoview' in self.package:
             activity = "TestRunnerActivity"
             self.device.launch_activity(self.package, activity_name=activity,
                                         e10s=False,  # gtest is non-e10s on desktop
                                         moz_env=env, extra_args=args)
@@ -329,16 +337,19 @@ class remoteGtestOptions(OptionParser):
                         dest="symbols_path",
                         default=None,
                         help="absolute path to directory containing breakpad "
                              "symbols, or the URL of a zip file containing symbols")
         self.add_option("--shuffle",
                         action="store_true",
                         default=False,
                         help="Randomize the execution order of tests.")
+        self.add_option("--tests-path",
+                        default=None,
+                        help="Path to gtest directory containing test support files.")
 
 
 def update_mozinfo():
     """
        Walk up directories to find mozinfo.json and update the info.
     """
     path = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
     dirs = set()
@@ -359,17 +370,18 @@ def main():
     if len(args) > 1:
         parser.error("only one test_filter is allowed")
         sys.exit(1)
     test_filter = args[0] if args else None
     tester = RemoteGTests()
     result = False
     try:
         device_exception = False
-        result = tester.run_gtest(options.shuffle, test_filter, options.package,
+        result = tester.run_gtest(options.tests_path,
+                                  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:
         log.error(str(e))
         if isinstance(e, mozdevice.ADBTimeoutError):
--- a/testing/mozharness/configs/android/android_common.py
+++ b/testing/mozharness/configs/android/android_common.py
@@ -328,16 +328,17 @@ config = {
             ],
         },
         "gtest": {
             "run_filename": "remotegtests.py",
             "testsdir": "gtest",
             "install": True,
             "options": [
                 "--symbols-path=%(symbols_path)s",
-                "--libxul=%(gtest_dir)s/libxul.so",
+                "--tests-path=%(gtest_dir)s",
+                "--libxul=%(gtest_dir)s/gtest_bin/gtest/libxul.so",
                 "--package=%(app)s",
                 "--deviceSerial=%(device_serial)s",
             ],
         },
 
     },  # end suite_definitions
 }
--- a/testing/mozharness/scripts/android_emulator_unittest.py
+++ b/testing/mozharness/scripts/android_emulator_unittest.py
@@ -242,18 +242,17 @@ class AndroidEmulatorTest(TestingMixin, 
             # marionette options
             'address': c.get('marionette_address'),
             'marionette_extra': c.get('marionette_extra', ''),
             'xpcshell_extra': c.get('xpcshell_extra', ''),
             'test_manifest': os.path.join(
                 dirs['abs_marionette_tests_dir'],
                 self.config.get('marionette_test_manifest', '')
             ),
-            'gtest_dir': os.path.join(dirs['abs_test_install_dir'], 'gtest',
-                                      'gtest_bin', 'gtest'),
+            'gtest_dir': os.path.join(dirs['abs_test_install_dir'], 'gtest'),
         }
 
         user_paths = self._get_mozharness_test_paths(self.test_suite)
 
         for option in self.config["suite_definitions"][self.test_suite]["options"]:
             opt = option.split('=')[0]
             # override configured chunk options with script args, if specified
             if opt in ('--this-chunk', '--total-chunks'):