Mon, 22 Jan 2018 21:21:53 +0200
Added command line option to list all checks.
16 | 1 | from warnings import warn |
2 | ||
3 | def report_element(bad_object, type, error_name, args): | |
12 | 4 | return { |
16 | 5 | 'type': type, |
6 | 'object': bad_object, | |
7 | 'name': error_name, | |
8 | 'args': args, | |
13 | 9 | } |
10 | ||
17 | 11 | def warning(bad_object, error_name, **args): |
16 | 12 | return report_element(bad_object, 'warning', error_name, args) |
13 | ||
17 | 14 | def error(bad_object, error_name, **args): |
16 | 15 | return report_element(bad_object, 'error', error_name, args) |
16 | ||
17 | def test_discovery(): | |
18 | ''' | |
19 | Finds all test modules and yields their names. | |
20 | ''' | |
21 | from pkgutil import walk_packages | |
22 | import tests | |
23 | yield from sorted( | |
24 | 'tests.' + result.name | |
25 | for result in walk_packages(tests.__path__) | |
26 | ) | |
27 | ||
28 | def do_manifest_integrity_checks(test_suite, module): | |
29 | ''' | |
30 | Runs integrity checks on a given module's manifest. | |
31 | ''' | |
32 | def check_for_extra_keys(): | |
33 | extra_keys = module.manifest.keys() - test_suite.keys() | |
34 | if extra_keys: | |
35 | warn(str.format( | |
36 | '{}: extra keys in manifest: {}', | |
37 | module.__name__, | |
38 | ', '.join(map(str, extra_keys)) | |
39 | )) | |
40 | def check_for_manifest_duplicates(): | |
41 | for key in test_suite.keys(): | |
42 | duplicates = module.manifest[key].keys() & test_suite[key].keys() | |
43 | if duplicates: | |
44 | warn(str.format( | |
45 | '{}: redefined {} in manifests: {}', | |
46 | module.__name__, | |
47 | key, | |
48 | duplicates, | |
49 | )) | |
50 | check_for_extra_keys() | |
51 | check_for_manifest_duplicates() | |
52 | ||
53 | def load_tests(): | |
54 | ''' | |
55 | Imports test modules and combines their manifests into a test suite. | |
56 | ''' | |
57 | test_suite = {'tests': {}, 'messages': {}} | |
58 | for module_name in test_discovery(): | |
59 | from importlib import import_module | |
60 | module = import_module(module_name) | |
61 | if hasattr(module, 'manifest'): | |
62 | do_manifest_integrity_checks(test_suite, module) | |
63 | # Merge the data from the manifest | |
64 | for key in module.manifest.keys() & test_suite.keys(): | |
65 | test_suite[key].update(module.manifest[key]) | |
66 | else: | |
67 | warn(str.format('Module {} does not have a manifest', module_name)) | |
68 | return test_suite | |
69 | ||
17 | 70 | def check_model(model, test_suite = None): |
71 | if not test_suite: | |
72 | test_suite = load_tests() | |
73 | report = [] | |
74 | line_numbers = { | |
75 | element: (i, i + 1 + model.body_offset) | |
76 | for i, element in enumerate(model.body) | |
77 | } | |
78 | for test_name, test_function in test_suite['tests'].items(): | |
79 | problems = test_function(model) | |
80 | for problem in problems: | |
81 | problem['body-index'], problem['line-number'] \ | |
82 | = line_numbers[problem['object']] | |
83 | del problem['object'] | |
84 | report.append(problem) | |
85 | return report | |
86 | ||
87 | def format_report(report, model, test_suite): | |
18 | 88 | import colorama |
89 | colorama.init() | |
17 | 90 | result = [] |
91 | for problem in report: | |
92 | problem_text = test_suite['messages'][problem['name']] | |
93 | if callable(problem_text): | |
94 | problem_text = problem_text(**problem['args']) | |
18 | 95 | if problem['type'] == 'error': |
96 | text_colour = colorama.Fore.LIGHTRED_EX | |
97 | elif problem['type'] == 'warning': | |
98 | text_colour = colorama.Fore.LIGHTYELLOW_EX | |
99 | else: | |
100 | text_colour = '' | |
19 | 101 | ldraw_code = model.body[problem['body-index']].textual_representation() |
17 | 102 | message = str.format( |
18 | 103 | '{text_colour}{model_name}:{line_number}: {problem_type}: {message}' |
19 | 104 | '{colour_reset}\n\t{ldraw_code}', |
18 | 105 | text_colour = text_colour, |
106 | model_name = model.name, | |
107 | line_number = problem['line-number'], | |
108 | problem_type = problem['type'], | |
109 | message = problem_text, | |
110 | colour_reset = colorama.Fore.RESET, | |
19 | 111 | ldraw_code = ldraw_code, |
17 | 112 | ) |
113 | result.append((problem['line-number'], message)) | |
114 | return '\n'.join( | |
115 | problem[1] | |
116 | for problem in sorted(result) | |
117 | ) | |
118 | ||
16 | 119 | if __name__ == '__main__': |
120 | from pprint import pprint | |
121 | pprint(load_tests()) |