xref: /llvm-project/lldb/packages/Python/lldbsuite/test/dotest.py (revision a16bb0701409376dee3a587ae351a6019d6de4e0)
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    if args.clang_module_cache_dir:
427        configuration.clang_module_cache_dir = args.clang_module_cache_dir
428    else:
429        configuration.clang_module_cache_dir = os.path.join(
430            configuration.test_build_dir, "module-cache-clang"
431        )
432
433    if args.lldb_libs_dir:
434        configuration.lldb_libs_dir = args.lldb_libs_dir
435
436    if args.enabled_plugins:
437        configuration.enabled_plugins = args.enabled_plugins
438
439    # Gather all the dirs passed on the command line.
440    if len(args.args) > 0:
441        configuration.testdirs = [
442            os.path.realpath(os.path.abspath(x)) for x in args.args
443        ]
444
445
446def registerFaulthandler():
447    try:
448        import faulthandler
449    except ImportError:
450        # faulthandler is not available until python3
451        return
452
453    faulthandler.enable()
454    # faulthandler.register is not available on Windows.
455    if getattr(faulthandler, "register", None):
456        faulthandler.register(signal.SIGTERM, chain=True)
457
458
459def setupSysPath():
460    """
461    Add LLDB.framework/Resources/Python to the search paths for modules.
462    As a side effect, we also discover the 'lldb' executable and export it here.
463    """
464
465    # Get the directory containing the current script.
466    if "DOTEST_PROFILE" in os.environ and "DOTEST_SCRIPT_DIR" in os.environ:
467        scriptPath = os.environ["DOTEST_SCRIPT_DIR"]
468    else:
469        scriptPath = os.path.dirname(os.path.abspath(__file__))
470    if not scriptPath.endswith("test"):
471        print("This script expects to reside in lldb's test directory.")
472        sys.exit(-1)
473
474    os.environ["LLDB_TEST"] = scriptPath
475
476    # Set up the root build directory.
477    if not configuration.test_build_dir:
478        raise Exception("test_build_dir is not set")
479    configuration.test_build_dir = os.path.abspath(configuration.test_build_dir)
480
481    # Set up the LLDB_SRC environment variable, so that the tests can locate
482    # the LLDB source code.
483    os.environ["LLDB_SRC"] = lldbsuite.lldb_root
484
485    pluginPath = os.path.join(scriptPath, "plugins")
486    toolsLLDBDAP = os.path.join(scriptPath, "tools", "lldb-dap")
487    toolsLLDBServerPath = os.path.join(scriptPath, "tools", "lldb-server")
488    intelpt = os.path.join(scriptPath, "tools", "intelpt")
489
490    # Insert script dir, plugin dir and lldb-server dir to the sys.path.
491    sys.path.insert(0, pluginPath)
492    # Adding test/tools/lldb-dap to the path makes it easy to
493    # "import lldb_dap_testcase" from the DAP tests
494    sys.path.insert(0, toolsLLDBDAP)
495    # Adding test/tools/lldb-server to the path makes it easy
496    # to "import lldbgdbserverutils" from the lldb-server tests
497    sys.path.insert(0, toolsLLDBServerPath)
498    # Adding test/tools/intelpt to the path makes it easy
499    # to "import intelpt_testcase" from the lldb-server tests
500    sys.path.insert(0, intelpt)
501
502    # This is the root of the lldb git/svn checkout
503    # When this changes over to a package instead of a standalone script, this
504    # will be `lldbsuite.lldb_root`
505    lldbRootDirectory = lldbsuite.lldb_root
506
507    # Some of the tests can invoke the 'lldb' command directly.
508    # We'll try to locate the appropriate executable right here.
509
510    # The lldb executable can be set from the command line
511    # if it's not set, we try to find it now
512    # first, we try the environment
513    if not lldbtest_config.lldbExec:
514        # First, you can define an environment variable LLDB_EXEC specifying the
515        # full pathname of the lldb executable.
516        if "LLDB_EXEC" in os.environ:
517            lldbtest_config.lldbExec = os.environ["LLDB_EXEC"]
518
519    if not lldbtest_config.lldbExec:
520        # Last, check the path
521        lldbtest_config.lldbExec = which("lldb")
522
523    if lldbtest_config.lldbExec and not is_exe(lldbtest_config.lldbExec):
524        print(
525            "'{}' is not a path to a valid executable".format(lldbtest_config.lldbExec)
526        )
527        lldbtest_config.lldbExec = None
528
529    if not lldbtest_config.lldbExec:
530        print(
531            "The 'lldb' executable cannot be located.  Some of the tests may not be run as a result."
532        )
533        sys.exit(-1)
534
535    os.system("%s -v" % lldbtest_config.lldbExec)
536
537    lldbDir = os.path.dirname(lldbtest_config.lldbExec)
538
539    lldbDAPExec = os.path.join(lldbDir, "lldb-dap")
540    if is_exe(lldbDAPExec):
541        os.environ["LLDBDAP_EXEC"] = lldbDAPExec
542    else:
543        if not configuration.shouldSkipBecauseOfCategories(["lldb-dap"]):
544            print(
545                "The 'lldb-dap' executable cannot be located.  The lldb-dap tests can not be run as a result."
546            )
547            configuration.skip_categories.append("lldb-dap")
548
549    lldbPythonDir = None  # The directory that contains 'lldb/__init__.py'
550
551    # If our lldb supports the -P option, use it to find the python path:
552    lldb_dash_p_result = subprocess.check_output(
553        [lldbtest_config.lldbExec, "-P"], universal_newlines=True
554    )
555    if lldb_dash_p_result:
556        for line in lldb_dash_p_result.splitlines():
557            if os.path.isdir(line) and os.path.exists(
558                os.path.join(line, "lldb", "__init__.py")
559            ):
560                lldbPythonDir = line
561                break
562
563    if not lldbPythonDir:
564        print(
565            "Unable to load lldb extension module.  Possible reasons for this include:"
566        )
567        print("  1) LLDB was built with LLDB_ENABLE_PYTHON=0")
568        print(
569            "  2) PYTHONPATH and PYTHONHOME are not set correctly.  PYTHONHOME should refer to"
570        )
571        print(
572            "     the version of Python that LLDB built and linked against, and PYTHONPATH"
573        )
574        print(
575            "     should contain the Lib directory for the same python distro, as well as the"
576        )
577        print("     location of LLDB's site-packages folder.")
578        print(
579            "  3) A different version of Python than that which was built against is exported in"
580        )
581        print("     the system's PATH environment variable, causing conflicts.")
582        print(
583            "  4) The executable '%s' could not be found.  Please check "
584            % lldbtest_config.lldbExec
585        )
586        print("     that it exists and is executable.")
587
588    if lldbPythonDir:
589        lldbPythonDir = os.path.normpath(lldbPythonDir)
590        # Some of the code that uses this path assumes it hasn't resolved the Versions... link.
591        # If the path we've constructed looks like that, then we'll strip out
592        # the Versions/A part.
593        (before, frameWithVersion, after) = lldbPythonDir.rpartition(
594            "LLDB.framework/Versions/A"
595        )
596        if frameWithVersion != "":
597            lldbPythonDir = before + "LLDB.framework" + after
598
599        lldbPythonDir = os.path.abspath(lldbPythonDir)
600
601        if "freebsd" in sys.platform or "linux" in sys.platform:
602            os.environ["LLDB_LIB_DIR"] = os.path.join(lldbPythonDir, "..", "..")
603
604        # If tests need to find LLDB_FRAMEWORK, now they can do it
605        os.environ["LLDB_FRAMEWORK"] = os.path.dirname(os.path.dirname(lldbPythonDir))
606
607        # This is to locate the lldb.py module.  Insert it right after
608        # sys.path[0].
609        sys.path[1:1] = [lldbPythonDir]
610
611
612def visit_file(dir, name):
613    # Try to match the regexp pattern, if specified.
614    if configuration.regexp:
615        if not re.search(configuration.regexp, name):
616            # We didn't match the regex, we're done.
617            return
618
619    if configuration.skip_tests:
620        for file_regexp in configuration.skip_tests:
621            if re.search(file_regexp, name):
622                return
623
624    # We found a match for our test.  Add it to the suite.
625
626    # Update the sys.path first.
627    if not sys.path.count(dir):
628        sys.path.insert(0, dir)
629    base = os.path.splitext(name)[0]
630
631    # Thoroughly check the filterspec against the base module and admit
632    # the (base, filterspec) combination only when it makes sense.
633
634    def check(obj, parts):
635        for part in parts:
636            try:
637                parent, obj = obj, getattr(obj, part)
638            except AttributeError:
639                # The filterspec has failed.
640                return False
641        return True
642
643    module = __import__(base)
644
645    def iter_filters():
646        for filterspec in configuration.filters:
647            parts = filterspec.split(".")
648            if check(module, parts):
649                yield filterspec
650            elif parts[0] == base and len(parts) > 1 and check(module, parts[1:]):
651                yield ".".join(parts[1:])
652            else:
653                for key, value in module.__dict__.items():
654                    if check(value, parts):
655                        yield key + "." + filterspec
656
657    filtered = False
658    for filterspec in iter_filters():
659        filtered = True
660        print("adding filter spec %s to module %s" % (filterspec, repr(module)))
661        tests = unittest.defaultTestLoader.loadTestsFromName(filterspec, module)
662        configuration.suite.addTests(tests)
663
664    # Forgo this module if the (base, filterspec) combo is invalid
665    if configuration.filters and not filtered:
666        return
667
668    if not filtered:
669        # Add the entire file's worth of tests since we're not filtered.
670        # Also the fail-over case when the filterspec branch
671        # (base, filterspec) combo doesn't make sense.
672        configuration.suite.addTests(unittest.defaultTestLoader.loadTestsFromName(base))
673
674
675def visit(prefix, dir, names):
676    """Visitor function for os.path.walk(path, visit, arg)."""
677
678    dir_components = set(dir.split(os.sep))
679    excluded_components = set([".svn", ".git"])
680    if dir_components.intersection(excluded_components):
681        return
682
683    # Gather all the Python test file names that follow the Test*.py pattern.
684    python_test_files = [
685        name for name in names if name.endswith(".py") and name.startswith(prefix)
686    ]
687
688    # Visit all the python test files.
689    for name in python_test_files:
690        # Ensure we error out if we have multiple tests with the same
691        # base name.
692        # Future improvement: find all the places where we work with base
693        # names and convert to full paths.  We have directory structure
694        # to disambiguate these, so we shouldn't need this constraint.
695        if name in configuration.all_tests:
696            raise Exception("Found multiple tests with the name %s" % name)
697        configuration.all_tests.add(name)
698
699        # Run the relevant tests in the python file.
700        visit_file(dir, name)
701
702
703# ======================================== #
704#                                          #
705# Execution of the test driver starts here #
706#                                          #
707# ======================================== #
708
709
710def checkDsymForUUIDIsNotOn():
711    cmd = ["defaults", "read", "com.apple.DebugSymbols"]
712    process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
713    cmd_output = process.stdout.read()
714    output_str = cmd_output.decode("utf-8")
715    if "DBGFileMappedPaths = " in output_str:
716        print("%s =>" % " ".join(cmd))
717        print(output_str)
718        print(
719            "Disable automatic lookup and caching of dSYMs before running the test suite!"
720        )
721        print("Exiting...")
722        sys.exit(0)
723
724
725def exitTestSuite(exitCode=None):
726    # lldb.py does SBDebugger.Initialize().
727    # Call SBDebugger.Terminate() on exit.
728    import lldb
729
730    lldb.SBDebugger.Terminate()
731    if exitCode:
732        sys.exit(exitCode)
733
734
735def getVersionForSDK(sdk):
736    sdk = str.lower(sdk)
737    full_path = seven.get_command_output("xcrun -sdk %s --show-sdk-path" % sdk)
738    basename = os.path.basename(full_path)
739    basename = os.path.splitext(basename)[0]
740    basename = str.lower(basename)
741    ver = basename.replace(sdk, "")
742    return ver
743
744
745def checkCompiler():
746    # Add some intervention here to sanity check that the compiler requested is sane.
747    # If found not to be an executable program, we abort.
748    c = configuration.compiler
749    if which(c):
750        return
751
752    if not sys.platform.startswith("darwin"):
753        raise Exception(c + " is not a valid compiler")
754
755    pipe = subprocess.Popen(
756        ["xcrun", "-find", c], stdout=subprocess.PIPE, stderr=subprocess.STDOUT
757    )
758    cmd_output = pipe.stdout.read()
759    if not cmd_output or "not found" in cmd_output:
760        raise Exception(c + " is not a valid compiler")
761
762    configuration.compiler = cmd_output.split("\n")[0]
763    print("'xcrun -find %s' returning %s" % (c, configuration.compiler))
764
765
766def canRunLibcxxTests():
767    from lldbsuite.test import lldbplatformutil
768
769    platform = lldbplatformutil.getPlatform()
770
771    if lldbplatformutil.target_is_android() or lldbplatformutil.platformIsDarwin():
772        return True, "libc++ always present"
773
774    if platform == "linux":
775        with tempfile.NamedTemporaryFile() as f:
776            cmd = [configuration.compiler, "-xc++", "-stdlib=libc++", "-o", f.name, "-"]
777            p = subprocess.Popen(
778                cmd,
779                stdin=subprocess.PIPE,
780                stdout=subprocess.PIPE,
781                stderr=subprocess.PIPE,
782                universal_newlines=True,
783            )
784            _, stderr = p.communicate("#include <cassert>\nint main() {}")
785            if not p.returncode:
786                return True, "Compiling with -stdlib=libc++ works"
787            return (
788                False,
789                "Compiling with -stdlib=libc++ fails with the error: %s" % stderr,
790            )
791
792    return False, "Don't know how to build with libc++ on %s" % platform
793
794
795def checkLibcxxSupport():
796    result, reason = canRunLibcxxTests()
797    if result:
798        return  # libc++ supported
799    if "libc++" in configuration.categories_list:
800        return  # libc++ category explicitly requested, let it run.
801    if configuration.verbose:
802        print("libc++ tests will not be run because: " + reason)
803    configuration.skip_categories.append("libc++")
804
805
806def canRunLibstdcxxTests():
807    from lldbsuite.test import lldbplatformutil
808
809    platform = lldbplatformutil.getPlatform()
810    if lldbplatformutil.target_is_android():
811        platform = "android"
812    if platform == "linux":
813        return True, "libstdcxx always present"
814    return False, "Don't know how to build with libstdcxx on %s" % platform
815
816
817def checkLibstdcxxSupport():
818    result, reason = canRunLibstdcxxTests()
819    if result:
820        return  # libstdcxx supported
821    if "libstdcxx" in configuration.categories_list:
822        return  # libstdcxx category explicitly requested, let it run.
823    if configuration.verbose:
824        print("libstdcxx tests will not be run because: " + reason)
825    configuration.skip_categories.append("libstdcxx")
826
827
828def canRunWatchpointTests():
829    from lldbsuite.test import lldbplatformutil
830
831    platform = lldbplatformutil.getPlatform()
832    if platform == "netbsd":
833        if os.geteuid() == 0:
834            return True, "root can always write dbregs"
835        try:
836            output = (
837                subprocess.check_output(
838                    ["/sbin/sysctl", "-n", "security.models.extensions.user_set_dbregs"]
839                )
840                .decode()
841                .strip()
842            )
843            if output == "1":
844                return True, "security.models.extensions.user_set_dbregs enabled"
845        except subprocess.CalledProcessError:
846            pass
847        return False, "security.models.extensions.user_set_dbregs disabled"
848    elif platform == "freebsd" and configuration.arch == "aarch64":
849        import lldb
850
851        if lldb.SBPlatform.GetHostPlatform().GetOSMajorVersion() < 13:
852            return False, "Watchpoint support on arm64 requires FreeBSD 13.0"
853    return True, "watchpoint support available"
854
855
856def checkWatchpointSupport():
857    result, reason = canRunWatchpointTests()
858    if result:
859        return  # watchpoints supported
860    if "watchpoint" in configuration.categories_list:
861        return  # watchpoint category explicitly requested, let it run.
862    if configuration.verbose:
863        print("watchpoint tests will not be run because: " + reason)
864    configuration.skip_categories.append("watchpoint")
865
866
867def checkObjcSupport():
868    from lldbsuite.test import lldbplatformutil
869
870    if not lldbplatformutil.platformIsDarwin():
871        if configuration.verbose:
872            print("objc tests will be skipped because of unsupported platform")
873        configuration.skip_categories.append("objc")
874
875
876def checkDebugInfoSupport():
877    from lldbsuite.test import lldbplatformutil
878
879    platform = lldbplatformutil.getPlatform()
880    compiler = configuration.compiler
881    for cat in test_categories.debug_info_categories:
882        if cat in configuration.categories_list:
883            continue  # Category explicitly requested, let it run.
884        if test_categories.is_supported_on_platform(cat, platform, compiler):
885            continue
886        configuration.skip_categories.append(cat)
887
888
889def checkDebugServerSupport():
890    from lldbsuite.test import lldbplatformutil
891    import lldb
892
893    skip_msg = "Skipping %s tests, as they are not compatible with remote testing on this platform"
894    if lldbplatformutil.platformIsDarwin():
895        configuration.skip_categories.append("llgs")
896        if lldb.remote_platform:
897            # <rdar://problem/34539270>
898            configuration.skip_categories.append("debugserver")
899            if configuration.verbose:
900                print(skip_msg % "debugserver")
901    else:
902        configuration.skip_categories.append("debugserver")
903        if lldb.remote_platform and lldbplatformutil.getPlatform() == "windows":
904            configuration.skip_categories.append("llgs")
905            if configuration.verbose:
906                print(skip_msg % "lldb-server")
907
908
909def checkForkVForkSupport():
910    from lldbsuite.test import lldbplatformutil
911
912    platform = lldbplatformutil.getPlatform()
913    if platform not in ["freebsd", "linux", "netbsd"]:
914        configuration.skip_categories.append("fork")
915
916
917def checkPexpectSupport():
918    from lldbsuite.test import lldbplatformutil
919
920    platform = lldbplatformutil.getPlatform()
921
922    # llvm.org/pr22274: need a pexpect replacement for windows
923    if platform in ["windows"]:
924        if configuration.verbose:
925            print("pexpect tests will be skipped because of unsupported platform")
926        configuration.skip_categories.append("pexpect")
927
928
929def run_suite():
930    # On MacOS X, check to make sure that domain for com.apple.DebugSymbols defaults
931    # does not exist before proceeding to running the test suite.
932    if sys.platform.startswith("darwin"):
933        checkDsymForUUIDIsNotOn()
934
935    # Start the actions by first parsing the options while setting up the test
936    # directories, followed by setting up the search paths for lldb utilities;
937    # then, we walk the directory trees and collect the tests into our test suite.
938    #
939    parseOptionsAndInitTestdirs()
940
941    # Print a stack trace if the test hangs or is passed SIGTERM.
942    registerFaulthandler()
943
944    setupSysPath()
945
946    import lldb
947
948    lldb.SBDebugger.Initialize()
949    lldb.SBDebugger.PrintStackTraceOnError()
950
951    # Use host platform by default.
952    lldb.remote_platform = None
953    lldb.selected_platform = lldb.SBPlatform.GetHostPlatform()
954
955    # Now we can also import lldbutil
956    from lldbsuite.test import lldbutil
957
958    if configuration.lldb_platform_name:
959        print("Setting up remote platform '%s'" % (configuration.lldb_platform_name))
960        lldb.remote_platform = lldb.SBPlatform(configuration.lldb_platform_name)
961        lldb.selected_platform = lldb.remote_platform
962        if not lldb.remote_platform.IsValid():
963            print(
964                "error: unable to create the LLDB platform named '%s'."
965                % (configuration.lldb_platform_name)
966            )
967            exitTestSuite(1)
968        if configuration.lldb_platform_url:
969            # We must connect to a remote platform if a LLDB platform URL was
970            # specified
971            print(
972                "Connecting to remote platform '%s' at '%s'..."
973                % (configuration.lldb_platform_name, configuration.lldb_platform_url)
974            )
975            platform_connect_options = lldb.SBPlatformConnectOptions(
976                configuration.lldb_platform_url
977            )
978            err = lldb.remote_platform.ConnectRemote(platform_connect_options)
979            if err.Success():
980                print("Connected.")
981            else:
982                print(
983                    "error: failed to connect to remote platform using URL '%s': %s"
984                    % (configuration.lldb_platform_url, err)
985                )
986                exitTestSuite(1)
987        else:
988            configuration.lldb_platform_url = None
989
990    if configuration.lldb_platform_working_dir:
991        print(
992            "Setting remote platform working directory to '%s'..."
993            % (configuration.lldb_platform_working_dir)
994        )
995        error = lldb.remote_platform.MakeDirectory(
996            configuration.lldb_platform_working_dir, 448
997        )  # 448 = 0o700
998        if error.Fail():
999            raise Exception(
1000                "making remote directory '%s': %s"
1001                % (configuration.lldb_platform_working_dir, error)
1002            )
1003
1004        if not lldb.remote_platform.SetWorkingDirectory(
1005            configuration.lldb_platform_working_dir
1006        ):
1007            raise Exception(
1008                "failed to set working directory '%s'"
1009                % configuration.lldb_platform_working_dir
1010            )
1011        lldb.selected_platform = lldb.remote_platform
1012    else:
1013        lldb.remote_platform = None
1014        configuration.lldb_platform_working_dir = None
1015        configuration.lldb_platform_url = None
1016
1017    # Set up the working directory.
1018    # Note that it's not dotest's job to clean this directory.
1019    lldbutil.mkdir_p(configuration.test_build_dir)
1020
1021    checkLibcxxSupport()
1022    checkLibstdcxxSupport()
1023    checkWatchpointSupport()
1024    checkDebugInfoSupport()
1025    checkDebugServerSupport()
1026    checkObjcSupport()
1027    checkForkVForkSupport()
1028    checkPexpectSupport()
1029
1030    skipped_categories_list = ", ".join(configuration.skip_categories)
1031    print(
1032        "Skipping the following test categories: {}".format(
1033            configuration.skip_categories
1034        )
1035    )
1036
1037    for testdir in configuration.testdirs:
1038        for dirpath, dirnames, filenames in os.walk(testdir):
1039            visit("Test", dirpath, filenames)
1040
1041    #
1042    # Now that we have loaded all the test cases, run the whole test suite.
1043    #
1044
1045    # Install the control-c handler.
1046    unittest.signals.installHandler()
1047
1048    #
1049    # Invoke the default TextTestRunner to run the test suite
1050    #
1051    checkCompiler()
1052
1053    if configuration.verbose:
1054        print("compiler=%s" % configuration.compiler)
1055
1056    # Iterating over all possible architecture and compiler combinations.
1057    configString = "arch=%s compiler=%s" % (configuration.arch, configuration.compiler)
1058
1059    # Output the configuration.
1060    if configuration.verbose:
1061        sys.stderr.write("\nConfiguration: " + configString + "\n")
1062
1063    # First, write out the number of collected test cases.
1064    if configuration.verbose:
1065        sys.stderr.write(configuration.separator + "\n")
1066        sys.stderr.write(
1067            "Collected %d test%s\n\n"
1068            % (
1069                configuration.suite.countTestCases(),
1070                configuration.suite.countTestCases() != 1 and "s" or "",
1071            )
1072        )
1073
1074    if configuration.suite.countTestCases() == 0:
1075        logging.error("did not discover any matching tests")
1076        exitTestSuite(1)
1077
1078    # Invoke the test runner.
1079    if configuration.count == 1:
1080        result = unittest.TextTestRunner(
1081            stream=sys.stderr,
1082            verbosity=configuration.verbose,
1083            resultclass=test_result.LLDBTestResult,
1084        ).run(configuration.suite)
1085    else:
1086        # We are invoking the same test suite more than once.  In this case,
1087        # mark __ignore_singleton__ flag as True so the signleton pattern is
1088        # not enforced.
1089        test_result.LLDBTestResult.__ignore_singleton__ = True
1090        for i in range(configuration.count):
1091            result = unittest.TextTestRunner(
1092                stream=sys.stderr,
1093                verbosity=configuration.verbose,
1094                resultclass=test_result.LLDBTestResult,
1095            ).run(configuration.suite)
1096
1097    configuration.failed = not result.wasSuccessful()
1098
1099    if configuration.sdir_has_content and configuration.verbose:
1100        sys.stderr.write(
1101            "Session logs for test failures/errors/unexpected successes"
1102            " can be found in the test build directory\n"
1103        )
1104
1105    if configuration.use_categories and len(configuration.failures_per_category) > 0:
1106        sys.stderr.write("Failures per category:\n")
1107        for category in configuration.failures_per_category:
1108            sys.stderr.write(
1109                "%s - %d\n" % (category, configuration.failures_per_category[category])
1110            )
1111
1112    # Exiting.
1113    exitTestSuite(configuration.failed)
1114
1115
1116if __name__ == "__main__":
1117    print(
1118        __file__
1119        + " is for use as a module only.  It should not be run as a standalone script."
1120    )
1121    sys.exit(-1)
1122