1#!/usr/bin/env python3
2
3from multiprocessing import Pool
4import multiprocessing
5import argparse
6import tempfile
7import logging
8import os
9import subprocess
10
11
12def run_reproducer(path):
13    proc = subprocess.Popen([LLDB, '--replay', path],
14                            stdout=subprocess.PIPE,
15                            stderr=subprocess.PIPE)
16    reason = None
17    try:
18        outs, errs = proc.communicate(timeout=TIMEOUT)
19        success = proc.returncode == 0
20        result = 'PASSED' if success else 'FAILED'
21        if not success:
22            outs = outs.decode()
23            errs = errs.decode()
24            # Do some pattern matching to find out the cause of the failure.
25            if 'Encountered unexpected packet during replay' in errs:
26                reason = 'Unexpected packet'
27            elif 'Assertion failed' in errs:
28                reason = 'Assertion failed'
29            elif 'UNREACHABLE' in errs:
30                reason = 'Unreachable executed'
31            elif 'Segmentation fault' in errs:
32                reason = 'Segmentation fault'
33            elif 'Illegal instruction' in errs:
34                reason = 'Illegal instruction'
35            else:
36                reason = f'Exit code {proc.returncode}'
37    except subprocess.TimeoutExpired:
38        proc.kill()
39        success = False
40        outs, errs = proc.communicate()
41        result = 'TIMEOUT'
42
43    if not FAILURE_ONLY or not success:
44        reason_str = f' ({reason})' if reason else ''
45        print(f'{result}: {path}{reason_str}')
46        if VERBOSE:
47            if outs:
48                print(outs)
49            if errs:
50                print(errs)
51
52
53def find_reproducers(path):
54    for root, dirs, files in os.walk(path):
55        for dir in dirs:
56            _, extension = os.path.splitext(dir)
57            if dir.startswith('Test') and extension == '.py':
58                yield os.path.join(root, dir)
59
60
61if __name__ == '__main__':
62    parser = argparse.ArgumentParser(
63        description='LLDB API Test Replay Driver. '
64        'Replay one or more reproducers in parallel using the specified LLDB driver. '
65        'The script will look for reproducers generated by the API lit test suite. '
66        'To generate the reproducers, pass --param \'lldb-run-with-repro=capture\' to lit.'
67    )
68    parser.add_argument(
69        '-j',
70        '--threads',
71        type=int,
72        default=multiprocessing.cpu_count(),
73        help='Number of threads. The number of CPU threads if not specified.')
74    parser.add_argument(
75        '-t',
76        '--timeout',
77        type=int,
78        default=60,
79        help='Replay timeout in seconds. 60 seconds if not specified.')
80    parser.add_argument(
81        '-p',
82        '--path',
83        type=str,
84        default=os.getcwd(),
85        help=
86        'Path to the directory containing the reproducers. The current working directory if not specified.'
87    )
88    parser.add_argument('-l',
89                        '--lldb',
90                        type=str,
91                        required=True,
92                        help='Path to the LLDB command line driver')
93    parser.add_argument('-v',
94                        '--verbose',
95                        help='Print replay output.',
96                        action='store_true')
97    parser.add_argument('--failure-only',
98                        help='Only log failures.',
99                        action='store_true')
100    args = parser.parse_args()
101
102    global LLDB
103    global TIMEOUT
104    global VERBOSE
105    global FAILURE_ONLY
106    LLDB = args.lldb
107    TIMEOUT = args.timeout
108    VERBOSE = args.verbose
109    FAILURE_ONLY = args.failure_only
110
111    print(
112        f'Replaying reproducers in {args.path} with {args.threads} threads and a {args.timeout} seconds timeout'
113    )
114
115    try:
116        pool = Pool(args.threads)
117        pool.map(run_reproducer, find_reproducers(args.path))
118    except KeyboardInterrupt:
119        print('Interrupted')
120