Skip to content

Commit 4d624fc

Browse files
authored
Merge pull request #2031 from su2code/feature_tolerance_for_file_diff
Add word-by-word comparison with tolerance and threshold to file diff.
2 parents 8b95cd7 + 757cf5d commit 4d624fc

1 file changed

Lines changed: 110 additions & 9 deletions

File tree

TestCases/TestCase.py

Lines changed: 110 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ def print_vals(vals, name="Values"):
3434
"""Print an array of floats."""
3535
print(name + ': ' + ', '.join('{:f}'.format(v) for v in vals))
3636

37+
def is_float(test_string):
38+
try:
39+
float(test_string)
40+
return True
41+
except ValueError:
42+
return False
3743

3844
class TestCase:
3945

@@ -104,6 +110,8 @@ def __init__(self,tag_in):
104110
self.command = self.Command()
105111
self.timeout = 0
106112
self.tol = 0.0
113+
self.tol_file_percent = 0.0
114+
self.comp_threshold = 0.0
107115

108116
# Options for file-comparison tests
109117
self.reference_file = "of_grad.dat.ref"
@@ -313,6 +321,7 @@ def run_filediff(self):
313321
print("Output from the failed case:")
314322
subprocess.call(["cat", logfilename])
315323

324+
diff_time_start = datetime.datetime.now()
316325
if not timed_out and passed:
317326
# Compare files
318327
fromfile = self.reference_file
@@ -325,7 +334,90 @@ def run_filediff(self):
325334
try:
326335
todate = time.ctime(os.stat(tofile).st_mtime)
327336
tolines = open(tofile, 'U').readlines()
328-
diff = list(difflib.unified_diff(fromlines, tolines, fromfile, tofile, fromdate, todate))
337+
338+
# If file tolerance is set to 0, make regular diff
339+
if self.tol_file_percent == 0.0:
340+
diff = list(difflib.unified_diff(fromlines, tolines, fromfile, tofile, fromdate, todate))
341+
342+
# Else test word by word with given tolerance
343+
else:
344+
345+
diff = []
346+
max_delta = 0
347+
compare_counter = 0
348+
ignore_counter = 0
349+
350+
# Assert that both files have the same number of lines
351+
if len(fromlines) != len(tolines):
352+
diff = ["ERROR: Number of lines in " + fromfile + " and " + tofile + " differ."]
353+
passed = False
354+
355+
# Loop through all lines
356+
for i_line in range(0, len(fromlines)):
357+
358+
if passed == False: break
359+
360+
# Extract next line and split it
361+
from_line = fromlines[i_line].split()
362+
to_line = tolines[i_line].split()
363+
364+
# Assert that both lines have the same number of words
365+
if len(from_line) != len(to_line):
366+
diff = ["ERROR: Number of words in line " + str(i_line+1) + " differ."]
367+
passed = False
368+
break
369+
370+
# Loop through all words of one line
371+
for i_word in range(0, len(from_line)):
372+
373+
# Extract next word and strip whitespace and commas
374+
from_word = from_line[i_word].strip().strip(',')
375+
to_word = to_line[i_word].strip().strip(',')
376+
377+
# Assert that words are either both numeric or both non-numeric
378+
from_isfloat = is_float(from_word)
379+
to_isfloat = is_float(to_word)
380+
if from_isfloat != to_isfloat:
381+
diff = ["ERROR: File entries '" + from_word + "' and '" + to_word + "' in line " + str(i_line+1) + ", word " + str(i_word+1) + " differ."]
382+
passed = False
383+
delta = 0.0
384+
max_delta = "Not applicable"
385+
break
386+
387+
# Make actual comparison
388+
# Compare floats
389+
if from_isfloat:
390+
try:
391+
# Only do a relative comparison when the threshold is met.
392+
# This is to prevent large relative differences for very small numbers.
393+
if (abs(float(from_word)) > self.comp_threshold):
394+
delta = abs( (float(from_word) - float(to_word)) / float(from_word) ) * 100
395+
compare_counter += 1
396+
else:
397+
delta = 0.0
398+
ignore_counter += 1
399+
400+
max_delta = max(max_delta, delta)
401+
402+
except ZeroDivisionError:
403+
ignore_counter += 1
404+
continue
405+
406+
# Compare non-floats
407+
else:
408+
delta = 0.0
409+
compare_counter += 1
410+
if from_word != to_word:
411+
diff = ["ERROR: File entries '" + from_word + "' and '" + to_word + "' in line " + str(i_line+1) + ", word " + str(i_word+1) + " differ."]
412+
passed = False
413+
max_delta = "Not applicable"
414+
break
415+
416+
if delta > self.tol_file_percent:
417+
diff = ["ERROR: File entries '" + from_word + "' and '" + to_word + "' in line " + str(i_line+1) + ", word " + str(i_word+1) + " differ."]
418+
passed = False
419+
break
420+
329421
except OSError:
330422
print("OS error, most likely from missing reference file:", fromfile)
331423
print("Current working directory contents:")
@@ -335,10 +427,6 @@ def run_filediff(self):
335427
print("Current working directory contents:")
336428
print(os.listdir("."))
337429

338-
339-
340-
341-
342430
if (diff==[]):
343431
passed=True
344432
else:
@@ -349,8 +437,21 @@ def run_filediff(self):
349437
else:
350438
passed = False
351439

352-
print('CPU architecture=%s'%self.cpu_arch)
353-
print('test duration: %.2f min'%(running_time/60.0))
440+
# Report results
441+
diff_time_stop = datetime.datetime.now()
442+
diff_time = (diff_time_stop - diff_time_start).microseconds
443+
444+
print('CPU architecture: %s'%self.cpu_arch)
445+
print('Test duration: %.2f min'%(running_time/60.0))
446+
print('Diff duration: %.2f sec'%(diff_time/1e6))
447+
print('Specified tolerance: ' + str(self.tol_file_percent) + '%')
448+
print('Specified threshold: ' + str(self.comp_threshold))
449+
450+
if self.tol_file_percent != 0.0:
451+
print('Compared entries: ' + str(compare_counter))
452+
print('Ignored entries: ' + str(ignore_counter))
453+
print('Maximum difference: ' + str(max_delta) + '%')
454+
354455
print('==================== End Test: %s ====================\n'%self.tag)
355456

356457
sys.stdout.flush()
@@ -654,7 +755,7 @@ def run_def(self):
654755
except AttributeError: # popen.kill apparently fails on some versions of subprocess... the killall command should take care of things!
655756
pass
656757
timed_out = True
657-
passed = False
758+
passed = False
658759

659760
# Examine the output
660761
f = open(logfilename,'r')
@@ -688,7 +789,7 @@ def run_def(self):
688789
delta_vals.append( abs(float(data[j])-self.test_vals[j]) )
689790
if delta_vals[j] > self.tol:
690791
exceed_tol = True
691-
passed = False
792+
passed = False
692793
break
693794
else:
694795
iter_missing = True

0 commit comments

Comments
 (0)