def _ingest_directive(self, directive_class, active_string, location, parse_result): """ A callback that ingests a particular matched directive. Called by PyParsing when processing the grammar. :param directive_class: The matched directive class (not an instance) :type directive_class: type :param active_string: The contents of the fixture file :type active_string: union[str, unicode] :param location: The file location of the current parsing activity :param parse_result: The resulting PyParsing parsing object """ self._line_number = get_parse_line_number(location, active_string) source = get_parse_line(location, active_string) self._fixture_source.append(source) if self._working_test_case and not self._working_test_case_line_number: self._working_test_case_line_number = self._line_number - 1 test_case_target = self._working_test_case if parse_result.is_global: # This is actually a global directive, so we're working on those, not on a test case test_case_target = self._global_directives else: self._working_test_case_source.append(source) if parse_result.action: action_path = '{}.{}'.format(parse_result.action, parse_result.action_index or '0') if 'actions' not in test_case_target: test_case_target['actions'] = [] if action_path not in test_case_target: if not parse_result.is_global: # Global directives don't need a list of actions to run; only test cases do test_case_target['actions'].append(action_path) if self._working_action_case: # We're done parsing the previous action case and starting a new one for dc in get_all_directives(): dc().post_parse_test_case_action( self._working_action_case, test_case_target) test_case_target[action_path] = {} action_case_target = test_case_target[action_path] self._working_action_case = action_case_target else: # This branch handles non-action directives, such as comments, test names, test descriptions, non-action # time-freezing, non-action mocking, etc. action_case_target = test_case_target directive_class().ingest_from_parsed_test_fixture( action_case_target, test_case_target, parse_result, self._fixture_file_name, self._line_number, )
def _ingest_directives(self): # type: () -> None try: assert self._grammar is not None with codecs.open(self._fixture_file_name, mode='rb', encoding='utf-8') as file_input: self._grammar.parseFile(file_input) # explicit finalize on EOF to catch case of no blank line at end of file self._finalize_test_case('', 0, None) except ParseException as e: line_text = get_parse_line(e.loc, e.pstr) line_number = get_parse_line_number(e.loc, e.pstr) offset = get_parse_column(e.loc, e.pstr) raise FixtureSyntaxError( 'Failed to parse line: {line}\nin file: {file}:{line_number}\n{message}' .format( line=line_text, file=self._fixture_file_name, line_number=line_number, message=e.msg, ), file_name=self._fixture_file_name, line_number=line_number, offset=offset, line_text=line_text, ) except DataTypeConversionError as e: raise FixtureSyntaxError( 'Data type conversion error\nin file: {file}:{line_number}\n{message}' .format( file=self._fixture_file_name, line_number=self._line_number, message=e.args[0], ), file_name=self._fixture_file_name, line_number=self._line_number, ) except IOError as e: raise FixtureLoadError(str(e))
def _finalize_test_case(self, active_string, location, _): # type: (six.text_type, int, Optional[ParseResults]) -> None """ Called by PyParsing at the end of each test case. :param active_string: The contents of the fixture file :param location: The file location of the current parsing activity """ self._fixture_source.append('') if self._working_action_case: # We're done parsing the test case and still need to wrap up the last action in the test case for dc in get_all_directives(): dc().post_parse_test_case_action( self._working_action_case, self._working_test_case or self._global_directives, ) self._working_action_case = {} if not self._working_test_case: # just a blank line before any test cases, probably after globals or an extra blank line between tests return self._working_test_case['line_number'] = self._working_test_case_line_number self._working_test_case_line_number = 0 self._working_test_case['fixture_name'] = self._fixture_name self._working_test_case['fixture_file_name'] = self._fixture_file_name self._working_test_case['source'] = self._working_test_case_source line_number = get_parse_line_number(location, active_string) if not self._working_test_case.get('name'): raise FixtureSyntaxError( '{}:{}: Test case without name'.format(self._fixture_file_name, line_number), file_name=self._fixture_file_name, line_number=line_number - 1, ) if not self._working_test_case.get('description'): raise FixtureSyntaxError( '{}:{}: Test case without description'.format(self._fixture_file_name, line_number), file_name=self._fixture_file_name, line_number=line_number - 1, ) if not self._working_test_case.get('actions') and not self._global_directives: raise FixtureSyntaxError( '{}:{}: Empty test case'.format(self._fixture_file_name, line_number), file_name=self._fixture_file_name, line_number=line_number - 1, ) if self._global_directives: # merge, but make sure current overlays global where there is conflict test_case = {} # type: TestCase for path in get_all_paths(self._global_directives, allow_blank=True): try: value = path_get(self._global_directives, path) path_put(test_case, path, copy.copy(value)) except (KeyError, IndexError): raise FixtureSyntaxError( 'Invalid path: `{}`'.format(path), file_name=self._fixture_file_name, line_number=line_number, ) for path in get_all_paths(self._working_test_case, allow_blank=True): try: path_put(test_case, path, path_get(self._working_test_case, path)) except (KeyError, IndexError): raise FixtureSyntaxError( 'Invalid path: `{}`'.format(path), file_name=self._fixture_file_name, line_number=line_number, ) for directive_class in get_all_directives(): directive_class().post_parse_test_case(test_case) else: for directive_class in get_all_directives(): directive_class().post_parse_test_case(self._working_test_case) test_case = copy.deepcopy(self._working_test_case) test_case['fixture_source'] = self._fixture_source self.test_cases.append(test_case) self._working_test_case.clear() self._working_test_case_source = []