Tue, 25 Aug 2020 22:31:16 +0300
added ability to get other header elements than just the first one
from testsuite import problem_type, report_problem import linetypes @problem_type('bad-colour', severity = 'hold', message = lambda colour_index, count: str.format( 'invalid colour {} used {} time(s)', colour_index, count, ), ) def colours_test(model): ''' Checks that all colours used in the part model are valid. ''' from collections import defaultdict bad_colours = defaultdict(lambda: {'count': 0, 'first-occurrence': None}) for element in model.body: if hasattr(element, 'colour') and not element.colour.is_valid: bad_colours[element.colour.index]['count'] += 1 if not bad_colours[element.colour.index]['first-occurrence']: bad_colours[element.colour.index]['first-occurrence'] = element yield from [ report_problem( 'bad-colour', bad_object = bad_colour['first-occurrence'], colour_index = colour_index, count = bad_colour['count'], ) for colour_index, bad_colour in bad_colours.items() ] @problem_type('syntax-error', severity = 'hold', message = lambda reason: str.format('syntax error: {}', reason), ) def syntax_errors(model): yield from ( report_problem('syntax-error', bad_object = element, reason = element.reason ) for element in model.body if isinstance(element, linetypes.Error) ) @problem_type('bad-header', severity = 'hold', message = lambda reason: str.format('bad header: {}', reason), ) @problem_type('no-license-set', severity = 'hold', message = 'no license set', ) @problem_type('non-ca-license', severity = 'hold', message = 'no new non-CA-submits are accepted', ) def bad_header(model): import header ca_license = 'Redistributable under CCAL version 2.0 : see CAreadme.txt' if isinstance(model.header, header.BadHeader): yield report_problem( 'bad-header', bad_object = model.body[model.header.index], reason = model.header.reason, ) elif not model.header.license: yield report_problem( 'no-license-set', bad_object = model.body[0], ) elif model.header.license != ca_license: yield report_problem( 'non-ca-license', bad_object = model.find_first_header_object('license'), ) @problem_type('bfc-nocertify', severity = 'hold', message = 'all new parts must be BFC certified', ) def nocertify_test(model): import header if model.header.valid and model.header.bfc == 'NOCERTIFY': yield report_problem( 'bfc-nocertify', bad_object = model.find_first_header_object('bfc'), ) @problem_type('physical-colour-part', severity = 'hold', message = 'no new physical colour parts are accepted', ) def physical_colours_test(model): if model.header.valid and 'Physical_Colour' in model.header.qualifiers: yield report_problem( 'physical-colour-part', bad_object = model.find_first_header_object('part type'), ) @problem_type('unofficial-part', severity = 'hold', message = 'new parts must be unofficial', ) def unofficiality_test(model): if model.header.valid and not model.header.filetype.startswith('Unofficial_'): yield report_problem( 'unofficial-part', bad_object = model.find_first_header_object('part type') ) @problem_type('primitive-ccw', severity = 'hold', message = 'primitives must have CCW winding', ) @problem_type('no-bfc-line', severity = 'hold', message = 'BFC declaration is missing', ) def header_bfc_test(model): if model.header.valid and not model.header.bfc: yield report_problem( 'no-bfc-line', bad_object = model.body[0], ) elif model.header.valid \ and model.header.filetype.endswith('Primitive') \ and model.header.bfc != 'CERTIFY CCW': yield report_problem( 'primitive-bfc-ccw', bad_object = model.find_first_header_object('bfc'), ) @problem_type('keywords-for-nonparts', severity = 'warning', message = lambda type: str.format( 'keywords are not allowed for {type} files', type = type, ), ) def keywords_tests(model): if model.header.valid: if model.header.keywords \ and model.header.effective_filetype != 'Part': yield report_problem( 'keywords-for-nonparts', bad_object = model.find_first_header_object('keywords'), type = model.header.effective_filetype, ) @problem_type('bad-colour-24-nonline', severity = 'hold', message = 'colour 24 used on non-lines', ) @problem_type('bad-colour-24-line', severity = 'hold', message = 'line with colour other than 24', ) def colour_24_test(model): for element in model.body: if hasattr(element, 'colour'): is_line = isinstance(element, linetypes.LineSegment) if not is_line and element.colour.index == 24: yield report_problem('bad-colour-24-nonline', bad_object = element) if is_line and element.colour.index != 24: yield report_problem('bad-colour-24-line', bad_object = element) @problem_type('moved-to-with-extension', severity = 'hold', message = 'moved-to files must not contain the ' '".dat"-extension in the description', ) def moved_to_with_extension_test(model): if model.header.valid \ and model.header.description.startswith('~Moved to') \ and '.dat' in model.header.description: yield report_problem( 'moved-to-with-extension', bad_object = model.body[0], ) @problem_type('bfc-invertnext-not-on-subfile', severity = 'hold', message = '"BFC INVERTNEXT" not followed by a type-1 line', ) def bfc_invertnext_not_on_subfile_test(model): def get_invertnexts(model): yield from [ (index, element) for index, element in enumerate(model.body) if isinstance(element, linetypes.MetaCommand) \ and element.text == 'BFC INVERTNEXT' ] def has_subfile_after_invertnext(index): index_subfile = index + 1 # subfile reference should be on the next line if index_subfile >= len(model.body): return False # past the end... else: element = model.body[index_subfile] return isinstance(element, linetypes.SubfileReference) for index, invertnext in get_invertnexts(model): if not has_subfile_after_invertnext(index): yield report_problem('bfc-invertnext-not-on-subfile', bad_object = model.body[index], ) @problem_type('unknown-metacommand', severity = 'hold', message = lambda command_text: str.format( 'unknown or deprecated metacommand: {command_text}', command_text = command_text, ) ) def metacommands_test(model): allowed_metacommand_patterns = [ r'^BFC (CLIP|NOCLIP|INVERTNEXT)$', r'^\!TEXMAP (START|NEXT) .+', r'^\!: .+', r'^\!TEXMAP (FALLBACK|END)$', ] import re for element in model.body[model.header_size:]: if isinstance(element, linetypes.MetaCommand): if element.text and not any( re.match(pattern, element.text) for pattern in allowed_metacommand_patterns ): yield report_problem('unknown-metacommand', bad_object = element, command_text = element.text, ) @problem_type('bad-line-endings', severity = 'hold', message = lambda count: str.format( 'file contains non-DOS line endings ({count} total)', count = count, ), ) def line_endings_test(model): # Line endings are already checked during parse. This function # only serves to report them. if model.line_ending_errors != None: yield report_problem( 'bad-line-endings', bad_object = model.body[model.line_ending_errors['first-at']], count = model.line_ending_errors['count'], ) manifest = { 'tests': [ colours_test, syntax_errors, bad_header, nocertify_test, physical_colours_test, unofficiality_test, header_bfc_test, keywords_tests, colour_24_test, moved_to_with_extension_test, bfc_invertnext_not_on_subfile_test, metacommands_test, line_endings_test, ], }