Skip to content

Commit c460d11

Browse files
author
H. Peter Anvin (Intel)
committed
travis: support .xz compressed golden reference files
Some tests may by necessity generate very large output files. Allow .xz compression of the reference files to avoid bloating the git repository too much. The committer of very large files will need to carefully consider the xz options used to maximize compressibility especially of highly regular files. This commit was AI-assisted (Copilot/Claude Sonnet 5). Signed-off-by: H. Peter Anvin (Intel) <hpa@zytor.com>
1 parent 386f5d4 commit c460d11

1 file changed

Lines changed: 89 additions & 42 deletions

File tree

travis/nasm-t.py

Lines changed: 89 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
import subprocess
44
import argparse
55
import difflib
6-
import filecmp
76
import fnmatch
87
import json
8+
import lzma
99
import sys
1010
import re
1111
import os
@@ -115,11 +115,22 @@
115115
parser.print_help()
116116
sys.exit(64)
117117

118+
#
119+
# Read a reference/golden file, transparently decompressing it if
120+
# only a "<path>.xz" variant is present. This lets very large but
121+
# highly repetitive reference files (e.g. object files with tens of
122+
# thousands of sections) be stored compressed instead of bloating
123+
# the git repository.
124+
def read_ref_file(path):
125+
if os.path.exists(path):
126+
with open(path, "rb") as f:
127+
return f.read()
128+
xz_path = path + '.xz'
129+
with lzma.open(xz_path, "rb") as f:
130+
return f.read()
131+
118132
def read_stdfile(path):
119-
with open(path, "rb") as f:
120-
data = f.read().decode("utf-8","replace")
121-
f.close()
122-
return data
133+
return read_ref_file(path).decode("utf-8","replace")
123134

124135
#
125136
# Check if descriptor has mandatory fields
@@ -265,30 +276,32 @@ def test_updated(test):
265276
print("=== Test %s UPDATED ===" % (test))
266277
return True
267278

268-
def hexdump(path):
279+
def hexdump(data):
269280
dump = ''
270281
addr = 0
271-
with open(path, 'rb') as f:
272-
while b := f.read(16):
273-
dump += "%08x " % (addr)
274-
for i in range(16):
275-
if (i == 8):
276-
dump += " -"
277-
if (i >= len(b)):
278-
dump += " "
279-
else:
280-
dump += " %02x" % b[i]
281-
dump += " |"
282-
for i in range(16):
283-
if (i >= len(b)):
284-
c = ord(' ')
285-
else:
286-
c = b[i]
287-
if (c < 32 or c > 126):
288-
c = ord('.')
289-
dump += chr(c)
290-
dump += "|\n";
291-
addr += 16
282+
pos = 0
283+
while pos < len(data):
284+
b = data[pos:pos+16]
285+
dump += "%08x " % (addr)
286+
for i in range(16):
287+
if (i == 8):
288+
dump += " -"
289+
if (i >= len(b)):
290+
dump += " "
291+
else:
292+
dump += " %02x" % b[i]
293+
dump += " |"
294+
for i in range(16):
295+
if (i >= len(b)):
296+
c = ord(' ')
297+
else:
298+
c = b[i]
299+
if (c < 32 or c > 126):
300+
c = ord('.')
301+
dump += chr(c)
302+
dump += "|\n";
303+
addr += 16
304+
pos += 16
292305
return dump
293306

294307
def show_std(stdname, data):
@@ -314,12 +327,9 @@ def cmp_std(from_name, from_data, match_name, match_data):
314327
return False
315328
return True
316329

317-
def show_diff(test, patha, pathb):
318-
try:
319-
sa = hexdump(patha)
320-
sb = hexdump(pathb)
321-
except OSError:
322-
return test_fail(test, "Can't create dumps")
330+
def show_diff(test, patha, adata, pathb, bdata):
331+
sa = hexdump(adata)
332+
sb = hexdump(bdata)
323333

324334
print("\t--- hexdump %s" % (patha))
325335
for i in sa.split("\n"):
@@ -424,8 +434,14 @@ def test_run(desc):
424434
if desc['_wait'] == 1:
425435
continue
426436
print("\tComparing %s %s" % (output, match))
427-
if filecmp.cmp(match, output) == False:
428-
show_diff(desc['_test-name'], match, output)
437+
try:
438+
match_data = read_ref_file(match)
439+
with open(output, "rb") as f:
440+
out_data = f.read()
441+
except OSError:
442+
return test_fail(desc['_test-name'], "Can't read " + match + " or " + output)
443+
if match_data != out_data:
444+
show_diff(desc['_test-name'], match, match_data, output, out_data)
429445
return test_fail(desc['_test-name'], match + " and " + output + " files are different")
430446
elif 'stdout' in t:
431447
print("\tComparing stdout")
@@ -464,6 +480,38 @@ def test_run(desc):
464480

465481
return test_pass(desc['_test-name'])
466482

483+
#
484+
# Compress data for a reference file, trying a couple of xz filter
485+
# chains suited to different kinds of content (plain text/binary vs.
486+
# highly repetitive fixed-stride records), and keep whichever is
487+
# smallest.
488+
def compress_ref_data(data):
489+
candidates = [lzma.compress(data, preset = 9 | lzma.PRESET_EXTREME)]
490+
if len(data) >= 4096:
491+
filters = [
492+
{'id': lzma.FILTER_DELTA, 'dist': 256},
493+
{'id': lzma.FILTER_LZMA2, 'preset': 9 | lzma.PRESET_EXTREME},
494+
]
495+
try:
496+
candidates.append(lzma.compress(data, format = lzma.FORMAT_XZ,
497+
filters = filters))
498+
except lzma.LZMAError:
499+
pass
500+
return min(candidates, key = len)
501+
502+
#
503+
# Write out a reference file. If a compressed "<path>.xz" copy is
504+
# already checked in (and no plaintext copy exists), keep storing it
505+
# compressed; otherwise write plaintext as before.
506+
def write_ref_file(path, data):
507+
xz_path = path + '.xz'
508+
if os.path.exists(xz_path) and not os.path.exists(path):
509+
with open(xz_path, "wb") as f:
510+
f.write(compress_ref_data(data))
511+
else:
512+
with open(path, "wb") as f:
513+
f.write(data)
514+
467515
#
468516
# Compile sources and generate new targets
469517
def test_update(desc):
@@ -483,19 +531,18 @@ def test_update(desc):
483531
output = desc['_base-dir'] + os.sep + t['output']
484532
match = desc['_base-dir'] + os.sep + t['match']
485533
print("\tMoving %s to %s" % (output, match))
486-
os.rename(output, match)
534+
with open(output, "rb") as f:
535+
data = f.read()
536+
os.remove(output)
537+
write_ref_file(match, data)
487538
if 'stdout' in t:
488539
match = desc['_base-dir'] + os.sep + t['stdout']
489540
print("\tMoving %s to %s" % ('stdout', match))
490-
with open(match, "wb") as f:
491-
f.write(stdout.encode("utf-8"))
492-
f.close()
541+
write_ref_file(match, stdout.encode("utf-8"))
493542
if 'stderr' in t:
494543
match = desc['_base-dir'] + os.sep + t['stderr']
495544
print("\tMoving %s to %s" % ('stderr', match))
496-
with open(match, "wb") as f:
497-
f.write(stderr.encode("utf-8"))
498-
f.close()
545+
write_ref_file(match, stderr.encode("utf-8"))
499546

500547
return test_updated(desc['_test-name'])
501548

0 commit comments

Comments
 (0)