xref: /llvm-project/lldb/packages/Python/lldbsuite/test/dotest.py (revision 0e913237871e8c9290e82be30be8b3484952eee0)
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 is 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.make:
270        configuration.make_path = args.make
271
272    if args.dsymutil:
273        configuration.dsymutil = args.dsymutil
274    elif platform_system == "Darwin":
275        configuration.dsymutil = seven.get_command_output(
276            "xcrun -find -toolchain default dsymutil"
277        )
278    if args.llvm_tools_dir:
279        configuration.llvm_tools_dir = args.llvm_tools_dir
280        configuration.filecheck = shutil.which("FileCheck", path=args.llvm_tools_dir)
281        configuration.yaml2obj = shutil.which("yaml2obj", path=args.llvm_tools_dir)
282
283    if not configuration.get_filecheck_path():
284        logging.warning("No valid FileCheck executable; some tests may fail...")
285        logging.warning("(Double-check the --llvm-tools-dir argument to dotest.py)")
286
287    if args.libcxx_include_dir or args.libcxx_library_dir:
288        if args.lldb_platform_name:
289            logging.warning(
290                "Custom libc++ is not supported for remote runs: ignoring --libcxx arguments"
291            )
292        elif not (args.libcxx_include_dir and args.libcxx_library_dir):
293            logging.error(
294                "Custom libc++ requires both --libcxx-include-dir and --libcxx-library-dir"
295            )
296            sys.exit(-1)
297    configuration.libcxx_include_dir = args.libcxx_include_dir
298    configuration.libcxx_include_target_dir = args.libcxx_include_target_dir
299    configuration.libcxx_library_dir = args.libcxx_library_dir
300
301    if args.channels:
302        lldbtest_config.channels = args.channels
303
304    if args.log_success:
305        lldbtest_config.log_success = args.log_success
306
307    if args.out_of_tree_debugserver:
308        lldbtest_config.out_of_tree_debugserver = args.out_of_tree_debugserver
309
310    # Set SDKROOT if we are using an Apple SDK
311    if args.sysroot is not None:
312        configuration.sdkroot = args.sysroot
313    elif platform_system == "Darwin" and args.apple_sdk:
314        configuration.sdkroot = seven.get_command_output(
315            'xcrun --sdk "%s" --show-sdk-path 2> /dev/null' % (args.apple_sdk)
316        )
317        if not configuration.sdkroot:
318            logging.error("No SDK found with the name %s; aborting...", args.apple_sdk)
319            sys.exit(-1)
320
321    if args.arch:
322        configuration.arch = args.arch
323    else:
324        configuration.arch = platform_machine
325
326    if args.categories_list:
327        configuration.categories_list = set(
328            test_categories.validate(args.categories_list, False)
329        )
330        configuration.use_categories = True
331    else:
332        configuration.categories_list = []
333
334    if args.skip_categories:
335        configuration.skip_categories += test_categories.validate(
336            args.skip_categories, False
337        )
338
339    if args.xfail_categories:
340        configuration.xfail_categories += test_categories.validate(
341            args.xfail_categories, False
342        )
343
344    if args.E:
345        os.environ["CFLAGS_EXTRAS"] = args.E
346
347    if args.dwarf_version:
348        configuration.dwarf_version = args.dwarf_version
349        # We cannot modify CFLAGS_EXTRAS because they're used in test cases
350        # that explicitly require no debug info.
351        os.environ["CFLAGS"] = "-gdwarf-{}".format(configuration.dwarf_version)
352
353    if args.settings:
354        for setting in args.settings:
355            if not len(setting) == 1 or not setting[0].count("="):
356                logging.error(
357                    '"%s" is not a setting in the form "key=value"', setting[0]
358                )
359                sys.exit(-1)
360            setting_list = setting[0].split("=", 1)
361            configuration.settings.append((setting_list[0], setting_list[1]))
362
363    if args.d:
364        sys.stdout.write(
365            "Suspending the process %d to wait for debugger to attach...\n"
366            % os.getpid()
367        )
368        sys.stdout.flush()
369        os.kill(os.getpid(), signal.SIGSTOP)
370
371    if args.f:
372        if any([x.startswith("-") for x in args.f]):
373            usage(parser)
374        configuration.filters.extend(args.f)
375
376    if args.framework:
377        configuration.lldb_framework_path = args.framework
378
379    if args.executable:
380        # lldb executable is passed explicitly
381        lldbtest_config.lldbExec = os.path.abspath(args.executable)
382        if not is_exe(lldbtest_config.lldbExec):
383            lldbtest_config.lldbExec = which(args.executable)
384        if not is_exe(lldbtest_config.lldbExec):
385            logging.error(
386                "%s is not a valid executable to test; aborting...", args.executable
387            )
388            sys.exit(-1)
389
390    if args.excluded:
391        for excl_file in args.excluded:
392            parseExclusion(excl_file)
393
394    if args.p:
395        if args.p.startswith("-"):
396            usage(parser)
397        configuration.regexp = args.p
398
399    if args.t:
400        os.environ["LLDB_COMMAND_TRACE"] = "YES"
401
402    if args.v:
403        configuration.verbose = 2
404
405    # argparse makes sure we have a number
406    if args.sharp:
407        configuration.count = args.sharp
408
409    if sys.platform.startswith("win32"):
410        os.environ["LLDB_DISABLE_CRASH_DIALOG"] = str(args.disable_crash_dialog)
411        os.environ["LLDB_LAUNCH_INFERIORS_WITHOUT_CONSOLE"] = str(True)
412
413    if do_help:
414        usage(parser)
415
416    if args.lldb_platform_name:
417        configuration.lldb_platform_name = args.lldb_platform_name
418    if args.lldb_platform_url:
419        configuration.lldb_platform_url = args.lldb_platform_url
420    if args.lldb_platform_working_dir:
421        configuration.lldb_platform_working_dir = args.lldb_platform_working_dir
422    if platform_system == "Darwin" and args.apple_sdk:
423        configuration.apple_sdk = args.apple_sdk
424    if args.test_build_dir:
425        configuration.test_build_dir = args.test_build_dir
426    if args.lldb_module_cache_dir:
427        configuration.lldb_module_cache_dir = args.lldb_module_cache_dir
428    else:
429        configuration.lldb_module_cache_dir = os.path.join(
430            configuration.test_build_dir, "module-cache-lldb"
431        )
432
433    if args.clang_module_cache_dir:
434        configuration.clang_module_cache_dir = args.clang_module_cache_dir
435    else:
436        configuration.clang_module_cache_dir = os.path.join(
437            configuration.test_build_dir, "module-cache-clang"
438        )
439
440    if args.lldb_libs_dir:
441        configuration.lldb_libs_dir = args.lldb_libs_dir
442    if args.lldb_obj_root:
443        configuration.lldb_obj_root = args.lldb_obj_root
444
445    if args.enabled_plugins:
446        configuration.enabled_plugins = args.enabled_plugins
447
448    # Gather all the dirs passed on the command line.
449    if len(args.args) > 0:
450        configuration.testdirs = [
451            os.path.realpath(os.path.abspath(x)) for x in args.args
452        ]
453
454
455def registerFaulthandler():
456    try:
457        import faulthandler
458    except ImportError:
459        # faulthandler is not available until python3
460        return
461
462    faulthandler.enable()
463    # faulthandler.register is not available on Windows.
464    if getattr(faulthandler, "register", None):
465        faulthandler.register(signal.SIGTERM, chain=True)
466
467
468def setupSysPath():
469    """
470    Add LLDB.framework/Resources/Python to the search paths for modules.
471    As a side effect, we also discover the 'lldb' executable and export it here.
472    """
473
474    # Get the directory containing the current script.
475    if "DOTEST_PROFILE" in os.environ and "DOTEST_SCRIPT_DIR" in os.environ:
476        scriptPath = os.environ["DOTEST_SCRIPT_DIR"]
477    else:
478        scriptPath = os.path.dirname(os.path.abspath(__file__))
479    if not scriptPath.endswith("test"):
480        print("This script expects to reside in lldb's test directory.")
481        sys.exit(-1)
482
483    os.environ["LLDB_TEST"] = scriptPath
484
485    # Set up the root build directory.
486    if not configuration.test_build_dir:
487        raise Exception("test_build_dir is not set")
488    configuration.test_build_dir = os.path.abspath(configuration.test_build_dir)
489
490    # Set up the LLDB_SRC environment variable, so that the tests can locate
491    # the LLDB source code.
492    os.environ["LLDB_SRC"] = lldbsuite.lldb_root
493
494    pluginPath = os.path.join(scriptPath, "plugins")
495    toolsLLDBDAP = os.path.join(scriptPath, "tools", "lldb-dap")
496    toolsLLDBServerPath = os.path.join(scriptPath, "tools", "lldb-server")
497    intelpt = os.path.join(scriptPath, "tools", "intelpt")
498
499    # Insert script dir, plugin dir and lldb-server dir to the sys.path.
500    sys.path.insert(0, pluginPath)
501    # Adding test/tools/lldb-dap to the path makes it easy to
502    # "import lldb_dap_testcase" from the DAP tests
503    sys.path.insert(0, toolsLLDBDAP)
504    # Adding test/tools/lldb-server to the path makes it easy
505    # to "import lldbgdbserverutils" from the lldb-server tests
506    sys.path.insert(0, toolsLLDBServerPath)
507    # Adding test/tools/intelpt to the path makes it easy
508    # to "import intelpt_testcase" from the lldb-server tests
509    sys.path.insert(0, intelpt)
510
511    # This is the root of the lldb git/svn checkout
512    # When this changes over to a package instead of a standalone script, this
513    # will be `lldbsuite.lldb_root`
514    lldbRootDirectory = lldbsuite.lldb_root
515
516    # Some of the tests can invoke the 'lldb' command directly.
517    # We'll try to locate the appropriate executable right here.
518
519    # The lldb executable can be set from the command line
520    # if it's not set, we try to find it now
521    # first, we try the environment
522    if not lldbtest_config.lldbExec:
523        # First, you can define an environment variable LLDB_EXEC specifying the
524        # full pathname of the lldb executable.
525        if "LLDB_EXEC" in os.environ:
526            lldbtest_config.lldbExec = os.environ["LLDB_EXEC"]
527
528    if not lldbtest_config.lldbExec:
529        # Last, check the path
530        lldbtest_config.lldbExec = which("lldb")
531
532    if lldbtest_config.lldbExec and not is_exe(lldbtest_config.lldbExec):
533        print(
534            "'{}' is not a path to a valid executable".format(lldbtest_config.lldbExec)
535        )
536        lldbtest_config.lldbExec = None
537
538    if not lldbtest_config.lldbExec:
539        print(
540            "The 'lldb' executable cannot be located.  Some of the tests may not be run as a result."
541        )
542        sys.exit(-1)
543
544    os.system("%s -v" % lldbtest_config.lldbExec)
545
546    lldbDir = os.path.dirname(lldbtest_config.lldbExec)
547
548    lldbDAPExec = os.path.join(lldbDir, "lldb-dap")
549    if is_exe(lldbDAPExec):
550        os.environ["LLDBDAP_EXEC"] = lldbDAPExec
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 checkDAPSupport():
933    import lldb
934
935    if "LLDBDAP_EXEC" not in os.environ:
936        msg = (
937            "The 'lldb-dap' executable cannot be located and its tests will not be run."
938        )
939    elif lldb.remote_platform:
940        msg = "lldb-dap tests are not compatible with remote platforms and will not be run."
941    else:
942        msg = None
943
944    if msg:
945        if configuration.verbose:
946            print(msg)
947        configuration.skip_categories.append("lldb-dap")
948
949
950def run_suite():
951    # On MacOS X, check to make sure that domain for com.apple.DebugSymbols defaults
952    # does not exist before proceeding to running the test suite.
953    if sys.platform.startswith("darwin"):
954        checkDsymForUUIDIsNotOn()
955
956    # Start the actions by first parsing the options while setting up the test
957    # directories, followed by setting up the search paths for lldb utilities;
958    # then, we walk the directory trees and collect the tests into our test suite.
959    #
960    parseOptionsAndInitTestdirs()
961
962    # Print a stack trace if the test hangs or is passed SIGTERM.
963    registerFaulthandler()
964
965    setupSysPath()
966
967    import lldb
968
969    lldb.SBDebugger.Initialize()
970    lldb.SBDebugger.PrintStackTraceOnError()
971
972    # Use host platform by default.
973    lldb.remote_platform = None
974    lldb.selected_platform = lldb.SBPlatform.GetHostPlatform()
975
976    # Now we can also import lldbutil
977    from lldbsuite.test import lldbutil
978
979    if configuration.lldb_platform_name:
980        print("Setting up remote platform '%s'" % (configuration.lldb_platform_name))
981        lldb.remote_platform = lldb.SBPlatform(configuration.lldb_platform_name)
982        lldb.selected_platform = lldb.remote_platform
983        if not lldb.remote_platform.IsValid():
984            print(
985                "error: unable to create the LLDB platform named '%s'."
986                % (configuration.lldb_platform_name)
987            )
988            exitTestSuite(1)
989        if configuration.lldb_platform_url:
990            # We must connect to a remote platform if a LLDB platform URL was
991            # specified
992            print(
993                "Connecting to remote platform '%s' at '%s'..."
994                % (configuration.lldb_platform_name, configuration.lldb_platform_url)
995            )
996            platform_connect_options = lldb.SBPlatformConnectOptions(
997                configuration.lldb_platform_url
998            )
999            err = lldb.remote_platform.ConnectRemote(platform_connect_options)
1000            if err.Success():
1001                print("Connected.")
1002            else:
1003                print(
1004                    "error: failed to connect to remote platform using URL '%s': %s"
1005                    % (configuration.lldb_platform_url, err)
1006                )
1007                exitTestSuite(1)
1008        else:
1009            configuration.lldb_platform_url = None
1010
1011    if configuration.lldb_platform_working_dir:
1012        print(
1013            "Setting remote platform working directory to '%s'..."
1014            % (configuration.lldb_platform_working_dir)
1015        )
1016        error = lldb.remote_platform.MakeDirectory(
1017            configuration.lldb_platform_working_dir, 448
1018        )  # 448 = 0o700
1019        if error.Fail():
1020            raise Exception(
1021                "making remote directory '%s': %s"
1022                % (configuration.lldb_platform_working_dir, error)
1023            )
1024
1025        if not lldb.remote_platform.SetWorkingDirectory(
1026            configuration.lldb_platform_working_dir
1027        ):
1028            raise Exception(
1029                "failed to set working directory '%s'"
1030                % configuration.lldb_platform_working_dir
1031            )
1032        lldb.selected_platform = lldb.remote_platform
1033    else:
1034        lldb.remote_platform = None
1035        configuration.lldb_platform_working_dir = None
1036        configuration.lldb_platform_url = None
1037
1038    # Set up the working directory.
1039    # Note that it's not dotest's job to clean this directory.
1040    lldbutil.mkdir_p(configuration.test_build_dir)
1041
1042    checkLibcxxSupport()
1043    checkLibstdcxxSupport()
1044    checkWatchpointSupport()
1045    checkDebugInfoSupport()
1046    checkDebugServerSupport()
1047    checkObjcSupport()
1048    checkForkVForkSupport()
1049    checkPexpectSupport()
1050    checkDAPSupport()
1051
1052    skipped_categories_list = ", ".join(configuration.skip_categories)
1053    print(
1054        "Skipping the following test categories: {}".format(
1055            configuration.skip_categories
1056        )
1057    )
1058
1059    for testdir in configuration.testdirs:
1060        for dirpath, dirnames, filenames in os.walk(testdir):
1061            visit("Test", dirpath, filenames)
1062
1063    #
1064    # Now that we have loaded all the test cases, run the whole test suite.
1065    #
1066
1067    # Install the control-c handler.
1068    unittest.signals.installHandler()
1069
1070    #
1071    # Invoke the default TextTestRunner to run the test suite
1072    #
1073    checkCompiler()
1074
1075    if configuration.verbose:
1076        print("compiler=%s" % configuration.compiler)
1077
1078    # Iterating over all possible architecture and compiler combinations.
1079    configString = "arch=%s compiler=%s" % (configuration.arch, configuration.compiler)
1080
1081    # Output the configuration.
1082    if configuration.verbose:
1083        sys.stderr.write("\nConfiguration: " + configString + "\n")
1084
1085    # First, write out the number of collected test cases.
1086    if configuration.verbose:
1087        sys.stderr.write(configuration.separator + "\n")
1088        sys.stderr.write(
1089            "Collected %d test%s\n\n"
1090            % (
1091                configuration.suite.countTestCases(),
1092                configuration.suite.countTestCases() != 1 and "s" or "",
1093            )
1094        )
1095
1096    if configuration.suite.countTestCases() == 0:
1097        logging.error("did not discover any matching tests")
1098        exitTestSuite(1)
1099
1100    # Invoke the test runner.
1101    if configuration.count == 1:
1102        result = unittest.TextTestRunner(
1103            stream=sys.stderr,
1104            verbosity=configuration.verbose,
1105            resultclass=test_result.LLDBTestResult,
1106        ).run(configuration.suite)
1107    else:
1108        # We are invoking the same test suite more than once.  In this case,
1109        # mark __ignore_singleton__ flag as True so the signleton pattern is
1110        # not enforced.
1111        test_result.LLDBTestResult.__ignore_singleton__ = True
1112        for i in range(configuration.count):
1113            result = unittest.TextTestRunner(
1114                stream=sys.stderr,
1115                verbosity=configuration.verbose,
1116                resultclass=test_result.LLDBTestResult,
1117            ).run(configuration.suite)
1118
1119    configuration.failed = not result.wasSuccessful()
1120
1121    if configuration.sdir_has_content and configuration.verbose:
1122        sys.stderr.write(
1123            "Session logs for test failures/errors/unexpected successes"
1124            " can be found in the test build directory\n"
1125        )
1126
1127    if configuration.use_categories and len(configuration.failures_per_category) > 0:
1128        sys.stderr.write("Failures per category:\n")
1129        for category in configuration.failures_per_category:
1130            sys.stderr.write(
1131                "%s - %d\n" % (category, configuration.failures_per_category[category])
1132            )
1133
1134    # Exiting.
1135    exitTestSuite(configuration.failed)
1136
1137
1138if __name__ == "__main__":
1139    print(
1140        __file__
1141        + " is for use as a module only.  It should not be run as a standalone script."
1142    )
1143    sys.exit(-1)
1144