Hi mclow.lists,

For accurate testing on Android, test compilation needs to more closely match 
the Android build system. This means flipping all the switches for 
-nostdlibinc, -nostdlib, etc. and instead using the libraries that were either 
prebuilt or built by the system.

Android's build system isn't able to efficiently handle many small builds run 
concurrently. The downside of this is that directly using the Android build 
system to compile each test cannot be done in parallel, and would take roughly 
7 hours to complete a test run.

To deal with this, when config.android is true, lit.cfg will pull all of the 
cppflags and ldflags right out of config instead of trying to determine them on 
its own. It's dirtied a little bit by crtbegin and crtend.

The other thing this patch does is deal with pushing the built test to the 
android device, running it on the device, and removing it when done. There are 
a few intricacies wrapped around `adb shell`, as adb considers all commands 
that were able to be run on the device, regardless of their exit codes, to be 
successful. As such, the commands are run as `adb shell COMMAND; echo $?`, and 
the last line of stdout is read to determine the real exit status.

http://reviews.llvm.org/D4594

Files:
  test/lit.cfg
Index: test/lit.cfg
===================================================================
--- test/lit.cfg
+++ test/lit.cfg
@@ -91,6 +91,27 @@
         # Evaluate the test.
         return self._evaluate_test(test, lit_config)
 
+    def _build(self, exec_path, source_path):
+        cmd = [self.cxx_under_test, '-o', exec_path,
+            source_path] + self.cpp_flags + self.ld_flags
+        out, err, exitCode = self.execute_command(cmd)
+        return cmd, out, err, exitCode
+
+    def _clean(self, exec_path):
+        os.remove(exec_path)
+
+    def _run(self, exec_path, lit_config, in_dir=None):
+        cmd = []
+        if self.exec_env:
+            cmd.append('env')
+            cmd.extend('%s=%s' % (name, value)
+                       for name, value in self.exec_env.items())
+        cmd.append(exec_path)
+        if lit_config.useValgrind:
+            cmd = lit_config.valgrindArgs + cmd
+        out, err, exitCode = self.execute_command(cmd, in_dir)
+        return cmd, out, err, exitCode
+
     def _evaluate_test(self, test, lit_config):
         name = test.path_in_suite[-1]
         source_path = test.getSourcePath()
@@ -102,9 +123,7 @@
 
         # If this is a compile (failure) test, build it and check for failure.
         if expected_compile_fail:
-            cmd = [self.cxx_under_test, '-c',
-                   '-o', '/dev/null', source_path] + self.cpp_flags
-            out, err, exitCode = self.execute_command(cmd)
+            cmd, out, err, exitCode = self._build('/dev/null', source_path)
             if exitCode == 1:
                 return lit.Test.PASS, ""
             else:
@@ -123,10 +142,8 @@
             exec_file.close()
 
             try:
-                compile_cmd = [self.cxx_under_test, '-o', exec_path,
-                       source_path] + self.cpp_flags + self.ld_flags
-                cmd = compile_cmd
-                out, err, exitCode = self.execute_command(cmd)
+                cmd, out, err, exitCode = self._build(exec_path, source_path)
+                compile_cmd = cmd
                 if exitCode != 0:
                     report = """Command: %s\n""" % ' '.join(["'%s'" % a
                                                              for a in cmd])
@@ -138,15 +155,8 @@
                     report += "\n\nCompilation failed unexpectedly!"
                     return lit.Test.FAIL, report
 
-                cmd = []
-                if self.exec_env:
-                    cmd.append('env')
-                    cmd.extend('%s=%s' % (name, value)
-                               for name,value in self.exec_env.items())
-                cmd.append(exec_path)
-                if lit_config.useValgrind:
-                    cmd = lit_config.valgrindArgs + cmd
-                out, err, exitCode = self.execute_command(cmd, source_dir)
+                cmd, out, err, exitCode = self._run(exec_path, lit_config,
+                                                    source_dir)
                 if exitCode != 0:
                     report = """Compiled With: %s\n""" % \
                         ' '.join(["'%s'" % a for a in compile_cmd])
@@ -161,11 +171,74 @@
                     return lit.Test.FAIL, report
             finally:
                 try:
