From 1304da2eaba9cf4377bd25e121dd37532bd74126 Mon Sep 17 00:00:00 2001 From: Ray Speth Date: Fri, 27 Jan 2012 19:31:43 +0000 Subject: [PATCH] Improved comparison of floating point values in test output Values are only compared up to the printed precision, so rounding errors will no longer result in failing tests. This also fixes differences in floating point output formats on different platforms. --- site_scons/buildutils.py | 64 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/site_scons/buildutils.py b/site_scons/buildutils.py index 4d5855884..5fad90259 100644 --- a/site_scons/buildutils.py +++ b/site_scons/buildutils.py @@ -168,15 +168,58 @@ def compareFiles(env, file1, file2): def compareTextFiles(env, file1, file2): """ - Compare the contents of two text files, ignoring trailing - whitespace and any lines starting with strings specified in the - variable env['test_ignoreLines']. + Compare the contents of two text files while: + - ignoring trailing whitespace + - ignoring any lines starting with strings specified in the + variable env['test_ignoreLines']. + - comparing floating point numbers only up to the printed precision """ text1 = [line.rstrip() for line in open(file1).readlines() if not line.startswith(tuple(env['test_ignoreLines']))] text2 = [line.rstrip() for line in open(file2).readlines() if not line.startswith(tuple(env['test_ignoreLines']))] + # Try to compare the files without testing the floating point numbers + diff = list(difflib.unified_diff(text1, text2)) + if not diff: + return 0 + + # Replace nearly-equal floating point numbers with exactly equivalent + # representations to avoid confusing difflib + reFloat = re.compile(r'(\s*)([+-]{0,1}\d+\.\d+[eE]{0,1}[+-]{0,1}\d*)') + for i in range(min(len(text1), len(text2))): + line1 = text1[i] + line2 = text2[i] + if line1 == line2: + continue + + # group(1) is the left space padding + # group(2) is the number + floats1 = [(m.group(1),m.group(2)) + for m in list(reFloat.finditer(line1))] + floats2 = [(m.group(1),m.group(2)) + for m in list(reFloat.finditer(line2))] + + # If the lines don't contain the same number of numbers, + # we're not going to pass the diff comparison no matter what + if len(floats1) != len(floats2): + continue + + for j in range(len(floats1)): + if floats1[j] == floats2[j]: + # String representations match, so replacement is unnecessary + continue + + delta = max(getPrecision(floats1[j][1]), getPrecision(floats2[j][1])) + num1 = float(floats1[j][1]) + num2 = float(floats2[j][1]) + if num1 - 1.1*delta < num2 < num1 + 1.1*delta: + # update the string with a matching string + line2 = line2.replace(''.join(floats2[j]), + ''.join(floats1[j])) + text2[i] = line2 + + # Try the comparison again diff = list(difflib.unified_diff(text1, text2)) if diff: 'Found differences between %s and %s:' % (file1, file2) @@ -188,6 +231,21 @@ def compareTextFiles(env, file1, file2): return 0 +def getPrecision(x): + """ + Return the number corresponding to the least significant digit of + the number represented by the string 'x'. + """ + x = x.lower() + if x.find('e') != -1: + precision = x.find('.') - x.find('e') + 1 + precision += int(x[x.find('e')+1:]) + else: + precision = x.find('.') - len(x) + 1 + + return 10**precision + + def compareCsvFiles(env, file1, file2): """ Compare the contents of two .csv file to see if they are