33import subprocess
44import argparse
55import difflib
6- import filecmp
76import fnmatch
87import json
8+ import lzma
99import sys
1010import re
1111import os
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+
118132def 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
294307def 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 ("\t Comparing %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 ("\t Comparing 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
469517def 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 ("\t Moving %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 ("\t Moving %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 ("\t Moving %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