testsuite.py

changeset 62
f0a6bf48b05e
parent 47
4da025d0b283
child 63
8949af6a4279
equal deleted inserted replaced
61:15c95d3fcfd8 62:f0a6bf48b05e
1 from warnings import warn 1 from warnings import warn
2 2
3 def report_element(bad_object, type, error_name, args): 3 class ProblemType:
4 return { 4 severities = ['error', 'notice'] # in descending order
5 'type': type, 5 def __init__(self, name, severity, message):
6 'object': bad_object, 6 if severity not in ProblemType.severities:
7 'name': error_name, 7 raise ValueError(str.format(
8 'args': args, 8 'bad severity {severity!r}',
9 } 9 severity = severity,
10 ))
11 self.name = name
12 self.severity = severity
13 self.message = message
14 def __call__(self, bad_object, **args):
15 return Problem(
16 problem_class = self,
17 bad_object = bad_object,
18 **args,
19 )
10 20
11 def warning(bad_object, error_name, **args): 21 class Problem:
12 return report_element(bad_object, 'warning', error_name, args) 22 def __init__(self, problem_class, bad_object, **args):
23 self.problem_class = problem_class
24 self.severity = problem_class.severity
25 self.object = bad_object
26 self.args = args
27 def __str__(self):
28 if callable(self.problem_class.message):
29 return self.problem_class.message(**self.args)
30 else:
31 return self.problem_class.message
13 32
14 def error(bad_object, error_name, **args): 33 def problem_type(problem_name, **args):
15 return report_element(bad_object, 'error', error_name, args) 34 def wrapper(function):
35 if not hasattr(function, 'ldcheck_problem_types'):
36 function.ldcheck_problem_types = {}
37 new_type = ProblemType(name = problem_name, **args)
38 function.ldcheck_problem_types[problem_name] = new_type
39 return function
40 return wrapper
16 41
17 def notice(bad_object, error_name, **args): 42 def report_problem(problem_name, *, bad_object, **args):
18 return report_element(bad_object, 'notice', error_name, args) 43 return {'type': problem_name, 'bad-object': bad_object, 'args': args}
19 44
20 def name_of_package(package): 45 def name_of_package(package):
21 if isinstance(package, tuple): 46 if isinstance(package, tuple):
22 return package[1] 47 return package[1]
23 else: 48 else:
32 yield from sorted( 57 yield from sorted(
33 'tests.' + name_of_package(result) 58 'tests.' + name_of_package(result)
34 for result in walk_packages(tests.__path__) 59 for result in walk_packages(tests.__path__)
35 ) 60 )
36 61
37 def do_manifest_integrity_checks(test_suite, module):
38 '''
39 Runs integrity checks on a given module's manifest.
40 '''
41 def check_for_extra_keys():
42 extra_keys = module.manifest.keys() - test_suite.keys()
43 if extra_keys:
44 warn(str.format(
45 '{}: extra keys in manifest: {}',
46 module.__name__,
47 ', '.join(map(str, extra_keys))
48 ))
49 def check_for_manifest_duplicates():
50 for key in test_suite.keys():
51 duplicates = module.manifest[key].keys() & test_suite[key].keys()
52 if duplicates:
53 warn(str.format(
54 '{}: redefined {} in manifests: {}',
55 module.__name__,
56 key,
57 duplicates,
58 ))
59 check_for_extra_keys()
60 check_for_manifest_duplicates()
61
62 def load_tests(): 62 def load_tests():
63 ''' 63 '''
64 Imports test modules and combines their manifests into a test suite. 64 Imports test modules and combines their manifests into a test suite.
65 ''' 65 '''
66 test_suite = {'tests': {}, 'messages': {}} 66 test_suite = {'tests': []}
67 for module_name in test_discovery(): 67 for module_name in test_discovery():
68 from importlib import import_module 68 from importlib import import_module
69 module = import_module(module_name) 69 module = import_module(module_name)
70 if hasattr(module, 'manifest'): 70 if hasattr(module, 'manifest'):
71 do_manifest_integrity_checks(test_suite, module)
72 # Merge the data from the manifest 71 # Merge the data from the manifest
73 for key in module.manifest.keys() & test_suite.keys(): 72 test_suite['tests'] += module.manifest['tests']
74 test_suite[key].update(module.manifest[key])
75 else: 73 else:
76 warn(str.format('Module {} does not have a manifest', module_name)) 74 warn(str.format('Module {} does not have a manifest', module_name))
75 test_suite['tests'].sort(key = lambda f: f.__name__)
77 return test_suite 76 return test_suite
78 77
79 def problem_key(problem): 78 def problem_key(problem):
80 problem_hierarchy = ['error', 'warning', 'notice'] 79 rank = ProblemType.severities.index(problem.severity) # sort by severity
81 return (problem_hierarchy.index(problem['type']), problem['line-number']) 80 return (rank, problem.line_number)
81
82 def build_problem(test_function, problem_params):
83 problem_name = problem_params['type']
84 problem_type = test_function.ldcheck_problem_types[problem_name]
85 problem_object = problem_type(
86 bad_object = problem_params['bad-object'],
87 **problem_params['args'],
88 )
89 return problem_object
82 90
83 def check_model(model, test_suite = None): 91 def check_model(model, test_suite = None):
84 if not test_suite: 92 if not test_suite:
85 test_suite = load_tests() 93 test_suite = load_tests()
86 problems = [] 94 problems = []
87 line_numbers = { 95 line_numbers = {
88 element: (i, i + 1) 96 element: (i, i + 1)
89 for i, element in enumerate(model.body) 97 for i, element in enumerate(model.body)
90 } 98 }
91 for test_name, test_function in test_suite['tests'].items(): 99 for test_function in test_suite['tests']:
92 for problem in test_function(model): 100 for problem_params in test_function(model):
93 problem['body-index'], problem['line-number'] \ 101 problem = build_problem(test_function, problem_params)
94 = line_numbers[problem['object']] 102 # add line numbers to the problem
95 del problem['object'] 103 problem.body_index, problem.line_number \
104 = line_numbers[problem.object]
105 problem.object = None
96 problems.append(problem) 106 problems.append(problem)
97 return { 107 return {
98 'passed': not any( 108 'passed': not any(
99 problem['type'] == 'error' 109 problem.severity == 'error'
100 for problem in problems 110 for problem in problems
101 ), 111 ),
102 'problems': sorted(problems, key = problem_key), 112 'problems': sorted(problems, key = problem_key),
103 } 113 }
104 114
105 def problem_text(problem, test_suite): 115 def problem_text(problem, test_suite):
106 message = test_suite['messages'][problem['name']] 116 message = problem.problem_class.message
107 if callable(message): 117 if callable(message):
108 message = message(**problem['args']) 118 message = message(**problem.args)
109 return message 119 return message
110 120
111 def format_report_html(report, model, test_suite): 121 def format_report_html(report, model, test_suite):
112 messages = [] 122 messages = []
113 for problem in report['problems']: 123 for problem in report['problems']:
114 ldraw_code = model.body[problem['body-index']].textual_representation() 124 ldraw_code = model.body[problem.body_index].textual_representation()
115 message = str.format( 125 message = str.format(
116 '<li class="{problem_type}">{model_name}:{line_number}:' 126 '<li class="{problem_type}">{model_name}:{line_number}:'
117 '{problem_type}: {message}<br />{ldraw_code}</li>', 127 '{problem_type}: {message}<br />{ldraw_code}</li>',
118 model_name = model.name, 128 model_name = model.name,
119 line_number = problem['line-number'], 129 line_number = problem.line_number,
120 problem_type = problem['type'], 130 problem_type = problem.severity,
121 message = problem_text(problem, test_suite), 131 message = problem_text(problem, test_suite),
122 ldraw_code = ldraw_code, 132 ldraw_code = ldraw_code,
123 ) 133 )
124 messages.append(message) 134 messages.append(message)
125 return '\n'.join(messages) 135 return '\n'.join(messages)
127 def format_report(report, model, test_suite): 137 def format_report(report, model, test_suite):
128 import colorama 138 import colorama
129 colorama.init() 139 colorama.init()
130 messages = [] 140 messages = []
131 for problem in report['problems']: 141 for problem in report['problems']:
132 if problem['type'] == 'error': 142 if problem.severity == 'error':
133 text_colour = colorama.Fore.LIGHTRED_EX 143 text_colour = colorama.Fore.LIGHTRED_EX
134 elif problem['type'] == 'warning': 144 elif problem.severity == 'notice':
135 text_colour = colorama.Fore.LIGHTYELLOW_EX
136 elif problem['type'] == 'notice':
137 text_colour = colorama.Fore.LIGHTBLUE_EX 145 text_colour = colorama.Fore.LIGHTBLUE_EX
138 else: 146 else:
139 text_colour = '' 147 text_colour = ''
140 ldraw_code = model.body[problem['body-index']].textual_representation() 148 ldraw_code = model.body[problem.body_index].textual_representation()
141 message = str.format( 149 message = str.format(
142 '{text_colour}{model_name}:{line_number}: {problem_type}: {message}' 150 '{text_colour}{model_name}:{line_number}: {problem_type}: {message}'
143 '{colour_reset}\n\t{ldraw_code}', 151 '{colour_reset}\n\t{ldraw_code}',
144 text_colour = text_colour, 152 text_colour = text_colour,
145 model_name = model.name, 153 model_name = model.name,
146 line_number = problem['line-number'], 154 line_number = problem.line_number,
147 problem_type = problem['type'], 155 problem_type = problem.severity,
148 message = problem_text(problem, test_suite), 156 message = problem_text(problem, test_suite),
149 colour_reset = colorama.Fore.RESET, 157 colour_reset = colorama.Fore.RESET,
150 ldraw_code = ldraw_code, 158 ldraw_code = ldraw_code,
151 ) 159 )
152 messages.append(message) 160 messages.append(message)
153 return '\n'.join(messages) 161 return '\n'.join(messages)
154 162
163 def all_warning_types(test_suite):
164 for test_function in test_suite['tests']:
165 yield from test_function.ldcheck_problem_types.values()
166
155 if __name__ == '__main__': 167 if __name__ == '__main__':
156 from pprint import pprint 168 from pprint import pprint
157 pprint(load_tests()) 169 pprint(load_tests())

mercurial