1# -*- Python -*-
2
3# Configuration file for the 'lit' test runner.
4
5import os
6import platform
7import shlex
8import shutil
9import subprocess
10
11import lit.formats
12
13# name: The name of this test suite.
14config.name = 'lldb-api'
15
16# suffixes: A list of file extensions to treat as test files.
17config.suffixes = ['.py']
18
19# test_source_root: The root path where tests are located.
20config.test_source_root = os.path.dirname(__file__)
21
22# test_exec_root: The root path where tests should be run.
23config.test_exec_root = os.path.join(config.lldb_obj_root, 'test', 'API')
24
25def mkdir_p(path):
26  import errno
27  try:
28    os.makedirs(path)
29  except OSError as e:
30    if e.errno != errno.EEXIST:
31      raise
32  if not os.path.isdir(path):
33    raise OSError(errno.ENOTDIR, "%s is not a directory"%path)
34
35
36def find_sanitizer_runtime(name):
37  resource_dir = subprocess.check_output(
38      [config.cmake_cxx_compiler,
39       '-print-resource-dir']).decode('utf-8').strip()
40  return os.path.join(resource_dir, 'lib', 'darwin', name)
41
42
43def find_shlibpath_var():
44  if platform.system() in ['Linux', 'FreeBSD', 'NetBSD', 'SunOS']:
45    yield 'LD_LIBRARY_PATH'
46  elif platform.system() == 'Darwin':
47    yield 'DYLD_LIBRARY_PATH'
48  elif platform.system() == 'Windows':
49    yield 'PATH'
50
51
52# On macOS, we can't do the DYLD_INSERT_LIBRARIES trick with a shim python
53# binary as the ASan interceptors get loaded too late. Also, when SIP is
54# enabled, we can't inject libraries into system binaries at all, so we need a
55# copy of the "real" python to work with.
56def find_python_interpreter():
57  # Avoid doing any work if we already copied the binary.
58  copied_python = os.path.join(config.lldb_build_directory, 'copied-python')
59  if os.path.isfile(copied_python):
60    return copied_python
61
62  # Find the "real" python binary.
63  real_python = subprocess.check_output([
64      config.python_executable,
65      os.path.join(os.path.dirname(os.path.realpath(__file__)),
66                   'get_darwin_real_python.py')
67  ]).decode('utf-8').strip()
68
69  shutil.copy(real_python, copied_python)
70
71  # Now make sure the copied Python works. The Python in Xcode has a relative
72  # RPATH and cannot be copied.
73  try:
74    # We don't care about the output, just make sure it runs.
75    subprocess.check_output([copied_python, '-V'], stderr=subprocess.STDOUT)
76  except subprocess.CalledProcessError:
77    # The copied Python didn't work. Assume we're dealing with the Python
78    # interpreter in Xcode. Given that this is not a system binary SIP
79    # won't prevent us form injecting the interceptors so we get away with
80    # not copying the executable.
81    os.remove(copied_python)
82    return real_python
83
84  # The copied Python works.
85  return copied_python
86
87
88def is_configured(attr):
89  """Return the configuration attribute if it exists and None otherwise.
90
91  This allows us to check if the attribute exists before trying to access it."""
92  return getattr(config, attr, None)
93
94
95def delete_module_cache(path):
96  """Clean the module caches in the test build directory.
97
98  This is necessary in an incremental build whenever clang changes underneath,
99  so doing it once per lit.py invocation is close enough. """
100  if os.path.isdir(path):
101    print("Deleting module cache at %s." % path)
102    shutil.rmtree(path)
103
104if is_configured('llvm_use_sanitizer'):
105  if 'Address' in config.llvm_use_sanitizer:
106    config.environment['ASAN_OPTIONS'] = 'detect_stack_use_after_return=1'
107    if 'Darwin' in config.host_os and 'x86' in config.host_triple:
108      config.environment['DYLD_INSERT_LIBRARIES'] = find_sanitizer_runtime(
109          'libclang_rt.asan_osx_dynamic.dylib')
110
111  if 'Thread' in config.llvm_use_sanitizer:
112    if 'Darwin' in config.host_os and 'x86' in config.host_triple:
113      config.environment['DYLD_INSERT_LIBRARIES'] = find_sanitizer_runtime(
114          'libclang_rt.tsan_osx_dynamic.dylib')
115
116if 'DYLD_INSERT_LIBRARIES' in config.environment and platform.system() == 'Darwin':
117  config.python_executable = find_python_interpreter()
118
119# Shared library build of LLVM may require LD_LIBRARY_PATH or equivalent.
120if is_configured('shared_libs'):
121  for shlibpath_var in find_shlibpath_var():
122    # In stand-alone build llvm_shlib_dir specifies LLDB's lib directory while
123    # llvm_libs_dir specifies LLVM's lib directory.
124    shlibpath = os.path.pathsep.join(
125        (config.llvm_shlib_dir, config.llvm_libs_dir,
126         config.environment.get(shlibpath_var, '')))
127    config.environment[shlibpath_var] = shlibpath
128  else:
129    lit_config.warning("unable to inject shared library path on '{}'".format(
130        platform.system()))
131
132# Support running the test suite under the lldb-repro wrapper. This makes it
133# possible to capture a test suite run and then rerun all the test from the
134# just captured reproducer.
135lldb_repro_mode = lit_config.params.get('lldb-run-with-repro', None)
136if lldb_repro_mode:
137  lit_config.note("Running API tests in {} mode.".format(lldb_repro_mode))
138  mkdir_p(config.lldb_reproducer_directory)
139  if lldb_repro_mode == 'capture':
140    config.available_features.add('lldb-repro-capture')
141  elif lldb_repro_mode == 'replay':
142    config.available_features.add('lldb-repro-replay')
143
144lldb_use_simulator = lit_config.params.get('lldb-run-with-simulator', None)
145if lldb_use_simulator:
146  if lldb_use_simulator == "ios":
147    lit_config.note("Running API tests on iOS simulator")
148    config.available_features.add('lldb-simulator-ios')
149  elif lldb_use_simulator == "watchos":
150    lit_config.note("Running API tests on watchOS simulator")
151    config.available_features.add('lldb-simulator-watchos')
152  elif lldb_use_simulator == "tvos":
153    lit_config.note("Running API tests on tvOS simulator")
154    config.available_features.add('lldb-simulator-tvos')
155  else:
156    lit_config.error("Unknown simulator id '{}'".format(lldb_use_simulator))
157
158# Set a default per-test timeout of 10 minutes. Setting a timeout per test
159# requires that killProcessAndChildren() is supported on the platform and
160# lit complains if the value is set but it is not supported.
161supported, errormsg = lit_config.maxIndividualTestTimeIsSupported
162if supported:
163  lit_config.maxIndividualTestTime = 600
164else:
165  lit_config.warning("Could not set a default per-test timeout. " + errormsg)
166
167# Build dotest command.
168dotest_cmd = [os.path.join(config.lldb_src_root, 'test', 'API', 'dotest.py')]
169
170if is_configured('dotest_args_str'):
171  dotest_cmd.extend(config.dotest_args_str.split(';'))
172
173# Library path may be needed to locate just-built clang.
174if is_configured('llvm_libs_dir'):
175  dotest_cmd += ['--env', 'LLVM_LIBS_DIR=' + config.llvm_libs_dir]
176
177# Forward ASan-specific environment variables to tests, as a test may load an
178# ASan-ified dylib.
179for env_var in ('ASAN_OPTIONS', 'DYLD_INSERT_LIBRARIES'):
180  if env_var in config.environment:
181    dotest_cmd += ['--inferior-env', env_var + '=' + config.environment[env_var]]
182
183if is_configured('test_arch'):
184  dotest_cmd += ['--arch', config.test_arch]
185
186if is_configured('lldb_build_directory'):
187  dotest_cmd += ['--build-dir', config.lldb_build_directory]
188
189if is_configured('lldb_module_cache'):
190  delete_module_cache(config.lldb_module_cache)
191  dotest_cmd += ['--lldb-module-cache-dir', config.lldb_module_cache]
192
193if is_configured('clang_module_cache'):
194  delete_module_cache(config.clang_module_cache)
195  dotest_cmd += ['--clang-module-cache-dir', config.clang_module_cache]
196
197if is_configured('lldb_executable'):
198  dotest_cmd += ['--executable', config.lldb_executable]
199
200if is_configured('test_compiler'):
201  dotest_cmd += ['--compiler', config.test_compiler]
202
203if is_configured('dsymutil'):
204  dotest_cmd += ['--dsymutil', config.dsymutil]
205
206if is_configured('llvm_tools_dir'):
207  dotest_cmd += ['--llvm-tools-dir', config.llvm_tools_dir]
208
209if is_configured('server'):
210  dotest_cmd += ['--server', config.server]
211
212if is_configured('lldb_libs_dir'):
213  dotest_cmd += ['--lldb-libs-dir', config.lldb_libs_dir]
214
215if is_configured('lldb_framework_dir'):
216  dotest_cmd += ['--framework', config.lldb_framework_dir]
217
218if 'lldb-repro-capture' in config.available_features or \
219    'lldb-repro-replay' in config.available_features:
220  dotest_cmd += ['--skip-category=lldb-vscode', '--skip-category=std-module']
221
222if 'lldb-simulator-ios' in config.available_features:
223  dotest_cmd += ['--apple-sdk', 'iphonesimulator',
224                 '--platform-name', 'ios-simulator']
225elif 'lldb-simulator-watchos' in config.available_features:
226  dotest_cmd += ['--apple-sdk', 'watchsimulator',
227                 '--platform-name', 'watchos-simulator']
228elif 'lldb-simulator-tvos' in config.available_features:
229  dotest_cmd += ['--apple-sdk', 'appletvsimulator',
230                 '--platform-name', 'tvos-simulator']
231
232if is_configured('enabled_plugins'):
233  for plugin in config.enabled_plugins:
234    dotest_cmd += ['--enable-plugin', plugin]
235
236if is_configured('dotest_lit_args_str'):
237  # We don't want to force users passing arguments to lit to use `;` as a
238  # separator. We use Python's simple lexical analyzer to turn the args into a
239  # list. Pass there arguments last so they can override anything that was
240  # already configured.
241  dotest_cmd.extend(shlex.split(config.dotest_lit_args_str))
242
243# Load LLDB test format.
244sys.path.append(os.path.join(config.lldb_src_root, "test", "API"))
245import lldbtest
246
247# testFormat: The test format to use to interpret tests.
248config.test_format = lldbtest.LLDBTest(dotest_cmd)
249
250# Propagate FREEBSD_LEGACY_PLUGIN
251if 'FREEBSD_LEGACY_PLUGIN' in os.environ:
252  config.environment['FREEBSD_LEGACY_PLUGIN'] = os.environ[
253      'FREEBSD_LEGACY_PLUGIN']
254
255# Propagate XDG_CACHE_HOME
256if 'XDG_CACHE_HOME' in os.environ:
257  config.environment['XDG_CACHE_HOME'] = os.environ['XDG_CACHE_HOME']
258