xref: /llvm-project/lldb/packages/Python/lldbsuite/test/dotest.py (revision ab755e65629ea098cb6faa77b13ac087849ffc67)
1"""
2A simple testing framework for lldb using python's unit testing framework.
3
4Tests for lldb are written as python scripts which take advantage of the script
5bridging provided by LLDB.framework to interact with lldb core.
6
7A specific naming pattern is followed by the .py script to be recognized as
8a module which implements a test scenario, namely, Test*.py.
9
10To specify the directories where "Test*.py" python test scripts are located,
11you need to pass in a list of directory names.  By default, the current
12working directory is searched if nothing is specified on the command line.
13
14Type:
15
16./dotest.py -h
17
18for available options.
19"""
20
21from __future__ import absolute_import
22from __future__ import print_function
23
24# System modules
25import atexit
26import datetime
27import errno
28import logging
29import os
30import platform
31import re
32import shutil
33import signal
34import subprocess
35import sys
36import tempfile
37
38# Third-party modules
39import unittest2
40
41# LLDB Modules
42import lldbsuite
43from . import configuration
44from . import dotest_args
45from . import lldbtest_config
46from . import test_categories
47from . import test_result
48from ..support import seven
49
50
51def is_exe(fpath):
52    """Returns true if fpath is an executable."""
53    if fpath == None:
54        return False
55    if sys.platform == 'win32':
56        if not fpath.endswith(".exe"):
57            fpath += ".exe"
58    return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
59
60
61def which(program):
62    """Returns the full path to a program; None otherwise."""
63    fpath, _ = os.path.split(program)
64    if fpath:
65        if is_exe(program):
66            return program
67    else:
68        for path in os.environ["PATH"].split(os.pathsep):
69            exe_file = os.path.join(path, program)
70            if is_exe(exe_file):
71                return exe_file
72    return None
73
74
75def usage(parser):
76    parser.print_help()
77    if configuration.verbose > 0:
78        print("""
79Examples:
80
81This is an example of using the -f option to pinpoint to a specific test class
82and test method to be run:
83
84$ ./dotest.py -f ClassTypesTestCase.test_with_dsym_and_run_command
85----------------------------------------------------------------------
86Collected 1 test
87
88test_with_dsym_and_run_command (TestClassTypes.ClassTypesTestCase)
89Test 'frame variable this' when stopped on a class constructor. ... ok
90
91----------------------------------------------------------------------
92Ran 1 test in 1.396s
93
94OK
95
96And this is an example of using the -p option to run a single file (the filename
97matches the pattern 'ObjC' and it happens to be 'TestObjCMethods.py'):
98
99$ ./dotest.py -v -p ObjC
100----------------------------------------------------------------------
101Collected 4 tests
102
103test_break_with_dsym (TestObjCMethods.FoundationTestCase)
104Test setting objc breakpoints using '_regexp-break' and 'breakpoint set'. ... ok
105test_break_with_dwarf (TestObjCMethods.FoundationTestCase)
106Test setting objc breakpoints using '_regexp-break' and 'breakpoint set'. ... ok
107test_data_type_and_expr_with_dsym (TestObjCMethods.FoundationTestCase)
108Lookup objective-c data types and evaluate expressions. ... ok
109test_data_type_and_expr_with_dwarf (TestObjCMethods.FoundationTestCase)
110Lookup objective-c data types and evaluate expressions. ... ok
111
112----------------------------------------------------------------------
113Ran 4 tests in 16.661s
114
115OK
116
117Running of this script also sets up the LLDB_TEST environment variable so that
118individual test cases can locate their supporting files correctly.  The script
119tries to set up Python's search paths for modules by looking at the build tree
120relative to this script.  See also the '-i' option in the following example.
121
122Finally, this is an example of using the lldb.py module distributed/installed by
123Xcode4 to run against the tests under the 'forward' directory, and with the '-w'
124option to add some delay between two tests.  It uses ARCH=x86_64 to specify that
125as the architecture and CC=clang to specify the compiler used for the test run:
126
127$ PYTHONPATH=/Xcode4/Library/PrivateFrameworks/LLDB.framework/Versions/A/Resources/Python ARCH=x86_64 CC=clang ./dotest.py -v -w -i forward
128
129Session logs for test failures/errors will go into directory '2010-11-11-13_56_16'
130----------------------------------------------------------------------
131Collected 2 tests
132
133test_with_dsym_and_run_command (TestForwardDeclaration.ForwardDeclarationTestCase)
134Display *bar_ptr when stopped on a function with forward declaration of struct bar. ... ok
135test_with_dwarf_and_run_command (TestForwardDeclaration.ForwardDeclarationTestCase)
136Display *bar_ptr when stopped on a function with forward declaration of struct bar. ... ok
137
138----------------------------------------------------------------------
139Ran 2 tests in 5.659s
140
141OK
142
143The 'Session ...' verbiage is recently introduced (see also the '-s' option) to
144notify the directory containing the session logs for test failures or errors.
145In case there is any test failure/error, a similar message is appended at the
146end of the stderr output for your convenience.
147
148ENABLING LOGS FROM TESTS
149
150Option 1:
151
152Writing logs into different files per test case::
153
154$ ./dotest.py --channel "lldb all"
155
156$ ./dotest.py --channel "lldb all" --channel "gdb-remote packets"
157
158These log files are written to:
159
160<session-dir>/<test-id>-host.log (logs from lldb host process)
161<session-dir>/<test-id>-server.log (logs from debugserver/lldb-server)
162<session-dir>/<test-id>-<test-result>.log (console logs)
163
164By default, logs from successful runs are deleted.  Use the --log-success flag
165to create reference logs for debugging.
166
167$ ./dotest.py --log-success
168
169""")
170    sys.exit(0)
171
172
173def parseExclusion(exclusion_file):
174    """Parse an exclusion file, of the following format, where
175       'skip files', 'skip methods', 'xfail files', and 'xfail methods'
176       are the possible list heading values:
177
178       skip files
179       <file name>
180       <file name>
181
182       xfail methods
183       <method name>
184    """
185    excl_type = None
186
187    with open(exclusion_file) as f:
188        for line in f:
189            line = line.strip()
190            if not excl_type:
191                excl_type = line
192                continue
193
194            if not line:
195                excl_type = None
196            elif excl_type == 'skip':
197                if not configuration.skip_tests:
198                    configuration.skip_tests = []
199                configuration.skip_tests.append(line)
200            elif excl_type == 'xfail':
201                if not configuration.xfail_tests:
202                    configuration.xfail_tests = []
203                configuration.xfail_tests.append(line)
204
205
206def parseOptionsAndInitTestdirs():
207    """Initialize the list of directories containing our unittest scripts.
208
209    '-h/--help as the first option prints out usage info and exit the program.
210    """
211
212    do_help = False
213
214    platform_system = platform.system()
215    platform_machine = platform.machine()
216
217    try:
218        parser = dotest_args.create_parser()
219        args = parser.parse_args()
220    except:
221        raise
222
223    if args.unset_env_varnames:
224        for env_var in args.unset_env_varnames:
225            if env_var in os.environ:
226                # From Python Doc: When unsetenv() is supported, deletion of items in os.environ
227                # is automatically translated into a corresponding call to
228                # unsetenv().
229                del os.environ[env_var]
230                # os.unsetenv(env_var)
231
232    if args.set_env_vars:
233        for env_var in args.set_env_vars:
234            parts = env_var.split('=', 1)
235            if len(parts) == 1:
236                os.environ[parts[0]] = ""
237            else:
238                os.environ[parts[0]] = parts[1]
239
240    if args.set_inferior_env_vars:
241        lldbtest_config.inferior_env = ' '.join(args.set_inferior_env_vars)
242
243    if args.h:
244        do_help = True
245
246    if args.compiler:
247        configuration.compiler = os.path.abspath(args.compiler)
248        if not is_exe(configuration.compiler):
249            configuration.compiler = which(args.compiler)
250        if not is_exe(configuration.compiler):
251            logging.error(
252                    '%s is not a valid compiler executable; aborting...',
253                    args.compiler)
254            sys.exit(-1)
255    else:
256        # Use a compiler appropriate appropriate for the Apple SDK if one was
257        # specified
258        if platform_system == 'Darwin' and args.apple_sdk:
259            configuration.compiler = seven.get_command_output(
260                'xcrun -sdk "%s" -find clang 2> /dev/null' %
261                (args.apple_sdk))
262        else:
263            # 'clang' on ubuntu 14.04 is 3.4 so we try clang-3.5 first
264            candidateCompilers = ['clang-3.5', 'clang', 'gcc']
265            for candidate in candidateCompilers:
266                if which(candidate):
267                    configuration.compiler = candidate
268                    break
269
270    if args.dsymutil:
271        configuration.dsymutil = args.dsymutil
272    elif platform_system == 'Darwin':
273        configuration.dsymutil = seven.get_command_output(
274            'xcrun -find -toolchain default dsymutil')
275    if args.llvm_tools_dir:
276        configuration.filecheck = shutil.which("FileCheck", path=args.llvm_tools_dir)
277        configuration.yaml2obj = shutil.which("yaml2obj", path=args.llvm_tools_dir)
278
279    if not configuration.get_filecheck_path():
280        logging.warning('No valid FileCheck executable; some tests may fail...')
281        logging.warning('(Double-check the --llvm-tools-dir argument to dotest.py)')
282
283    configuration.libcxx_include_dir = args.libcxx_include_dir
284    configuration.libcxx_library_dir = args.libcxx_library_dir
285    if args.libcxx_include_dir or args.libcxx_library_dir:
286        if args.lldb_platform_name:
287            logging.warning('Custom libc++ is not supported for remote runs: ignoring --libcxx arguments')
288        elif args.libcxx_include_dir and args.libcxx_library_dir:
289            configuration.libcxx_include_dir = args.libcxx_include_dir
290            configuration.libcxx_library_dir = args.libcxx_library_dir
291        else:
292            logging.error('Custom libc++ requires both --libcxx-include-dir and --libcxx-library-dir')
293            sys.exit(-1)
294
295    if args.channels:
296        lldbtest_config.channels = args.channels
297
298    if args.log_success:
299        lldbtest_config.log_success = args.log_success
300
301    if args.out_of_tree_debugserver:
302        lldbtest_config.out_of_tree_debugserver = args.out_of_tree_debugserver
303
304    # Set SDKROOT if we are using an Apple SDK
305    if platform_system == 'Darwin' and args.apple_sdk:
306        configuration.sdkroot = seven.get_command_output(
307            'xcrun --sdk "%s" --show-sdk-path 2> /dev/null' %
308            (args.apple_sdk))
309        if not configuration.sdkroot:
310            logging.error(
311                    'No SDK found with the name %s; aborting...',
312                    args.apple_sdk)
313            sys.exit(-1)
314
315    if args.arch:
316        configuration.arch = args.arch
317    else:
318        configuration.arch = platform_machine
319
320    if args.categories_list:
321        configuration.categories_list = set(
322            test_categories.validate(
323                args.categories_list, False))
324        configuration.use_categories = True
325    else:
326        configuration.categories_list = []
327
328    if args.skip_categories:
329        configuration.skip_categories += test_categories.validate(
330            args.skip_categories, False)
331
332    if args.xfail_categories:
333        configuration.xfail_categories += test_categories.validate(
334            args.xfail_categories, False)
335
336    if args.E:
337        os.environ['CFLAGS_EXTRAS'] = args.E
338
339    if args.dwarf_version:
340        configuration.dwarf_version = args.dwarf_version
341        # We cannot modify CFLAGS_EXTRAS because they're used in test cases
342        # that explicitly require no debug info.
343        os.environ['CFLAGS'] = '-gdwarf-{}'.format(configuration.dwarf_version)
344
345    if args.settings:
346        for setting in args.settings:
347            if not len(setting) == 1 or not setting[0].count('='):
348                logging.error('"%s" is not a setting in the form "key=value"',
349                              setting[0])
350                sys.exit(-1)
351            setting_list = setting[0].split('=', 1)
352            configuration.settings.append((setting_list[0], setting_list[1]))
353
354    if args.d:
355        sys.stdout.write(
356            "Suspending the process %d to wait for debugger to attach...\n" %
357            os.getpid())
358        sys.stdout.flush()
359        os.kill(os.getpid(), signal.SIGSTOP)
360
361    if args.f:
362        if any([x.startswith('-') for x in args.f]):
363            usage(parser)
364        configuration.filters.extend(args.f)
365
366    if args.framework:
367        configuration.lldb_framework_path = args.framework
368
369    if args.executable:
370        # lldb executable is passed explicitly
371        lldbtest_config.lldbExec = os.path.abspath(args.executable)
372        if not is_exe(lldbtest_config.lldbExec):
373            lldbtest_config.lldbExec = which(args.executable)
374        if not is_exe(lldbtest_config.lldbExec):
375            logging.error(
376                    '%s is not a valid executable to test; aborting...',
377                    args.executable)
378            sys.exit(-1)
379
380    if args.excluded:
381        for excl_file in args.excluded:
382            parseExclusion(excl_file)
383
384    if args.p:
385        if args.p.startswith('-'):
386            usage(parser)
387        configuration.regexp = args.p
388
389    if args.t:
390        os.environ['LLDB_COMMAND_TRACE'] = 'YES'
391
392    if args.v:
393        configuration.verbose = 2
394
395    # argparse makes sure we have a number
396    if args.sharp:
397        configuration.count = args.sharp
398
399    if sys.platform.startswith('win32'):
400        os.environ['LLDB_DISABLE_CRASH_DIALOG'] = str(
401            args.disable_crash_dialog)
402        os.environ['LLDB_LAUNCH_INFERIORS_WITHOUT_CONSOLE'] = str(True)
403
404    if do_help:
405        usage(parser)
406
407    if args.lldb_platform_name:
408        configuration.lldb_platform_name = args.lldb_platform_name
409    if args.lldb_platform_url:
410        configuration.lldb_platform_url = args.lldb_platform_url
411    if args.lldb_platform_working_dir:
412        configuration.lldb_platform_working_dir = args.lldb_platform_working_dir
413    if platform_system == 'Darwin'  and args.apple_sdk:
414        configuration.apple_sdk = args.apple_sdk
415    if args.test_build_dir:
416        configuration.test_build_dir = args.test_build_dir
417    if args.lldb_module_cache_dir:
418        configuration.lldb_module_cache_dir = args.lldb_module_cache_dir
419    else:
420        configuration.lldb_module_cache_dir = os.path.join(
421            configuration.test_build_dir, 'module-cache-lldb')
422    if args.clang_module_cache_dir:
423        configuration.clang_module_cache_dir = args.clang_module_cache_dir
424    else:
425        configuration.clang_module_cache_dir = os.path.join(
426            configuration.test_build_dir, 'module-cache-clang')
427
428    if args.lldb_libs_dir:
429        configuration.lldb_libs_dir = args.lldb_libs_dir
430
431    if args.enabled_plugins:
432        configuration.enabled_plugins = args.enabled_plugins
433
434    # Gather all the dirs passed on the command line.
435    if len(args.args) > 0:
436        configuration.testdirs = [os.path.realpath(os.path.abspath(x)) for x in args.args]
437
438    lldbtest_config.codesign_identity = args.codesign_identity
439
440def registerFaulthandler():
441    try:
442        import faulthandler
443    except ImportError:
444        # faulthandler is not available until python3
445        return
446
447    faulthandler.enable()
448    # faulthandler.register is not available on Windows.
449    if getattr(faulthandler, 'register', None):
450        faulthandler.register(signal.SIGTERM, chain=True)
451
452def setupSysPath():
453    """
454    Add LLDB.framework/Resources/Python to the search paths for modules.
455    As a side effect, we also discover the 'lldb' executable and export it here.
456    """
457
458    # Get the directory containing the current script.
459    if "DOTEST_PROFILE" in os.environ and "DOTEST_SCRIPT_DIR" in os.environ:
460        scriptPath = os.environ["DOTEST_SCRIPT_DIR"]
461    else:
462        scriptPath = os.path.dirname(os.path.abspath(__file__))
463    if not scriptPath.endswith('test'):
464        print("This script expects to reside in lldb's test directory.")
465        sys.exit(-1)
466
467    os.environ["LLDB_TEST"] = scriptPath
468
469    # Set up the root build directory.
470    if not configuration.test_build_dir:
471        raise Exception("test_build_dir is not set")
472    configuration.test_build_dir = os.path.abspath(configuration.test_build_dir)
473
474    # Set up the LLDB_SRC environment variable, so that the tests can locate
475    # the LLDB source code.
476    os.environ["LLDB_SRC"] = lldbsuite.lldb_root
477
478    pluginPath = os.path.join(scriptPath, 'plugins')
479    toolsLLDBVSCode = os.path.join(scriptPath, 'tools', 'lldb-vscode')
480    toolsLLDBServerPath = os.path.join(scriptPath, 'tools', 'lldb-server')
481    intelpt = os.path.join(scriptPath, 'tools', 'intelpt')
482
483    # Insert script dir, plugin dir and lldb-server dir to the sys.path.
484    sys.path.insert(0, pluginPath)
485    # Adding test/tools/lldb-vscode to the path makes it easy to
486    # "import lldb_vscode_testcase" from the VSCode tests
487    sys.path.insert(0, toolsLLDBVSCode)
488    # Adding test/tools/lldb-server to the path makes it easy
489    # to "import lldbgdbserverutils" from the lldb-server tests
490    sys.path.insert(0, toolsLLDBServerPath)
491    # Adding test/tools/intelpt to the path makes it easy
492    # to "import intelpt_testcase" from the lldb-server tests
493    sys.path.insert(0, intelpt)
494
495    # This is the root of the lldb git/svn checkout
496    # When this changes over to a package instead of a standalone script, this
497    # will be `lldbsuite.lldb_root`
498    lldbRootDirectory = lldbsuite.lldb_root
499
500    # Some of the tests can invoke the 'lldb' command directly.
501    # We'll try to locate the appropriate executable right here.
502
503    # The lldb executable can be set from the command line
504    # if it's not set, we try to find it now
505    # first, we try the environment
506    if not lldbtest_config.lldbExec:
507        # First, you can define an environment variable LLDB_EXEC specifying the
508        # full pathname of the lldb executable.
509        if "LLDB_EXEC" in os.environ:
510            lldbtest_config.lldbExec = os.environ["LLDB_EXEC"]
511
512    if not lldbtest_config.lldbExec:
513        # Last, check the path
514        lldbtest_config.lldbExec = which('lldb')
515
516    if lldbtest_config.lldbExec and not is_exe(lldbtest_config.lldbExec):
517        print(
518            "'{}' is not a path to a valid executable".format(
519                lldbtest_config.lldbExec))
520        lldbtest_config.lldbExec = None
521
522    if not lldbtest_config.lldbExec:
523        print("The 'lldb' executable cannot be located.  Some of the tests may not be run as a result.")
524        sys.exit(-1)
525
526    os.system('%s -v' % lldbtest_config.lldbExec)
527
528    lldbDir = os.path.dirname(lldbtest_config.lldbExec)
529
530    lldbVSCodeExec = os.path.join(lldbDir, "lldb-vscode")
531    if is_exe(lldbVSCodeExec):
532        os.environ["LLDBVSCODE_EXEC"] = lldbVSCodeExec
533    else:
534        if not configuration.shouldSkipBecauseOfCategories(["lldb-vscode"]):
535            print(
536                "The 'lldb-vscode' executable cannot be located.  The lldb-vscode tests can not be run as a result.")
537            configuration.skip_categories.append("lldb-vscode")
538
539    lldbPythonDir = None  # The directory that contains 'lldb/__init__.py'
540
541    # If our lldb supports the -P option, use it to find the python path:
542    lldb_dash_p_result = subprocess.check_output([lldbtest_config.lldbExec, "-P"], universal_newlines=True)
543    if lldb_dash_p_result:
544        for line in lldb_dash_p_result.splitlines():
545            if os.path.isdir(line) and os.path.exists(os.path.join(line, 'lldb', '__init__.py')):
546                lldbPythonDir = line
547                break
548
549    if not lldbPythonDir:
550        print(
551            "Unable to load lldb extension module.  Possible reasons for this include:")
552        print("  1) LLDB was built with LLDB_ENABLE_PYTHON=0")
553        print(
554            "  2) PYTHONPATH and PYTHONHOME are not set correctly.  PYTHONHOME should refer to")
555        print(
556            "     the version of Python that LLDB built and linked against, and PYTHONPATH")
557        print(
558            "     should contain the Lib directory for the same python distro, as well as the")
559        print("     location of LLDB\'s site-packages folder.")
560        print(
561            "  3) A different version of Python than that which was built against is exported in")
562        print("     the system\'s PATH environment variable, causing conflicts.")
563        print(
564            "  4) The executable '%s' could not be found.  Please check " %
565            lldbtest_config.lldbExec)
566        print("     that it exists and is executable.")
567
568    if lldbPythonDir:
569        lldbPythonDir = os.path.normpath(lldbPythonDir)
570        # Some of the code that uses this path assumes it hasn't resolved the Versions... link.
571        # If the path we've constructed looks like that, then we'll strip out
572        # the Versions/A part.
573        (before, frameWithVersion, after) = lldbPythonDir.rpartition(
574            "LLDB.framework/Versions/A")
575        if frameWithVersion != "":
576            lldbPythonDir = before + "LLDB.framework" + after
577
578        lldbPythonDir = os.path.abspath(lldbPythonDir)
579
580        if "freebsd" in sys.platform or "linux" in sys.platform:
581            os.environ['LLDB_LIB_DIR'] = os.path.join(lldbPythonDir, '..', '..')
582
583        # If tests need to find LLDB_FRAMEWORK, now they can do it
584        os.environ["LLDB_FRAMEWORK"] = os.path.dirname(
585            os.path.dirname(lldbPythonDir))
586
587        # This is to locate the lldb.py module.  Insert it right after
588        # sys.path[0].
589        sys.path[1:1] = [lldbPythonDir]
590
591
592def visit_file(dir, name):
593    # Try to match the regexp pattern, if specified.
594    if configuration.regexp:
595        if not re.search(configuration.regexp, name):
596            # We didn't match the regex, we're done.
597            return
598
599    if configuration.skip_tests:
600        for file_regexp in configuration.skip_tests:
601            if re.search(file_regexp, name):
602                return
603
604    # We found a match for our test.  Add it to the suite.
605
606    # Update the sys.path first.
607    if not sys.path.count(dir):
608        sys.path.insert(0, dir)
609    base = os.path.splitext(name)[0]
610
611    # Thoroughly check the filterspec against the base module and admit
612    # the (base, filterspec) combination only when it makes sense.
613
614    def check(obj, parts):
615        for part in parts:
616            try:
617                parent, obj = obj, getattr(obj, part)
618            except AttributeError:
619                # The filterspec has failed.
620                return False
621        return True
622
623    module = __import__(base)
624
625    def iter_filters():
626        for filterspec in configuration.filters:
627            parts = filterspec.split('.')
628            if check(module, parts):
629                yield filterspec
630            elif parts[0] == base and len(parts) > 1 and check(module, parts[1:]):
631                yield '.'.join(parts[1:])
632            else:
633                for key,value in module.__dict__.items():
634                    if check(value, parts):
635                        yield key + '.' + filterspec
636
637    filtered = False
638    for filterspec in iter_filters():
639        filtered = True
640        print("adding filter spec %s to module %s" % (filterspec, repr(module)))
641        tests = unittest2.defaultTestLoader.loadTestsFromName(filterspec, module)
642        configuration.suite.addTests(tests)
643
644    # Forgo this module if the (base, filterspec) combo is invalid
645    if configuration.filters and not filtered:
646        return
647
648    if not filtered:
649        # Add the entire file's worth of tests since we're not filtered.
650        # Also the fail-over case when the filterspec branch
651        # (base, filterspec) combo doesn't make sense.
652        configuration.suite.addTests(
653            unittest2.defaultTestLoader.loadTestsFromName(base))
654
655
656def visit(prefix, dir, names):
657    """Visitor function for os.path.walk(path, visit, arg)."""
658
659    dir_components = set(dir.split(os.sep))
660    excluded_components = set(['.svn', '.git'])
661    if dir_components.intersection(excluded_components):
662        return
663
664    # Gather all the Python test file names that follow the Test*.py pattern.
665    python_test_files = [
666        name
667        for name in names
668        if name.endswith('.py') and name.startswith(prefix)]
669
670    # Visit all the python test files.
671    for name in python_test_files:
672        # Ensure we error out if we have multiple tests with the same
673        # base name.
674        # Future improvement: find all the places where we work with base
675        # names and convert to full paths.  We have directory structure
676        # to disambiguate these, so we shouldn't need this constraint.
677        if name in configuration.all_tests:
678            raise Exception("Found multiple tests with the name %s" % name)
679        configuration.all_tests.add(name)
680
681        # Run the relevant tests in the python file.
682        visit_file(dir, name)
683
684
685# ======================================== #
686#                                          #
687# Execution of the test driver starts here #
688#                                          #
689# ======================================== #
690
691
692def checkDsymForUUIDIsNotOn():
693    cmd = ["defaults", "read", "com.apple.DebugSymbols"]
694    process = subprocess.Popen(
695        cmd,
696        stdout=subprocess.PIPE,
697        stderr=subprocess.STDOUT)
698    cmd_output = process.stdout.read()
699    output_str = cmd_output.decode("utf-8")
700    if "DBGFileMappedPaths = " in output_str:
701        print("%s =>" % ' '.join(cmd))
702        print(output_str)
703        print(
704            "Disable automatic lookup and caching of dSYMs before running the test suite!")
705        print("Exiting...")
706        sys.exit(0)
707
708
709def exitTestSuite(exitCode=None):
710    # lldb.py does SBDebugger.Initialize().
711    # Call SBDebugger.Terminate() on exit.
712    import lldb
713    lldb.SBDebugger.Terminate()
714    if exitCode:
715        sys.exit(exitCode)
716
717
718def getVersionForSDK(sdk):
719    sdk = str.lower(sdk)
720    full_path = seven.get_command_output('xcrun -sdk %s --show-sdk-path' % sdk)
721    basename = os.path.basename(full_path)
722    basename = os.path.splitext(basename)[0]
723    basename = str.lower(basename)
724    ver = basename.replace(sdk, '')
725    return ver
726
727
728def checkCompiler():
729    # Add some intervention here to sanity check that the compiler requested is sane.
730    # If found not to be an executable program, we abort.
731    c = configuration.compiler
732    if which(c):
733        return
734
735    if not sys.platform.startswith("darwin"):
736        raise Exception(c + " is not a valid compiler")
737
738    pipe = subprocess.Popen(
739        ['xcrun', '-find', c], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
740    cmd_output = pipe.stdout.read()
741    if not cmd_output or "not found" in cmd_output:
742        raise Exception(c + " is not a valid compiler")
743
744    configuration.compiler = cmd_output.split('\n')[0]
745    print("'xcrun -find %s' returning %s" % (c, configuration.compiler))
746
747def canRunLibcxxTests():
748    from lldbsuite.test import lldbplatformutil
749
750    platform = lldbplatformutil.getPlatform()
751
752    if lldbplatformutil.target_is_android() or lldbplatformutil.platformIsDarwin():
753        return True, "libc++ always present"
754
755    if platform == "linux":
756        with tempfile.NamedTemporaryFile() as f:
757            cmd = [configuration.compiler, "-xc++", "-stdlib=libc++", "-o", f.name, "-"]
758            p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
759            _, stderr = p.communicate("#include <cassert>\nint main() {}")
760            if not p.returncode:
761                return True, "Compiling with -stdlib=libc++ works"
762            return False, "Compiling with -stdlib=libc++ fails with the error: %s" % stderr
763
764    return False, "Don't know how to build with libc++ on %s" % platform
765
766def checkLibcxxSupport():
767    result, reason = canRunLibcxxTests()
768    if result:
769        return # libc++ supported
770    if "libc++" in configuration.categories_list:
771        return # libc++ category explicitly requested, let it run.
772    if configuration.verbose:
773        print("libc++ tests will not be run because: " + reason)
774    configuration.skip_categories.append("libc++")
775
776def canRunLibstdcxxTests():
777    from lldbsuite.test import lldbplatformutil
778
779    platform = lldbplatformutil.getPlatform()
780    if lldbplatformutil.target_is_android():
781        platform = "android"
782    if platform == "linux":
783        return True, "libstdcxx always present"
784    return False, "Don't know how to build with libstdcxx on %s" % platform
785
786def checkLibstdcxxSupport():
787    result, reason = canRunLibstdcxxTests()
788    if result:
789        return # libstdcxx supported
790    if "libstdcxx" in configuration.categories_list:
791        return # libstdcxx category explicitly requested, let it run.
792    if configuration.verbose:
793        print("libstdcxx tests will not be run because: " + reason)
794    configuration.skip_categories.append("libstdcxx")
795
796def canRunWatchpointTests():
797    from lldbsuite.test import lldbplatformutil
798
799    platform = lldbplatformutil.getPlatform()
800    if platform == "netbsd":
801        if os.geteuid() == 0:
802            return True, "root can always write dbregs"
803        try:
804            output = subprocess.check_output(["/sbin/sysctl", "-n",
805              "security.models.extensions.user_set_dbregs"]).decode().strip()
806            if output == "1":
807                return True, "security.models.extensions.user_set_dbregs enabled"
808        except subprocess.CalledProcessError:
809            pass
810        return False, "security.models.extensions.user_set_dbregs disabled"
811    elif platform == "freebsd" and configuration.arch == "aarch64":
812        import lldb
813        if lldb.SBPlatform.GetHostPlatform().GetOSMajorVersion() < 13:
814            return False, "Watchpoint support on arm64 requires FreeBSD 13.0"
815    return True, "watchpoint support available"
816
817def checkWatchpointSupport():
818    result, reason = canRunWatchpointTests()
819    if result:
820        return # watchpoints supported
821    if "watchpoint" in configuration.categories_list:
822        return # watchpoint category explicitly requested, let it run.
823    if configuration.verbose:
824        print("watchpoint tests will not be run because: " + reason)
825    configuration.skip_categories.append("watchpoint")
826
827def checkObjcSupport():
828    from lldbsuite.test import lldbplatformutil
829
830    if not lldbplatformutil.platformIsDarwin():
831        if configuration.verbose:
832            print("objc tests will be skipped because of unsupported platform")
833        configuration.skip_categories.append("objc")
834
835def checkDebugInfoSupport():
836    from lldbsuite.test import lldbplatformutil
837
838    platform = lldbplatformutil.getPlatform()
839    compiler = configuration.compiler
840    for cat in test_categories.debug_info_categories:
841        if cat in configuration.categories_list:
842            continue # Category explicitly requested, let it run.
843        if test_categories.is_supported_on_platform(cat, platform, compiler):
844            continue
845        configuration.skip_categories.append(cat)
846
847def checkDebugServerSupport():
848    from lldbsuite.test import lldbplatformutil
849    import lldb
850
851    skip_msg = "Skipping %s tests, as they are not compatible with remote testing on this platform"
852    if lldbplatformutil.platformIsDarwin():
853        configuration.skip_categories.append("llgs")
854        if configuration.lldb_platform_name:
855            # <rdar://problem/34539270>
856            configuration.skip_categories.append("debugserver")
857            if configuration.verbose:
858                print(skip_msg%"debugserver");
859    else:
860        configuration.skip_categories.append("debugserver")
861        if configuration.lldb_platform_name and lldbplatformutil.getPlatform() == "windows":
862            configuration.skip_categories.append("llgs")
863            if configuration.verbose:
864                print(skip_msg%"lldb-server");
865
866
867def checkForkVForkSupport():
868    from lldbsuite.test import lldbplatformutil
869
870    platform = lldbplatformutil.getPlatform()
871    if platform not in ["freebsd", "linux", "netbsd"]:
872        configuration.skip_categories.append("fork")
873
874
875def run_suite():
876    # On MacOS X, check to make sure that domain for com.apple.DebugSymbols defaults
877    # does not exist before proceeding to running the test suite.
878    if sys.platform.startswith("darwin"):
879        checkDsymForUUIDIsNotOn()
880
881    # Start the actions by first parsing the options while setting up the test
882    # directories, followed by setting up the search paths for lldb utilities;
883    # then, we walk the directory trees and collect the tests into our test suite.
884    #
885    parseOptionsAndInitTestdirs()
886
887    # Print a stack trace if the test hangs or is passed SIGTERM.
888    registerFaulthandler()
889
890    setupSysPath()
891
892    import lldb
893    lldb.SBDebugger.Initialize()
894    lldb.SBDebugger.PrintStackTraceOnError()
895
896    checkLibcxxSupport()
897    checkLibstdcxxSupport()
898    checkWatchpointSupport()
899    checkDebugInfoSupport()
900    checkDebugServerSupport()
901    checkObjcSupport()
902    checkForkVForkSupport()
903
904    # Use host platform by default.
905    lldb.remote_platform = None
906    lldb.selected_platform = lldb.SBPlatform.GetHostPlatform()
907
908    # Now we can also import lldbutil
909    from lldbsuite.test import lldbutil
910
911    if configuration.lldb_platform_name:
912        print("Setting up remote platform '%s'" %
913              (configuration.lldb_platform_name))
914        lldb.remote_platform = lldb.SBPlatform(
915            configuration.lldb_platform_name)
916        lldb.selected_platform = lldb.remote_platform
917        if not lldb.remote_platform.IsValid():
918            print(
919                "error: unable to create the LLDB platform named '%s'." %
920                (configuration.lldb_platform_name))
921            exitTestSuite(1)
922        if configuration.lldb_platform_url:
923            # We must connect to a remote platform if a LLDB platform URL was
924            # specified
925            print(
926                "Connecting to remote platform '%s' at '%s'..." %
927                (configuration.lldb_platform_name, configuration.lldb_platform_url))
928            platform_connect_options = lldb.SBPlatformConnectOptions(
929                configuration.lldb_platform_url)
930            err = lldb.remote_platform.ConnectRemote(platform_connect_options)
931            if err.Success():
932                print("Connected.")
933            else:
934                print("error: failed to connect to remote platform using URL '%s': %s" % (
935                    configuration.lldb_platform_url, err))
936                exitTestSuite(1)
937        else:
938            configuration.lldb_platform_url = None
939
940    if configuration.lldb_platform_working_dir:
941        print("Setting remote platform working directory to '%s'..." %
942              (configuration.lldb_platform_working_dir))
943        error = lldb.remote_platform.MakeDirectory(
944            configuration.lldb_platform_working_dir, 448)  # 448 = 0o700
945        if error.Fail():
946            raise Exception("making remote directory '%s': %s" % (
947                configuration.lldb_platform_working_dir, error))
948
949        if not lldb.remote_platform.SetWorkingDirectory(
950                configuration.lldb_platform_working_dir):
951            raise Exception("failed to set working directory '%s'" % configuration.lldb_platform_working_dir)
952        lldb.selected_platform = lldb.remote_platform
953    else:
954        lldb.remote_platform = None
955        configuration.lldb_platform_working_dir = None
956        configuration.lldb_platform_url = None
957
958    # Set up the working directory.
959    # Note that it's not dotest's job to clean this directory.
960    lldbutil.mkdir_p(configuration.test_build_dir)
961
962    skipped_categories_list = ", ".join(configuration.skip_categories)
963    print("Skipping the following test categories: {}".format(skipped_categories_list))
964
965    for testdir in configuration.testdirs:
966        for (dirpath, dirnames, filenames) in os.walk(testdir):
967            visit('Test', dirpath, filenames)
968
969    #
970    # Now that we have loaded all the test cases, run the whole test suite.
971    #
972
973    # Install the control-c handler.
974    unittest2.signals.installHandler()
975
976    #
977    # Invoke the default TextTestRunner to run the test suite
978    #
979    checkCompiler()
980
981    if configuration.verbose:
982        print("compiler=%s" % configuration.compiler)
983
984    # Iterating over all possible architecture and compiler combinations.
985    configString = "arch=%s compiler=%s" % (configuration.arch,
986                                            configuration.compiler)
987
988    # Output the configuration.
989    if configuration.verbose:
990        sys.stderr.write("\nConfiguration: " + configString + "\n")
991
992    # First, write out the number of collected test cases.
993    if configuration.verbose:
994        sys.stderr.write(configuration.separator + "\n")
995        sys.stderr.write(
996            "Collected %d test%s\n\n" %
997            (configuration.suite.countTestCases(),
998             configuration.suite.countTestCases() != 1 and "s" or ""))
999
1000    if configuration.suite.countTestCases() == 0:
1001        logging.error("did not discover any matching tests")
1002        exitTestSuite(1)
1003
1004    # Invoke the test runner.
1005    if configuration.count == 1:
1006        result = unittest2.TextTestRunner(
1007            stream=sys.stderr,
1008            verbosity=configuration.verbose,
1009            resultclass=test_result.LLDBTestResult).run(
1010            configuration.suite)
1011    else:
1012        # We are invoking the same test suite more than once.  In this case,
1013        # mark __ignore_singleton__ flag as True so the signleton pattern is
1014        # not enforced.
1015        test_result.LLDBTestResult.__ignore_singleton__ = True
1016        for i in range(configuration.count):
1017
1018            result = unittest2.TextTestRunner(
1019                stream=sys.stderr,
1020                verbosity=configuration.verbose,
1021                resultclass=test_result.LLDBTestResult).run(
1022                configuration.suite)
1023
1024    configuration.failed = not result.wasSuccessful()
1025
1026    if configuration.sdir_has_content and configuration.verbose:
1027        sys.stderr.write(
1028            "Session logs for test failures/errors/unexpected successes"
1029            " can be found in the test build directory\n")
1030
1031    if configuration.use_categories and len(
1032            configuration.failures_per_category) > 0:
1033        sys.stderr.write("Failures per category:\n")
1034        for category in configuration.failures_per_category:
1035            sys.stderr.write(
1036                "%s - %d\n" %
1037                (category, configuration.failures_per_category[category]))
1038
1039    # Exiting.
1040    exitTestSuite(configuration.failed)
1041
1042if __name__ == "__main__":
1043    print(
1044        __file__ +
1045        " is for use as a module only.  It should not be run as a standalone script.")
1046    sys.exit(-1)
1047