from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import os.path
import re
import sys
STATUS_ERR = 2
STATUS_WARN = 1
STATUS_OK = 0
class ProblemVault(object):
def __init__(self, exception_fname=None):
self.exceptions = {}
self.exception_list = []
self.used_exception_for = {}
if exception_fname == None:
return
try:
with open(exception_fname, 'r') as exception_f:
self.register_exceptions(exception_f)
except IOError:
print("No exception file provided", file=sys.stderr)
def register_exceptions(self, exception_file):
for lineno, line in enumerate(exception_file, 1):
try:
problem = get_old_problem_from_exception_str(line)
except ValueError as v:
print("Exception file line {} not recognized: {}"
.format(lineno,v),
file=sys.stderr)
continue
if problem is None:
continue
if problem.key() in self.exceptions:
print("Duplicate exceptions lines found in exception file:\n\t{}\n\t{}\nAborting...".format(problem, self.exceptions[problem.key()]),
file=sys.stderr)
sys.exit(1)
self.exceptions[problem.key()] = problem
self.exception_list.append(problem)
def register_problem(self, problem):
if problem.key() not in self.exceptions:
return STATUS_ERR
status = problem.is_worse_than(self.exceptions[problem.key()])
self.used_exception_for[problem.key()] = problem
return status
def list_overbroad_exceptions(self):
for k in self.exceptions:
e = self.exceptions[k]
p = self.used_exception_for.get(k)
if p is None or e.is_worse_than(p):
yield (e, p)
def list_exceptions_without_overbroad(self):
for e in self.exception_list:
p = self.used_exception_for.get(e.key())
if p is None:
continue
if e.is_worse_than(p):
yield p
else:
yield e
def set_tolerances(self, fns):
for k in self.exceptions:
ex = self.exceptions[k]
fn = fns.get(ex.problem_type)
if fn is not None:
ex.metric_value = fn(ex.metric_value)
class ProblemFilter(object):
def __init__(self):
self.thresholds = dict()
def addThreshold(self, item):
self.thresholds[(item.get_type(),item.get_file_type())] = item
def matches(self, item):
key = (item.get_type(), item.get_file_type())
filt = self.thresholds.get(key, None)
if filt is None:
return False
return item.is_worse_than(filt)
def filter(self, sequence):
for item in iter(sequence):
if self.matches(item):
yield item
class Item(object):
def __init__(self, problem_type, problem_location, metric_value):
self.problem_location = problem_location
self.metric_value = int(metric_value)
self.warning_threshold = self.metric_value
self.problem_type = problem_type
def is_worse_than(self, other_problem):
if self.metric_value > other_problem.metric_value:
return STATUS_ERR
elif self.metric_value > other_problem.warning_threshold:
return STATUS_WARN
else:
return STATUS_OK
def key(self):
canonical_location = os.path.normcase(self.problem_location)
return "%s:%s" % (canonical_location, self.problem_type)
def __str__(self):
return "problem %s %s %s" % (self.problem_type, self.problem_location, self.metric_value)
def get_type(self):
return self.problem_type
def get_file_type(self):
if self.problem_location.endswith(".h"):
return "*.h"
else:
return "*.c"
class FileSizeItem(Item):
def __init__(self, problem_location, metric_value):
super(FileSizeItem, self).__init__("file-size", problem_location, metric_value)
class IncludeCountItem(Item):
def __init__(self, problem_location, metric_value):
super(IncludeCountItem, self).__init__("include-count", problem_location, metric_value)
class FunctionSizeItem(Item):
def __init__(self, problem_location, metric_value):
super(FunctionSizeItem, self).__init__("function-size", problem_location, metric_value)
class DependencyViolationItem(Item):
def __init__(self, problem_location, metric_value):
super(DependencyViolationItem, self).__init__("dependency-violation",
problem_location,
metric_value)
comment_re = re.compile(r'#.*$')
def get_old_problem_from_exception_str(exception_str):
orig_str = exception_str
exception_str = comment_re.sub("", exception_str)
fields = exception_str.split()
if len(fields) == 0:
return None
elif len(fields) == 4:
_, problem_type, problem_location, metric_value = fields
else:
raise ValueError("Misformatted line {!r}".format(orig_str))
if problem_type == "file-size":
return FileSizeItem(problem_location, metric_value)
elif problem_type == "include-count":
return IncludeCountItem(problem_location, metric_value)
elif problem_type == "function-size":
return FunctionSizeItem(problem_location, metric_value)
elif problem_type == "dependency-violation":
return DependencyViolationItem(problem_location, metric_value)
else:
raise ValueError("Unknown exception type {!r}".format(orig_str))