-                    os.remove(exec_path)
+                    self._clean(exec_path)
                 except:
                     pass
         return lit.Test.PASS, ""
 
+
+class AndroidLibcxxTestFormat(LibcxxTestFormat):
+    def __init__(self, cxx_under_test, libcxx_src_root, libcxx_obj_root,
+                 cpp_flags, ld_flags, crtbegin, crtend, timeout):
+        self.cxx_under_test = cxx_under_test
+        self.libcxx_src_root = libcxx_src_root
+        self.libcxx_obj_root = libcxx_obj_root
+        self.cpp_flags = cpp_flags
+        self.ld_flags = ld_flags
+        self.crtbegin = crtbegin
+        self.crtend = crtend
+        self.timeout = timeout
+
+    def _build(self, exec_path, source_path):
+        cmd = ([self.cxx_under_test, '-o', exec_path] + self.cpp_flags +
+               [self.crtbegin, source_path] + self.ld_flags + [self.crtend])
+        try:
+            build_cmd = cmd
+            out, err, exit_code = self.execute_command(build_cmd)
+            if exit_code != 0:
+                return build_cmd, out, err, exit_code
+            exec_file = os.path.basename(exec_path)
+            device_path = os.path.join('/data/nativetest/', exec_file)
+            cmd = ['adb', 'push', exec_path, device_path]
+            out, err, exit_code = self.execute_command(cmd)
+            cmd = build_cmd + ['&&'] + cmd
+            return cmd, out, err, exit_code
+        except:
+            return cmd, out, err, exit_code
+
+    def _clean(self, exec_path):
+        exec_file = os.path.basename(exec_path)
+        device_path = os.path.join('/data/nativetest/', exec_file)
+        cmd = ['adb', 'shell', 'rm', device_path]
+        self.execute_command(cmd)
+        os.remove(exec_path)
+
+    def _run(self, exec_path, lit_config, in_dir=None):
+        exec_path = os.path.basename(exec_path)
+        device_path = os.path.join('/data/nativetest', exec_path)
+        shell_cmd = '{}; echo $?'.format(device_path)
+        cmd = ['timeout', self.timeout, 'adb', 'shell', shell_cmd]
+
+        # Tests will commonly fail with ETXTBSY. Possibly related to this bug:
+        # https://code.google.com/p/android/issues/detail?id=65857. Work around
+        # it by just waiting a second and then retrying.
+        for _ in range(10):
+            out, err, exit_code = self.execute_command(cmd)
+            if exit_code == 0:
+                if 'Text file busy' in out:
+                    time.sleep(1)
+                else:
+                    out = out.strip().split('\r\n')
+                    status_line = out[-1:][0]
+                    out = '\n'.join(out[:-1])
+                    exit_code = int(status_line)
+                    break
+            else:
+                err += '\nTimed out after {} seconds'.format(self.timeout)
+                break
+        return cmd, out, err, exit_code
+
+
 # name: The name of this test suite.
 config.name = 'libc++'
 
@@ -175,132 +248,161 @@
 # test_source_root: The root path where tests are located.
 config.test_source_root = os.path.dirname(__file__)
 
-# Gather various compiler parameters.
-cxx_under_test = lit_config.params.get('cxx_under_test', None)
-if cxx_under_test is None:
-    cxx_under_test = getattr(config, 'cxx_under_test', None)
-
-    # If no specific cxx_under_test was given, attempt to infer it as clang++.
+if getattr(config, 'android', False):
+    android_root = getattr(config, 'android_root', None)
+    if not android_root:
+        lit_config.fatal('config.android_root must be set')
+    cxx_under_test = lit_config.params.get('cxx_under_test', None)
     if cxx_under_test is None:
-        clangxx = lit.util.which('clang++', config.environment['PATH'])
-        if clangxx is not None:
-            cxx_under_test = clangxx
-    lit_config.note("inferred cxx_under_test as: %r" % (cxx_under_test,))
-if cxx_under_test is None:
-    lit_config.fatal('must specify user parameter cxx_under_test '
-                     '(e.g., --param=cxx_under_test=clang++)')
-
-libcxx_src_root = lit_config.params.get('libcxx_src_root', None)
-if libcxx_src_root is None:
-    libcxx_src_root = getattr(config, 'libcxx_src_root', None)
+        cxx_under_test = getattr(config, 'cxx_under_test', None)
+        if cxx_under_test is None:
+            lit_config.fatal('config.cxx_under_test must be set')
+    libcxx_src_root = lit_config.params.get('libcxx_src_root', None)
     if libcxx_src_root is None:
