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