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