-        libcxx_src_root = os.path.dirname(config.test_source_root)
+        libcxx_src_root = getattr(config, 'libcxx_src_root', None)
+        if libcxx_src_root is None:
+            libcxx_src_root = os.path.dirname(config.test_source_root)
+    libcxx_obj_root = lit_config.params.get('libcxx_obj_root', None)
+    if libcxx_obj_root is None:
+        libcxx_obj_root = getattr(config, 'libcxx_obj_root', None)
+        if libcxx_obj_root is None:
+            libcxx_obj_root = libcxx_src_root
+    config.test_format = AndroidLibcxxTestFormat(
+        cxx_under_test,
+        libcxx_src_root,
+        libcxx_obj_root,
+        config.cppflags,
+        config.ldflags,
+        config.crtbegin,
+        config.crtend,
+        getattr(config, 'timeout', '30'))
+else:
+    # Gather various compiler parameters.
+    cxx_under_test = lit_config.params.get('cxx_under_test', None)
+    if cxx_under_test is None:
+        cxx_under_test = getattr(config, 'cxx_under_test', None)
+
+        # If no specific cxx_under_test was given, attempt to infer it as clang++.
+        if cxx_under_test is None:
+            clangxx = lit.util.which('clang++', config.environment['PATH'])
+            if clangxx is not None:
+                cxx_under_test = clangxx
+        lit_config.note("inferred cxx_under_test as: %r" % (cxx_under_test,))
+    if cxx_under_test is None:
+        lit_config.fatal('must specify user parameter cxx_under_test '
+                         '(e.g., --param=cxx_under_test=clang++)')
 
-libcxx_obj_root = lit_config.params.get('libcxx_obj_root', None)
-if libcxx_obj_root is None:
-    libcxx_obj_root = getattr(config, 'libcxx_obj_root', None)
+    libcxx_src_root = lit_config.params.get('libcxx_src_root', None)
+    if libcxx_src_root is None:
+        libcxx_src_root = getattr(config, 'libcxx_src_root', None)
+        if libcxx_src_root is None:
+            libcxx_src_root = os.path.dirname(config.test_source_root)
+
+    libcxx_obj_root = lit_config.params.get('libcxx_obj_root', None)
     if libcxx_obj_root is None:
-        libcxx_obj_root = libcxx_src_root
-
-cxx_has_stdcxx0x_flag_str = lit_config.params.get('cxx_has_stdcxx0x_flag', None)
-if cxx_has_stdcxx0x_flag_str is not None:
-    if cxx_has_stdcxx0x_flag_str.lower() in ('1', 'true'):
-        cxx_has_stdcxx0x_flag = True
-    elif cxx_has_stdcxx0x_flag_str.lower() in ('', '0', 'false'):
-        cxx_has_stdcxx0x_flag = False
+        libcxx_obj_root = getattr(config, 'libcxx_obj_root', None)
+        if libcxx_obj_root is None:
+            libcxx_obj_root = libcxx_src_root
+
+    cxx_has_stdcxx0x_flag_str = lit_config.params.get('cxx_has_stdcxx0x_flag', None)
+    if cxx_has_stdcxx0x_flag_str is not None:
+        if cxx_has_stdcxx0x_flag_str.lower() in ('1', 'true'):
+            cxx_has_stdcxx0x_flag = True
+        elif cxx_has_stdcxx0x_flag_str.lower() in ('', '0', 'false'):
+            cxx_has_stdcxx0x_flag = False
+        else:
+            lit_config.fatal(
+                'user parameter cxx_has_stdcxx0x_flag_str should be 0 or 1')
     else:
