414 lines
13 KiB
Python
Executable File
414 lines
13 KiB
Python
Executable File
#!/usr/bin/python
|
|
##
|
|
# CU - C unit testing framework
|
|
# ---------------------------------
|
|
# Copyright (c)2007,2008 Daniel Fiser <danfis@danfis.cz>
|
|
#
|
|
#
|
|
# This file is part of CU.
|
|
#
|
|
# CU is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU Lesser General Public License as
|
|
# published by the Free Software Foundation; either version 3 of
|
|
# the License, or (at your option) any later version.
|
|
#
|
|
# CU is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Lesser General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Lesser General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
from subprocess import Popen, PIPE
|
|
import os
|
|
import re
|
|
import sys
|
|
import math
|
|
from getopt import gnu_getopt, GetoptError
|
|
|
|
EPS = 0.6
|
|
BASE_DIR = "."
|
|
MAX_DIFF_LINES = 20
|
|
EXACT = False
|
|
|
|
PROGRESS_ON = True
|
|
MSG_BASE = ""
|
|
|
|
class Hunk:
|
|
""" This class represents one hunk from diff. """
|
|
|
|
def __init__(self):
|
|
self.added = []
|
|
self.deleted = []
|
|
self.lines = []
|
|
|
|
# to identify lines with floating point numbers
|
|
self.re_is_num = re.compile("^.*[0-9].*$")
|
|
|
|
# pattern to match floating point number
|
|
self.num_pattern = r"-?(?:(?:[0-9]+(?:\.[0-9]*)?)|(?:\.[0-9]+))(?:[eE]-?[0-9]+)?"
|
|
self.re_num = re.compile(self.num_pattern)
|
|
|
|
def numLines(self):
|
|
return len(self.lines)
|
|
def numLinesAdded(self):
|
|
return len(self.added)
|
|
def numLinesDeleted(self):
|
|
return len(self.deleted)
|
|
|
|
def addLineAdded(self, line):
|
|
self.added.append(line)
|
|
def addLineDeleted(self, line):
|
|
self.deleted.append(line)
|
|
def addLine(self, line):
|
|
self.lines.append(line)
|
|
|
|
def getLines(self):
|
|
return self.lines
|
|
def getLinesAdded(self):
|
|
return self.added
|
|
def getLinesDeleted(self):
|
|
return self.deleted
|
|
|
|
def __eq(self, num1, num2):
|
|
""" Returns True if num1 equals to num2 with respect to EPS
|
|
(defined above) """
|
|
return math.fabs(num1 - num2) < EPS
|
|
|
|
def checkFloats(self):
|
|
""" This method try to check if only difference between added and
|
|
deleted lines of this hunk is different precission of floating
|
|
point numbers
|
|
"""
|
|
|
|
# If number of added and deleted lines differs, then there is more
|
|
# differences that precission of floating point numbers
|
|
if self.numLinesAdded() != self.numLinesDeleted():
|
|
return False
|
|
|
|
for i in xrange(0, self.numLinesAdded()):
|
|
# if any line does not contain number - return False because
|
|
# there must be more differences than in numbers
|
|
if not self.re_is_num.match(self.added[i]) \
|
|
or not self.re_is_num.match(self.deleted[i]):
|
|
return False
|
|
|
|
line1 = self.added[i]
|
|
line2 = self.deleted[i]
|
|
|
|
# Extract all floating point numbers from each line
|
|
nums1 = self.re_num.findall(line1)
|
|
nums2 = self.re_num.findall(line2)
|
|
# and remove all empty strings
|
|
nums1 = filter(lambda x: len(x) > 0, nums1)
|
|
nums2 = filter(lambda x: len(x) > 0, nums2)
|
|
|
|
# if length of list nums1 does not equal to length of nums2
|
|
# return False
|
|
if len(nums1) != len(nums2):
|
|
return False
|
|
|
|
# iterate trough all numbers
|
|
for j in xrange(0, len(nums1)):
|
|
# if numbers do not equal to each other return False
|
|
if not self.__eq(float(nums1[j]), float(nums2[j])):
|
|
return False
|
|
|
|
# compare the rest of lines
|
|
line1 = self.re_num.sub("", line1)
|
|
line2 = self.re_num.sub("", line2)
|
|
if line1 != line2:
|
|
return False
|
|
|
|
# If it does not fail anywhere, added and deleted lines must be
|
|
# same
|
|
return True
|
|
|
|
|
|
class Diff:
|
|
""" Represents whole diff. """
|
|
|
|
def __init__(self):
|
|
self.hunks = []
|
|
self.lines = 0
|
|
self.omitted_lines = 0
|
|
|
|
def addHunk(self, hunk):
|
|
self.hunks.append(hunk)
|
|
self.lines += hunk.numLines()
|
|
|
|
def numLines(self):
|
|
return self.lines
|
|
def numOmittedLines(self):
|
|
return self.omitted_lines
|
|
|
|
def getHunks(self):
|
|
return self.hunks
|
|
def numHunks(self):
|
|
return len(self.hunks)
|
|
|
|
def checkFloats(self):
|
|
""" Will call method checkFloats on each hunk """
|
|
hks = self.hunks[:]
|
|
self.hunks = []
|
|
self.lines = 0
|
|
for h in hks:
|
|
if not h.checkFloats():
|
|
self.hunks.append(h)
|
|
self.lines += h.numLines()
|
|
else:
|
|
self.omitted_lines += h.numLines()
|
|
|
|
|
|
|
|
class Parser:
|
|
def __init__(self, fin):
|
|
self.fin = fin
|
|
self.line = ""
|
|
self.diff = Diff()
|
|
self.cur_hunk = None
|
|
|
|
# to recognize beginning of hunk:
|
|
self.re_hunk = re.compile(r"^[0-9]*(,[0-9]*){0,1}[a-zA-Z]?[0-9]*(,[0-9]*){0,1}$")
|
|
|
|
self.re_added = re.compile(r"^> (.*)$")
|
|
self.re_deleted = re.compile(r"^< (.*)$")
|
|
|
|
def __readNextLine(self):
|
|
self.line = self.fin.readline()
|
|
if len(self.line) == 0:
|
|
return False
|
|
return True
|
|
|
|
def parse(self):
|
|
global PROGRESS_ON
|
|
global MSG_BASE
|
|
|
|
num_lines = 0
|
|
while self.__readNextLine():
|
|
# beggining of hunk
|
|
if self.re_hunk.match(self.line):
|
|
if self.cur_hunk is not None:
|
|
self.diff.addHunk(self.cur_hunk)
|
|
self.cur_hunk = Hunk()
|
|
self.cur_hunk.addLine(self.line)
|
|
|
|
# line added
|
|
match = self.re_added.match(self.line)
|
|
if match is not None:
|
|
self.cur_hunk.addLine(self.line)
|
|
self.cur_hunk.addLineAdded(match.group(1))
|
|
|
|
# line deleted
|
|
match = self.re_deleted.match(self.line)
|
|
if match is not None:
|
|
self.cur_hunk.addLine(self.line)
|
|
self.cur_hunk.addLineDeleted(match.group(1))
|
|
|
|
num_lines += 1
|
|
|
|
if PROGRESS_ON and num_lines % 50 == 0:
|
|
print MSG_BASE, "[ %08d ]" % num_lines, "\r",
|
|
sys.stdout.flush()
|
|
|
|
# last push to list of hunks
|
|
if self.cur_hunk is not None:
|
|
self.diff.addHunk(self.cur_hunk)
|
|
|
|
if PROGRESS_ON:
|
|
print MSG_BASE, " ", "\r",
|
|
sys.stdout.flush()
|
|
|
|
def getDiff(self):
|
|
return self.diff
|
|
|
|
|
|
def regressionFilesInDir():
|
|
""" Returns sorted list of pairs of filenames where first name in pair
|
|
is tmp. file and second corresponding file with saved regressions.
|
|
"""
|
|
|
|
re_tmp_out_file = re.compile(r"tmp\.(.*\.out)")
|
|
re_tmp_err_file = re.compile(r"tmp\.(.*\.err)")
|
|
files = []
|
|
|
|
all_files = os.listdir(".")
|
|
all_files.sort()
|
|
for file in all_files:
|
|
res = re_tmp_out_file.match(file)
|
|
if res is not None:
|
|
fname = res.group(1)
|
|
tmp = [file, ""]
|
|
for file2 in all_files:
|
|
if file2 == fname:
|
|
tmp = [file, file2,]
|
|
break
|
|
files.append(tmp)
|
|
|
|
res = re_tmp_err_file.match(file)
|
|
if res is not None:
|
|
fname = res.group(1)
|
|
tmp = [file, ""]
|
|
for file2 in all_files:
|
|
if file2 == fname:
|
|
tmp = [file, file2,]
|
|
break
|
|
files.append(tmp)
|
|
|
|
return files
|
|
|
|
|
|
def MSG(str = "", wait = False):
|
|
if wait:
|
|
print str,
|
|
else:
|
|
print str
|
|
def MSGOK(prestr = "", str = "", poststr = ""):
|
|
print prestr, "\033[0;32m" + str + "\033[0;0m", poststr
|
|
def MSGFAIL(prestr = "", str = "", poststr = ""):
|
|
print prestr, "\033[0;31m" + str + "\033[0;0m", poststr
|
|
def MSGINFO(prestr = "", str = "", poststr = ""):
|
|
print prestr, "\033[0;33m" + str + "\033[0;0m", poststr
|
|
def dumpLines(lines, prefix = "", wait = False, max_lines = -1):
|
|
line_num = 0
|
|
if wait:
|
|
for line in lines:
|
|
print prefix, line,
|
|
line_num += 1
|
|
if max_lines >= 0 and line_num > max_lines:
|
|
break
|
|
else:
|
|
for line in lines:
|
|
print prefix, line
|
|
line_num += 1
|
|
if max_lines >= 0 and line_num > max_lines:
|
|
break
|
|
|
|
def main(files):
|
|
global MSG_BASE
|
|
|
|
# As first compute length of columns
|
|
len1 = 0
|
|
len2 = 0
|
|
for filenames in files:
|
|
if len(filenames[0]) > len1:
|
|
len1 = len(filenames[0])
|
|
if len(filenames[1]) > len2:
|
|
len2 = len(filenames[1])
|
|
|
|
for filenames in files:
|
|
if len(filenames[1]) == 0:
|
|
MSGFAIL("", "===", "Can't compare %s %s, bacause %s does not exist!" % \
|
|
(filenames[0], filenames[0][4:], filenames[0][4:]))
|
|
continue
|
|
|
|
cmd = ["diff", filenames[0], filenames[1]]
|
|
MSG_BASE = "Comparing %s and %s" % \
|
|
(filenames[0].ljust(len1) ,filenames[1].ljust(len2))
|
|
if not PROGRESS_ON:
|
|
print MSG_BASE,
|
|
sys.stdout.flush()
|
|
|
|
pipe = Popen(cmd, stdout=PIPE)
|
|
parser = Parser(pipe.stdout)
|
|
parser.parse()
|
|
diff = parser.getDiff()
|
|
if not EXACT:
|
|
diff.checkFloats()
|
|
|
|
if PROGRESS_ON:
|
|
print MSG_BASE,
|
|
|
|
if diff.numHunks() == 0:
|
|
MSGOK(" [", "OK", "]")
|
|
if diff.numOmittedLines() > 0:
|
|
MSGINFO(" -->", str(diff.numOmittedLines()) + " lines from diff omitted")
|
|
else:
|
|
MSGFAIL(" [", "FAILED", "]")
|
|
if diff.numOmittedLines() > 0:
|
|
MSGINFO(" -->", str(diff.numOmittedLines()) + " lines from diff omitted")
|
|
MSGINFO(" -->", "Diff has " + str(diff.numLines()) + " lines")
|
|
|
|
if diff.numLines() <= MAX_DIFF_LINES:
|
|
MSGINFO(" -->", "Diff:")
|
|
for h in diff.getHunks():
|
|
dumpLines(h.getLines(), " |", True)
|
|
else:
|
|
MSGINFO(" -->", "Printing only first " + str(MAX_DIFF_LINES) + " lines:")
|
|
lines = []
|
|
for h in diff.getHunks():
|
|
lines += h.getLines()
|
|
if len(lines) > MAX_DIFF_LINES:
|
|
break;
|
|
dumpLines(lines, " |", True, MAX_DIFF_LINES)
|
|
|
|
def usage():
|
|
print "Usage: " + sys.argv[0] + " [ OPTIONS ] [ directory, [ directory, [ ... ] ] ]"
|
|
print ""
|
|
print " OPTIONS:"
|
|
print " --help / -h none Print this help"
|
|
print " --exact / -e none Switch do exact comparasion of files"
|
|
print " --not-exact / -n none Switch do non exact comparasion of files (default behaviour)"
|
|
print " --max-diff-lines int Maximum of lines of diff which can be printed (default " + str(MAX_DIFF_LINES) + ")"
|
|
print " --eps float Precision of floating point numbers (epsilon) (default " + str(EPS) + ")"
|
|
print " --no-progress none Turn off progress bar"
|
|
print " --progress none Turn on progress bar (default)"
|
|
print ""
|
|
print " This program is able to compare files with regressions generated by CU testsuites."
|
|
print " You can specify directories which are to be searched for regression files."
|
|
print " In non exact copmarasion mode (which is default), this program tries to compare"
|
|
print " floating point numbers in files with respect to specified precision (see --eps) and"
|
|
print " those lines which differ only in precission of floating point numbers are omitted."
|
|
print ""
|
|
sys.exit(-1)
|
|
|
|
|
|
|
|
# Init:
|
|
|
|
# Set up base dir
|
|
BASE_DIR = os.getcwd()
|
|
|
|
# Parse command line options:
|
|
optlist, args = gnu_getopt(sys.argv[1:],
|
|
"hen",
|
|
["help", "max-diff-lines=", "eps=", \
|
|
"exact", "not-exact", \
|
|
"no-progress", "progress"])
|
|
for opt in optlist:
|
|
if opt[0] == "--help" or opt[0] == "-h":
|
|
usage()
|
|
if opt[0] == "--exact" or opt[0] == "-e":
|
|
EXACT = True
|
|
if opt[0] == "--not-exact" or opt[0] == "-n":
|
|
EXACT = False
|
|
if opt[0] == "--max-diff-lines":
|
|
MAX_DIFF_LINES = int(opt[1])
|
|
if opt[0] == "--eps":
|
|
EPS = float(opt[1])
|
|
if opt[0] == "--no-progress":
|
|
PROGRESS_ON = False
|
|
if opt[0] == "--progress":
|
|
PROGRESS_ON = True
|
|
|
|
if len(args) == 0:
|
|
files = regressionFilesInDir()
|
|
main(files)
|
|
else:
|
|
for dir in args:
|
|
os.chdir(BASE_DIR)
|
|
|
|
MSGINFO()
|
|
MSGINFO("", "Processing directory '" + dir + "':")
|
|
MSGINFO()
|
|
try:
|
|
os.chdir(dir)
|
|
except:
|
|
MSGFAIL(" -->", "Directory '" + dir + "' does not exist.")
|
|
files = regressionFilesInDir()
|
|
main(files)
|
|
|
|
sys.exit(0)
|