|
| 1 | +import os |
| 2 | +import shlex |
| 3 | +import pathlib |
| 4 | +from update_verify_tests.core import check_expectations |
| 5 | + |
| 6 | +""" |
| 7 | +This file provides the `uvt_lit_plugin` function, which is invoked on failed RUN lines when lit is executed with --update-tests. |
| 8 | +It checks whether the failed command is a swift compiler invocation with the `-verify` flag and analyses the output to try to |
| 9 | +repair the failed test. If the updated file was originally created by `split-file` it updates the corresponding slice in the source file. |
| 10 | +""" |
| 11 | + |
| 12 | + |
| 13 | +class SplitFileTarget: |
| 14 | + def __init__(self, slice_start_idx, test_path, lines, name): |
| 15 | + self.slice_start_idx = slice_start_idx |
| 16 | + self.test_path = test_path |
| 17 | + self.lines = lines |
| 18 | + self.name = name |
| 19 | + |
| 20 | + def copyFrom(self, source): |
| 21 | + lines_before = self.lines[: self.slice_start_idx + 1] |
| 22 | + self.lines = self.lines[self.slice_start_idx + 1 :] |
| 23 | + slice_end_idx = None |
| 24 | + for i, l in enumerate(self.lines): |
| 25 | + if SplitFileTarget._get_split_line_path(l) != None: |
| 26 | + slice_end_idx = i |
| 27 | + break |
| 28 | + if slice_end_idx is not None: |
| 29 | + lines_after = self.lines[slice_end_idx:] |
| 30 | + else: |
| 31 | + lines_after = [] |
| 32 | + with open(source, "r") as f: |
| 33 | + new_lines = lines_before + f.readlines() + lines_after |
| 34 | + with open(self.test_path, "w") as f: |
| 35 | + for l in new_lines: |
| 36 | + f.write(l) |
| 37 | + |
| 38 | + def __str__(self): |
| 39 | + return f"slice {self.name} in {self.test_path}" |
| 40 | + |
| 41 | + @staticmethod |
| 42 | + def get_target_dir(commands, test_path): |
| 43 | + # posix=True breaks Windows paths because \ is treated as an escaping character |
| 44 | + for cmd in commands: |
| 45 | + split = shlex.split(cmd, posix=False) |
| 46 | + if "split-file" not in split: |
| 47 | + continue |
| 48 | + start_idx = split.index("split-file") |
| 49 | + split = split[start_idx:] |
| 50 | + if len(split) < 3: |
| 51 | + continue |
| 52 | + p = unquote(split[1].strip()) |
| 53 | + if not test_path.samefile(p): |
| 54 | + continue |
| 55 | + return unquote(split[2].strip()) |
| 56 | + return None |
| 57 | + |
| 58 | + @staticmethod |
| 59 | + def create(path, commands, test_path, target_dir): |
| 60 | + path = pathlib.Path(path) |
| 61 | + with open(test_path, "r") as f: |
| 62 | + lines = f.readlines() |
| 63 | + for i, l in enumerate(lines): |
| 64 | + p = SplitFileTarget._get_split_line_path(l) |
| 65 | + if p and path.samefile(os.path.join(target_dir, p)): |
| 66 | + idx = i |
| 67 | + break |
| 68 | + else: |
| 69 | + return None |
| 70 | + return SplitFileTarget(idx, test_path, lines, p) |
| 71 | + |
| 72 | + @staticmethod |
| 73 | + def _get_split_line_path(l): |
| 74 | + if len(l) < 6: |
| 75 | + return None |
| 76 | + if l.startswith("//"): |
| 77 | + l = l[2:] |
| 78 | + else: |
| 79 | + l = l[1:] |
| 80 | + if l.startswith("--- "): |
| 81 | + l = l[4:] |
| 82 | + else: |
| 83 | + return None |
| 84 | + return l.rstrip() |
| 85 | + |
| 86 | + |
| 87 | +def unquote(s): |
| 88 | + if len(s) > 1 and s[0] == s[-1] and (s[0] == '"' or s[0] == "'"): |
| 89 | + return s[1:-1] |
| 90 | + return s |
| 91 | + |
| 92 | + |
| 93 | +def propagate_split_files(test_path, updated_files, commands): |
| 94 | + test_path = pathlib.Path(test_path) |
| 95 | + split_target_dir = SplitFileTarget.get_target_dir(commands, test_path) |
| 96 | + if not split_target_dir: |
| 97 | + return updated_files |
| 98 | + |
| 99 | + new = [] |
| 100 | + for file in updated_files: |
| 101 | + target = SplitFileTarget.create(file, commands, test_path, split_target_dir) |
| 102 | + if target: |
| 103 | + target.copyFrom(file) |
| 104 | + new.append(target) |
| 105 | + else: |
| 106 | + new.append(file) |
| 107 | + return new |
| 108 | + |
| 109 | + |
| 110 | +def uvt_lit_plugin(result, test, commands): |
| 111 | + if ( |
| 112 | + not any(e.endswith("swift-frontend") for e in result.command.args) |
| 113 | + or not "-verify" in result.command.args |
| 114 | + ): |
| 115 | + return None |
| 116 | + |
| 117 | + prefix = "" |
| 118 | + for i, arg in enumerate(result.command.args): |
| 119 | + if arg == "-verify-additional-prefix": |
| 120 | + if i + 1 >= len(result.command.args): |
| 121 | + return None |
| 122 | + if prefix: |
| 123 | + # can only handle at most 1 additional prefix at the moment |
| 124 | + return None |
| 125 | + prefix = result.command.args[i + 1] |
| 126 | + |
| 127 | + (err, updated_files) = check_expectations(result.stderr.split("\n"), prefix) |
| 128 | + if err: |
| 129 | + return err |
| 130 | + |
| 131 | + updated_files = propagate_split_files(test.getFilePath(), updated_files, commands) |
| 132 | + |
| 133 | + if len(updated_files) > 1: |
| 134 | + return "\n\t".join(["updated files:"] + updated_files) |
| 135 | + return f"updated file: {updated_files[0]}" |
0 commit comments