-        lit_config.fatal(
-            'user parameter cxx_has_stdcxx0x_flag_str should be 0 or 1')
-else:
-    cxx_has_stdcxx0x_flag = getattr(config, 'cxx_has_stdcxx0x_flag', True)
-
-# This test suite supports testing against either the system library or the
-# locally built one; the former mode is useful for testing ABI compatibility
-# between the current headers and a shipping dynamic library.
-use_system_lib_str = lit_config.params.get('use_system_lib', None)
-if use_system_lib_str is not None:
-    if use_system_lib_str.lower() in ('1', 'true'):
-        use_system_lib = True
-    elif use_system_lib_str.lower() in ('', '0', 'false'):
-        use_system_lib = False
+        cxx_has_stdcxx0x_flag = getattr(config, 'cxx_has_stdcxx0x_flag', True)
+
+    # This test suite supports testing against either the system library or the
+    # locally built one; the former mode is useful for testing ABI compatibility
+    # between the current headers and a shipping dynamic library.
+    use_system_lib_str = lit_config.params.get('use_system_lib', None)
+    if use_system_lib_str is not None:
+        if use_system_lib_str.lower() in ('1', 'true'):
+            use_system_lib = True
+        elif use_system_lib_str.lower() in ('', '0', 'false'):
+            use_system_lib = False
+        else:
+            lit_config.fatal('user parameter use_system_lib should be 0 or 1')
     else:
-        lit_config.fatal('user parameter use_system_lib should be 0 or 1')
-else:
-    # Default to testing against the locally built libc++ library.
-    use_system_lib = False
-    lit_config.note("inferred use_system_lib as: %r" % (use_system_lib,))
-
-link_flags = []
-link_flags_str = lit_config.params.get('link_flags', None)
-if link_flags_str is None:
-    link_flags_str = getattr(config, 'link_flags', None)
+        # Default to testing against the locally built libc++ library.
+        use_system_lib = False
+        lit_config.note("inferred use_system_lib as: %r" % (use_system_lib,))
+
+    link_flags = []
+    link_flags_str = lit_config.params.get('link_flags', None)
     if link_flags_str is None:
-      cxx_abi = getattr(config, 'cxx_abi', 'libcxxabi')
-      if cxx_abi == 'libstdc++':
-        link_flags += ['-lstdc++']
-      elif cxx_abi == 'libsupc++':
-        link_flags += ['-lsupc++']
-      elif cxx_abi == 'libcxxabi':
-        link_flags += ['-lc++abi']
-      elif cxx_abi == 'none':
-        pass
-      else:
-        lit_config.fatal('C++ ABI setting %s unsupported for tests' % cxx_abi)
-
-      if sys.platform == 'darwin':
-        link_flags += ['-lSystem']
-      elif sys.platform == 'linux2':
-        link_flags += [ '-lgcc_eh', '-lc', '-lm', '-lpthread',
-              '-lrt', '-lgcc_s']
-      else:
+        link_flags_str = getattr(config, 'link_flags', None)
+        if link_flags_str is None:
+          cxx_abi = getattr(config, 'cxx_abi', 'libcxxabi')
+          if cxx_abi == 'libstdc++':
+            link_flags += ['-lstdc++']
+          elif cxx_abi == 'libsupc++':
+            link_flags += ['-lsupc++']
+          elif cxx_abi == 'libcxxabi':
+            link_flags += ['-lc++abi']
+          elif cxx_abi == 'none':
+            pass
+          else:
+            lit_config.fatal('C++ ABI setting %s unsupported for tests' % cxx_abi)
+
+          if sys.platform == 'darwin':
+            link_flags += ['-lSystem']
+          elif sys.platform == 'linux2':
+            link_flags += [ '-lgcc_eh', '-lc', '-lm', '-lpthread',
+                  '-lrt', '-lgcc_s']
+          else:
+            lit_config.fatal("unrecognized system")
+
+          lit_config.note("inferred link_flags as: %r" % (link_flags,))
+    if not link_flags_str is None:
+        link_flags += shlex.split(link_flags_str)
+
+    # Configure extra compiler flags.
+    include_paths = ['-I' + libcxx_src_root + '/include',
+        '-I' + libcxx_src_root + '/test/support']
+    library_paths = ['-L' + libcxx_obj_root + '/lib']
+    compile_flags = []
+    if cxx_has_stdcxx0x_flag:
+        compile_flags += ['-std=c++0x']
+
+    # Configure extra linker parameters.
+    exec_env = {}
+    if sys.platform == 'darwin':
+        if not use_system_lib:
+            exec_env['DYLD_LIBRARY_PATH'] = os.path.join(libcxx_obj_root, 'lib')
+    elif sys.platform == 'linux2':
+        if not use_system_lib:
+            link_flags += ['-Wl,-R', libcxx_obj_root + '/lib']
+        compile_flags += ['-D__STDC_FORMAT_MACROS', '-D__STDC_LIMIT_MACROS',
+            '-D__STDC_CONSTANT_MACROS']
+    else:
         lit_config.fatal("unrecognized system")
 
