Fri, 18 Sep 2020 21:53:08 +0300
added category unit tests
#!/usr/bin/env python3 from ldcheck import appname, version, version_string from ldcheck import load_config, find_ldconfig_ldr_paths from ldcheck import script_directory from pathlib import Path from parse import read_ldraw from testsuite import load_tests, check_model, problem_text, all_problem_type_names def unit_test_discovery(): ''' Yields unit test paths ''' import os unit_test_dir = Path(script_directory) / 'unittests' for dirpath, dirnames, filenames in os.walk(unit_test_dir): yield from sorted( Path(dirpath) / filename for filename in filenames if filename.endswith('.test') ) def parse_expectation(text): problem_type, line_no_str = str.split(text, ':') return problem_type, int(line_no_str) def load_unit_test(unit_test_path, *, name, ldraw_directories): with open(unit_test_path, 'rb') as device: import re problem_types = set() expecting = None while True: pos = device.tell() line = bytes.decode(device.readline()) if not line: raise ValueError('unit test ended unexpectedly') match = re.match('^0 Testing: (.+)', line) if match: set.update(problem_types, match.group(1).split()) elif str.strip(line) == '0 Expecting: none': expecting = set() else: match = re.match('^0 Expecting: (.+)', line) if match: if not expecting: expecting = set() set.update(expecting, map(parse_expectation, match.group(1).split())) else: device.seek(pos) break if not problem_types or expecting is None: raise ValueError(str.format( 'Unit test {name} does not have a proper manifest', name = name, )) return { 'problem_types': problem_types, 'expecting': expecting, 'model': read_ldraw( device, name = name, ldraw_directories = ldraw_directories ), } def parse_problem(problem): return problem.problem_class.name, problem.line_number def run_unit_test(unit_test_path, *, config, test_suite): from os.path import basename unit_test = load_unit_test( unit_test_path, name = basename(unit_test_path), ldraw_directories = config['libraries'], ) bad_problems = set.difference( unit_test['problem_types'], all_problem_type_names(test_suite) ) if bad_problems: raise ValueError(str.format( 'unknown problem type: {names}', names = ' '.join(sorted(bad_problems)) )) problem_types = unit_test['problem_types'] report = check_model(unit_test['model'], test_suite) expected_problems = unit_test['expecting'] problems = set( filter( lambda problem: problem[0] in problem_types, map( parse_problem, report['problems'] ) ) ) return { 'passed': problems == expected_problems, 'unexpected': set.difference(problems, expected_problems), 'missing': set.difference(expected_problems, problems), 'problem_types': problem_types, } def format_problem_tuple(problem_tuple): return problem_tuple[0] + ':' + str(problem_tuple[1]) def run_unit_test_suite(): from argparse import ArgumentParser parser = ArgumentParser() parser.add_argument('-d', '--debug', action = 'store_true') args = parser.parse_args() config = load_config() test_suite = load_tests() num_tested = 0 num_passed = 0 all_problem_types = all_problem_type_names(test_suite) problem_types_tested = set() print('Running unit test suite.') for unit_test_path in unit_test_discovery(): try: unit_test_report = run_unit_test( unit_test_path, config = config, test_suite = test_suite ) except Exception as error: if args.debug: raise else: print(str.format( 'Error running {test_name}: {error}', test_name = unit_test_path.name, error = str(error), )) else: print(str.format( '{name}: {verdict}', name = unit_test_path.name, verdict = ('FAILED', 'PASSED')[unit_test_report['passed']], )) num_tested += 1 num_passed += int(unit_test_report['passed']) set.update(problem_types_tested, unit_test_report['problem_types']) if not unit_test_report['passed']: def format_problem_list(key): return str.join( ' ', map(format_problem_tuple, unit_test_report[key]) ) print('\tunexpected:', format_problem_list('unexpected')) print('\tmissing:', format_problem_list('missing')) print(str.format( '{num_tested} tests run, {num_passed} tests passed.', num_tested = num_tested, num_passed = num_passed, )) untested_problem_types = set.difference(all_problem_types, problem_types_tested) if untested_problem_types: print('The following problem types were not tested:') for problem_type in sorted(untested_problem_types): print('\t' + problem_type) if __name__ == '__main__': run_unit_test_suite()