update_data.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. from __future__ import annotations
  2. import re
  3. from collections import defaultdict
  4. from typing import Iterator
  5. from mypy.test.data import DataDrivenTestCase, DataFileCollector, DataFileFix, parse_test_data
  6. def update_testcase_output(
  7. testcase: DataDrivenTestCase, actual: list[str], *, incremental_step: int
  8. ) -> None:
  9. if testcase.xfail:
  10. return
  11. collector = testcase.parent
  12. assert isinstance(collector, DataFileCollector)
  13. for fix in _iter_fixes(testcase, actual, incremental_step=incremental_step):
  14. collector.enqueue_fix(fix)
  15. def _iter_fixes(
  16. testcase: DataDrivenTestCase, actual: list[str], *, incremental_step: int
  17. ) -> Iterator[DataFileFix]:
  18. reports_by_line: dict[tuple[str, int], list[tuple[str, str]]] = defaultdict(list)
  19. for error_line in actual:
  20. comment_match = re.match(
  21. r"^(?P<filename>[^:]+):(?P<lineno>\d+): (?P<severity>error|note|warning): (?P<msg>.+)$",
  22. error_line,
  23. )
  24. if comment_match:
  25. filename = comment_match.group("filename")
  26. lineno = int(comment_match.group("lineno"))
  27. severity = comment_match.group("severity")
  28. msg = comment_match.group("msg")
  29. reports_by_line[filename, lineno].append((severity, msg))
  30. test_items = parse_test_data(testcase.data, testcase.name)
  31. # If we have [out] and/or [outN], we update just those sections.
  32. if any(re.match(r"^out\d*$", test_item.id) for test_item in test_items):
  33. for test_item in test_items:
  34. if (incremental_step < 2 and test_item.id == "out") or (
  35. incremental_step >= 2 and test_item.id == f"out{incremental_step}"
  36. ):
  37. yield DataFileFix(
  38. lineno=testcase.line + test_item.line - 1,
  39. end_lineno=testcase.line + test_item.end_line - 1,
  40. lines=actual + [""] * test_item.trimmed_newlines,
  41. )
  42. return
  43. # Update assertion comments within the sections
  44. for test_item in test_items:
  45. if test_item.id == "case":
  46. source_lines = test_item.data
  47. file_path = "main"
  48. elif test_item.id == "file":
  49. source_lines = test_item.data
  50. file_path = f"tmp/{test_item.arg}"
  51. else:
  52. continue # other sections we don't touch
  53. fix_lines = []
  54. for lineno, source_line in enumerate(source_lines, start=1):
  55. reports = reports_by_line.get((file_path, lineno))
  56. comment_match = re.search(r"(?P<indent>\s+)(?P<comment># [EWN]: .+)$", source_line)
  57. if comment_match:
  58. source_line = source_line[: comment_match.start("indent")] # strip old comment
  59. if reports:
  60. indent = comment_match.group("indent") if comment_match else " "
  61. # multiline comments are on the first line and then on subsequent lines emtpy lines
  62. # with a continuation backslash
  63. for j, (severity, msg) in enumerate(reports):
  64. out_l = source_line if j == 0 else " " * len(source_line)
  65. is_last = j == len(reports) - 1
  66. severity_char = severity[0].upper()
  67. continuation = "" if is_last else " \\"
  68. fix_lines.append(f"{out_l}{indent}# {severity_char}: {msg}{continuation}")
  69. else:
  70. fix_lines.append(source_line)
  71. yield DataFileFix(
  72. lineno=testcase.line + test_item.line - 1,
  73. end_lineno=testcase.line + test_item.end_line - 1,
  74. lines=fix_lines + [""] * test_item.trimmed_newlines,
  75. )