corpus_check.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. #!/usr/bin/env python3
  2. """Compile the TP3 example corpus in Sources/Dezip/pascsrc.
  3. The corpus is deliberately outside the build tree, so it is easy for a compiler
  4. change to regress it without any of the fixture suites noticing. This script
  5. runs each source in its own compiletest process (one crashing file must not hide
  6. the rest), prints a compact result, and requires the whole corpus to compile.
  7. python3 tests/corpus_check.py
  8. python3 tests/corpus_check.py --expected 71
  9. python3 tests/corpus_check.py --verbose
  10. A missing Sources tree is a skip rather than a failure: the corpus is user
  11. material and is not tracked by git. Once the tree exists, both the file count
  12. and every verdict are checked, so adding or losing a source is visible too.
  13. """
  14. import argparse
  15. import pathlib
  16. import re
  17. import subprocess
  18. import sys
  19. HERE = pathlib.Path(__file__).resolve().parent
  20. SHELL = HERE.parent
  21. SOURCES = SHELL.parent / "Sources"
  22. CORPUS = SOURCES / "Dezip" / "pascsrc"
  23. COMPILETEST = SHELL / "compiletest"
  24. BUILD = HERE / "run_compile_tests.sh"
  25. def build_compiletest() -> bool:
  26. """Build the harness through its single, timestamp-checked recipe."""
  27. r = subprocess.run([str(BUILD), "--build-only"], cwd=SHELL,
  28. capture_output=True, text=True)
  29. if r.returncode != 0:
  30. print(r.stdout, end="")
  31. print(r.stderr, end="", file=sys.stderr)
  32. return False
  33. return True
  34. def compile_one(path: pathlib.Path) -> tuple[bool, str]:
  35. try:
  36. r = subprocess.run([str(COMPILETEST)],
  37. input=str(path) + "\n",
  38. capture_output=True, text=True, timeout=5)
  39. except subprocess.TimeoutExpired:
  40. return False, "timeout"
  41. out = r.stdout + r.stderr
  42. if re.search(r"\bOK\s+code=\d+\s+data=\d+", out):
  43. return True, "OK"
  44. m = re.search(r"\bERROR\s+(\d+)\s+at pos\s+(\d+)", out)
  45. if m:
  46. return False, "ERROR %s at pos %s" % (m.group(1), m.group(2))
  47. if "ERROR" in out:
  48. return False, out.strip().splitlines()[-1]
  49. return False, "no compile result (exit %d)" % r.returncode
  50. def main() -> int:
  51. ap = argparse.ArgumentParser(description=__doc__)
  52. ap.add_argument("--expected", type=int, default=71,
  53. help="required number of .pas files (default: 71)")
  54. ap.add_argument("--verbose", action="store_true",
  55. help="print every passing source as well as failures")
  56. args = ap.parse_args()
  57. if not CORPUS.is_dir():
  58. print("SKIP: %s does not exist" % CORPUS)
  59. return 0
  60. if not build_compiletest():
  61. print("FAIL: compiletest did not build")
  62. return 1
  63. files = sorted(CORPUS.rglob("*.[pP][aA][sS]"))
  64. if len(files) != args.expected:
  65. print("FAIL: expected %d .pas files, found %d in %s"
  66. % (args.expected, len(files), CORPUS))
  67. for p in files:
  68. print(" " + str(p.relative_to(SOURCES)))
  69. return 1
  70. failed = []
  71. for p in files:
  72. ok, detail = compile_one(p)
  73. rel = p.relative_to(SOURCES)
  74. if ok:
  75. if args.verbose:
  76. print(" PASS %s" % rel)
  77. else:
  78. failed.append((rel, detail))
  79. print(" FAIL %s %s" % (rel, detail))
  80. print("\ncorpus: %d passed, %d failed (of %d)"
  81. % (len(files) - len(failed), len(failed), len(files)))
  82. return 1 if failed else 0
  83. if __name__ == "__main__":
  84. sys.exit(main())