run_com_tests.sh 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. #!/bin/bash
  2. # Build and run the .COM linker harness (tests/ComTest.mod), then verify every
  3. # .COM it produced with an INDEPENDENT checker.
  4. #
  5. # The independent pass matters: ComTest computes the expectations from the same
  6. # Compiler state it is testing, so a bug in the compiler would be invisible to
  7. # it. The Python pass re-derives what the file must contain - the runtime's
  8. # first bytes, a zero gap, a size that covers the data area - from the
  9. # constants only, and cross-checks.
  10. set -u
  11. D=/home/eric/Projets/Projets-Modula2/MyWork/TP3-comp/shell
  12. GM2=/home/eric/bin/Modula2/Gm2/bin/gm2
  13. cd "$D" || exit 9
  14. FLAGS="-fiso"
  15. echo "== support modules =="
  16. [ -f Posix.o ] || cc -c Posix.c || exit 1
  17. for m in TextBuf Compiler Runtime Linker; do
  18. $GM2 $FLAGS -c $m.mod >/tmp/cm_c_$m 2>&1 \
  19. || { echo "COMPILE_FAIL $m"; grep -m5 "error:" /tmp/cm_c_$m; exit 1; }
  20. done
  21. $GM2 $FLAGS -c tests/ComTest.mod >/tmp/cm_c_ComTest 2>&1 \
  22. || { echo "COMPILE_FAIL ComTest"; grep -m5 "error:" /tmp/cm_c_ComTest; exit 1; }
  23. rm -f tests/ct.lst comtest
  24. $GM2 $FLAGS -fgen-module-list=tests/ct.lst -o /dev/null \
  25. tests/ComTest.mod TextBuf.o Posix.o Compiler.o Runtime.o Linker.o \
  26. >/tmp/cm_p1 2>&1
  27. p1=$?
  28. $GM2 $FLAGS -fuse-list=tests/ct.lst -o comtest \
  29. tests/ComTest.mod TextBuf.o Posix.o Compiler.o Runtime.o Linker.o \
  30. >/tmp/cm_p2 2>&1
  31. p2=$?
  32. if [ $p2 -ne 0 ]; then
  33. echo "LINK_FAIL p1_rc=$p1 p2_rc=$p2"
  34. grep -E "error:|undefined" /tmp/cm_p2 | head -10
  35. exit 1
  36. fi
  37. echo "comtest built (p1_rc=$p1, phase 1 rc=1 is the expected rollup)"
  38. # .COM files are written beside the harness, so run it in a scratch dir
  39. OUT=$(mktemp -d) || exit 9
  40. trap 'rm -rf "$OUT"' EXIT
  41. cd "$OUT" || exit 9
  42. ls "$D"/tests/fixtures/*.pas | "$D"/comtest > "$OUT/raw.txt" 2>&1
  43. sed 's/^.*fixtures\///' "$OUT/raw.txt"
  44. echo "----------------------------------------------------------------"
  45. nok=$(grep -c " OK com=" "$OUT/raw.txt")
  46. nerr=$(grep -c " ERROR " "$OUT/raw.txt")
  47. nbad=$(grep -cE "WRITE_COM_FAILED|CANNOT" "$OUT/raw.txt")
  48. echo "linked: $nok .COM files, $nerr fixtures rejected at compile time, $nbad harness failures"
  49. if [ "$nbad" -ne 0 ]; then
  50. echo "RESULT: FAIL (harness could not link every compiling fixture)"
  51. exit 1
  52. fi
  53. # ---- independent verification of the bytes on disk ------------------------
  54. python3 - "$OUT" <<'PYEOF'
  55. import sys, os, re, glob
  56. out = sys.argv[1]
  57. # Compiler layout constants, restated here on purpose: the checker must not
  58. # ask the code under test what the answer is.
  59. RTSZ = 385 # Runtime.RT_Size()
  60. DATAB = RTSZ + 0x1000 # Compiler: data base = rtSz + 1000H
  61. # initmem: MOV AX,AX / MOV DX,[SI+4] / MOV CX,[SI+8]
  62. HEAD = '8B C0 8B 54 04 8B 4C 08'
  63. raw = open(os.path.join(out, 'raw.txt')).read()
  64. rows = re.findall(r'(\S+\.pas)\s+OK\s+com=(\d+)\s+image=(\d+)\s+data=(\d+)\s+nonzeroInGap=(\d+)', raw)
  65. if not rows:
  66. print('RESULT: FAIL (no linked fixtures found in output)')
  67. sys.exit(1)
  68. bad = 0
  69. for name, com, image, data, nzg in rows:
  70. com, image, data, nzg = int(com), int(image), int(data), int(nzg)
  71. # ComTest writes the .COM by BASENAME beside itself (it cannot graft a
  72. # directory onto a source path), so the checker must look for the bare
  73. # name, not the full source path the fixture was read from.
  74. path = os.path.join(out, os.path.basename(name)[:-4] + '.COM')
  75. errs = []
  76. if not os.path.exists(path):
  77. errs.append('no .COM file')
  78. d = b''
  79. else:
  80. d = open(path, 'rb').read()
  81. if d[:8].hex(' ').upper() != HEAD:
  82. errs.append('runtime not at offset 0 (first bytes %s)' % d[:8].hex(' ').upper())
  83. if len(d) != com:
  84. errs.append('file is %d bytes, harness said %d' % (len(d), com))
  85. if DATAB + data != len(d):
  86. errs.append('size %d != dataBase+data %d' % (len(d), DATAB + data))
  87. # the gap between the image and the data area must be entirely zero
  88. gap = d[image:DATAB]
  89. if any(gap):
  90. errs.append('%d non-zero bytes in the code/data gap' % sum(1 for b in gap if b))
  91. if nzg != 0:
  92. errs.append('harness itself reported %d non-zero gap bytes' % nzg)
  93. # the program must start exactly where the runtime ends
  94. if image < RTSZ:
  95. errs.append('image %d shorter than the runtime %d' % (image, RTSZ))
  96. # the program header sits at offset rtSz, and its words must describe the
  97. # image that is actually in the file
  98. if len(d) > RTSZ + 8:
  99. hdr = d[RTSZ:RTSZ+16]
  100. flag = int.from_bytes(hdr[0:2], 'little')
  101. cs = int.from_bytes(hdr[2:4], 'little')
  102. ds = int.from_bytes(hdr[4:6], 'little')
  103. heap = int.from_bytes(hdr[6:8], 'little')
  104. if flag != 1:
  105. errs.append('hdrFlag=%d' % flag)
  106. # hdrCS is pc, the end of the code, and the harness's `image` is
  107. # rtSz + code ALREADY - so the two are the same number. Adding
  108. # RTSZ here counted the runtime twice.
  109. if cs != image:
  110. errs.append('hdrCS=%d, want %d (end of image)' % (cs, image))
  111. if ds != DATAB:
  112. errs.append('hdrDS=%d, want %d' % (ds, DATAB))
  113. if heap != DATAB + data:
  114. errs.append('hdrHeap=%d, want %d' % (heap, DATAB + data))
  115. if errs:
  116. bad += 1
  117. print(' %-24s FAIL %s' % (os.path.basename(name)[:-4], '; '.join(errs)))
  118. else:
  119. print(' %-24s PASS %d bytes' % (os.path.basename(name)[:-4], com))
  120. print('----------------------------------------------------------------')
  121. print('independent .COM check: %d checked, %d failed' % (len(rows), bad))
  122. sys.exit(1 if bad else 0)
  123. PYEOF
  124. rc=$?
  125. [ "$rc" -eq 0 ] || exit 1
  126. echo "RESULT: ALL PASS"