|
| 1 | +import os |
| 2 | +import os.path |
| 3 | +import shutil |
| 4 | +import subprocess |
| 5 | +import sys |
| 6 | +import tempfile |
| 7 | +from textwrap import dedent |
| 8 | +import traceback |
| 9 | + |
| 10 | + |
| 11 | +HOWTO_DIR = os.path.dirname(__file__) |
| 12 | +HOWTO_FILE = os.path.join(HOWTO_DIR, 'multiple-interpreters.rst') |
| 13 | + |
| 14 | +VERBOSITY = 3 |
| 15 | + |
| 16 | + |
| 17 | +def get_examples(lines): |
| 18 | + examples = {} |
| 19 | + current = None |
| 20 | + expected = None |
| 21 | + start = None |
| 22 | + for lno, line in enumerate(lines, 1): |
| 23 | + if current is None: |
| 24 | + if line.endswith('::'): |
| 25 | + # It *might* be an example. |
| 26 | + current = [] |
| 27 | + else: |
| 28 | + if not current: |
| 29 | + if line.strip(): |
| 30 | + if line == ' from concurrent import interpreters': |
| 31 | + # It *is* an example. |
| 32 | + current.append(line) |
| 33 | + expected = [] |
| 34 | + start = lno |
| 35 | + else: |
| 36 | + # It wasn't actually an example. |
| 37 | + current = None |
| 38 | + elif not line.strip() or line.startswith(' '): |
| 39 | + # The example continues. |
| 40 | + current.append(line) |
| 41 | + before, sep, after = line.partition('# prints: ') |
| 42 | + if sep: |
| 43 | + assert not before.strip(), before |
| 44 | + expected.append(after) |
| 45 | + else: |
| 46 | + # We've reached the end of the example. |
| 47 | + assert not current[-1].strip(), current |
| 48 | + example = dedent(os.linesep.join(current)) |
| 49 | + expected = ''.join(f'{l}{os.linesep}' for l in expected) |
| 50 | + examples[start] = (example, expected) |
| 51 | + current = expected = start = None |
| 52 | + |
| 53 | + if line.endswith('::'): |
| 54 | + # It *might* be an example. |
| 55 | + current = [] |
| 56 | + if current: |
| 57 | + if current[-1].strip(): |
| 58 | + current.append('') |
| 59 | + example = dedent(os.linesep.join(current)) |
| 60 | + expected = ''.join(f'{l}{os.linesep}' for l in expected) |
| 61 | + examples[start] = (example, expected) |
| 62 | + current = expected = start = None |
| 63 | + return examples |
| 64 | + |
| 65 | + |
| 66 | +def write_example(examplesdir, name, text): |
| 67 | + filename = os.path.join(examplesdir, f'example-{name}.py') |
| 68 | + with open(filename, 'w') as outfile: |
| 69 | + outfile.write(text) |
| 70 | + return filename, name, text |
| 71 | + |
| 72 | + |
| 73 | +def run_example(filename, expected): |
| 74 | + if isinstance(expected, str): |
| 75 | + rc = 0 |
| 76 | + stdout = expected |
| 77 | + stderr = '' |
| 78 | + else: |
| 79 | + rc, stdout, stderr = expected |
| 80 | + |
| 81 | + proc = subprocess.run( |
| 82 | + [sys.executable, filename], |
| 83 | + stdout=subprocess.PIPE, |
| 84 | + stderr=subprocess.PIPE, |
| 85 | + text=True, |
| 86 | + cwd=os.path.dirname(filename), |
| 87 | + ) |
| 88 | + if proc.returncode != rc: |
| 89 | + if proc.returncode != 0 and proc.stderr: |
| 90 | + print(proc.stderr) |
| 91 | + raise AssertionError((proc.returncode, rc)) |
| 92 | + assert proc.stdout == stdout, (proc.stdout, stdout) |
| 93 | + assert proc.stderr == stderr, (proc.stderr, stderr) |
| 94 | + |
| 95 | + |
| 96 | +####################################### |
| 97 | +# the script |
| 98 | + |
| 99 | +def parse_args(argv=sys.argv[1:], prog=sys.argv[0]): |
| 100 | + import argparse |
| 101 | + parser = argparse.ArgumentParser(prog=prog) |
| 102 | + |
| 103 | + parser.add_argument('--dry-run', dest='dryrun', action='store_true') |
| 104 | + |
| 105 | + parser.add_argument('-v', '--verbose', action='count', default=0) |
| 106 | + parser.add_argument('-q', '--quiet', action='count', default=0) |
| 107 | + |
| 108 | + parser.add_argument('requested', nargs='*') |
| 109 | + |
| 110 | + args = parser.parse_args(argv) |
| 111 | + ns = vars(args) |
| 112 | + |
| 113 | + args.verbosity = max(0, VERBOSITY + ns.pop('verbose') - ns.pop('quiet')) |
| 114 | + |
| 115 | + requested = [] |
| 116 | + for req in args.requested: |
| 117 | + for i in req.replace(',', ' ').split(): |
| 118 | + requested.append(int(i)) |
| 119 | + args.requested = requested or None |
| 120 | + |
| 121 | + return ns |
| 122 | + |
| 123 | + |
| 124 | +def main(requested=None, *, verbosity=VERBOSITY, dryrun=False): |
| 125 | + with open(HOWTO_FILE) as infile: |
| 126 | + examples = get_examples(l.rstrip(os.linesep) for l in infile) |
| 127 | + examplesdir = tempfile.mkdtemp(prefix='multi-interp-howto-') |
| 128 | + try: |
| 129 | + if requested: |
| 130 | + requested = set(requested) |
| 131 | + _req = f', {len(requested)} requested' |
| 132 | + else: |
| 133 | + _req = '' |
| 134 | + summary = f'# ({len(examples)} found{_req})' |
| 135 | + print(summary) |
| 136 | + |
| 137 | + failed = [] |
| 138 | + for i, (lno, (text, expected)) in enumerate(examples.items(), 1): |
| 139 | + if requested and i not in requested: |
| 140 | + continue |
| 141 | + name = 'multiinterp-{i}' |
| 142 | + print() |
| 143 | + print('#'*60) |
| 144 | + print(f'# example {i} ({os.path.relpath(HOWTO_FILE)}:{lno})') |
| 145 | + print('#'*60) |
| 146 | + print() |
| 147 | + if verbosity > VERBOSITY or (verbosity == VERBOSITY and dryrun): |
| 148 | + print(text) |
| 149 | + print('----') |
| 150 | + if expected.rstrip(): |
| 151 | + print(expected.rstrip(os.linesep)) |
| 152 | + print('----') |
| 153 | + if dryrun: |
| 154 | + continue |
| 155 | + filename, _, _ = write_example(examplesdir, name, text) |
| 156 | + try: |
| 157 | + run_example(filename, expected) |
| 158 | + except Exception: |
| 159 | + traceback.print_exc() |
| 160 | + failed.append(str(i)) |
| 161 | + |
| 162 | + req = f'/{len(requested)}' if requested else '' |
| 163 | + if failed: |
| 164 | + print() |
| 165 | + print(f'{len(failed)} failed: {",".join(failed)}') |
| 166 | + print(summary) |
| 167 | + elif verbosity > VERBOSITY or dryrun: |
| 168 | + print() |
| 169 | + print(f'{len(failed)} failed') |
| 170 | + print(summary) |
| 171 | + |
| 172 | + return len(failed) |
| 173 | + finally: |
| 174 | + shutil.rmtree(examplesdir) |
| 175 | + |
| 176 | + |
| 177 | +if __name__ == '__main__': |
| 178 | + kwargs = parse_args() |
| 179 | + ec = main(**kwargs) |
| 180 | + sys.exit(ec) |
0 commit comments