-      lit_config.note("inferred link_flags as: %r" % (link_flags,))
-if not link_flags_str is None:
-    link_flags += shlex.split(link_flags_str)
-
-# Configure extra compiler flags.
-include_paths = ['-I' + libcxx_src_root + '/include',
-    '-I' + libcxx_src_root + '/test/support']
-library_paths = ['-L' + libcxx_obj_root + '/lib']
-compile_flags = []
-if cxx_has_stdcxx0x_flag:
-    compile_flags += ['-std=c++0x']
-
-# Configure extra linker parameters.
-exec_env = {}
-if sys.platform == 'darwin':
-    if not use_system_lib:
-        exec_env['DYLD_LIBRARY_PATH'] = os.path.join(libcxx_obj_root, 'lib')
-elif sys.platform == 'linux2':
-    if not use_system_lib:
-        link_flags += ['-Wl,-R', libcxx_obj_root + '/lib']
-    compile_flags += ['-D__STDC_FORMAT_MACROS', '-D__STDC_LIMIT_MACROS',
-        '-D__STDC_CONSTANT_MACROS']
-else:
-    lit_config.fatal("unrecognized system")
-
-config.test_format = LibcxxTestFormat(
-    cxx_under_test,
-    cpp_flags = ['-nostdinc++'] + compile_flags + include_paths,
-    ld_flags = ['-nodefaultlibs'] + library_paths + ['-lc++'] + link_flags,
-    exec_env = exec_env)
-
-# Get or infer the target triple.
-config.target_triple = lit_config.params.get('target_triple', None)
-# If no target triple was given, try to infer it from the compiler under test.
-if config.target_triple is None:
-    config.target_triple = lit.util.capture(
-        [cxx_under_test, '-dumpmachine']).strip()
-    lit_config.note("inferred target_triple as: %r" % (config.target_triple,))
-
-# Write an "available feature" that combines the triple when use_system_lib is
-# enabled. This is so that we can easily write XFAIL markers for tests that are
-# known to fail with versions of libc++ as were shipped with a particular
-# triple.
-if use_system_lib:
-    # Drop sub-major version components from the triple, because the current
-    # XFAIL handling expects exact matches for feature checks.
-    sanitized_triple = re.sub(r"([^-]+)-([^-]+)-([^-.]+).*", r"\1-\2-\3",
-                              config.target_triple)
-    config.available_features.add('with_system_lib=%s' % (sanitized_triple,))
+    config.test_format = LibcxxTestFormat(
+        cxx_under_test,
+        cpp_flags = ['-nostdinc++'] + compile_flags + include_paths,
+        ld_flags = ['-nodefaultlibs'] + library_paths + ['-lc++'] + link_flags,
+        exec_env = exec_env)
+
+    # Get or infer the target triple.
+    config.target_triple = lit_config.params.get('target_triple', None)
+    # If no target triple was given, try to infer it from the compiler under test.
+    if config.target_triple is None:
+        config.target_triple = lit.util.capture(
+            [cxx_under_test, '-dumpmachine']).strip()
+        lit_config.note("inferred target_triple as: %r" % (config.target_triple,))
+
+    # Write an "available feature" that combines the triple when use_system_lib is
+    # enabled. This is so that we can easily write XFAIL markers for tests that are
+    # known to fail with versions of libc++ as were shipped with a particular
+    # triple.
+    if use_system_lib:
+        # Drop sub-major version components from the triple, because the current
+        # XFAIL handling expects exact matches for feature checks.
+        sanitized_triple = re.sub(r"([^-]+)-([^-]+)-([^-.]+).*", r"\1-\2-\3",
+                                  config.target_triple)
+        config.available_features.add('with_system_lib=%s' % (sanitized_triple,))
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits

Reply via email to