# SPDX-License-Identifier: LGPL-2.1-only # Copyright 2022 Jookia <contact@jookia.org> # Directive syntax consists of one of the following: # - A set # - A conditional # - A statement, terminated by "Done" # # Parsing gives one of the following: # Set - The parsed set node # Conditional - The parsed conditional node # Statement - The parsed statement node # # The following error contexts are used: # PARSE_DIRECTIVE - Used when parsing the directive # # The following parse errors are generated: # NO_TOKEN - When there's not enough tokens # # The following parsers are used and have their errors # and data structures propagated: # parse_statement - Used with "Done" terminator import enum from hypothesis import given from hypothesis.strategies import composite, just, one_of from src.parse import ( ParseContext, ParseError, ParseErrorException, ParseTask, Parser, ) from tests.parse.templates import ( template_test_valid, template_test_invalid, ) from tests.test_token import ( draw_token_unknown, static_token_by_value, ) from tests.parse.test_parse import static_parse_context # # Helper functions # # Values used by the mocked parser class MockDirective(enum.Enum): MockSet = enum.auto() MockConditional = enum.auto() MockStatement = enum.auto() # Mocks and tests the parse_directive parser # Instead of parsing sets, conditionals and statements # it instead returns a mock value class MockParserValid(Parser): def parse_set(self, stream, parent_context): stream.pop() return MockDirective.MockSet def parse_conditional(self, stream, parent_context): stream.pop() return MockDirective.MockConditional def parse_statement(self, stream, parent_context, terminator): assert terminator == "Done" stream.pop() return MockDirective.MockStatement # Mocks and tests the parse_directive parser error handling # Instead of parsing, just return an error # Re-use the enum elements to give a unique error for each node class MockParserInvalid(Parser): def _raise_error(self, error, parent_context): raise ParseErrorException(error, None, None, parent_context) def parse_set(self, stream, parent_context): self._raise_error(MockDirective.MockSet, parent_context) def parse_conditional(self, stream, parent_context): self._raise_error(MockDirective.MockConditional, parent_context) def parse_statement(self, stream, parent_context, terminator): assert terminator == "Done" self._raise_error(MockDirective.MockStatement, parent_context) # A valid directive containing a set def static_directive_set(): return ([static_token_by_value("Set")], MockDirective.MockSet) # A valid directive containing a conditional def static_directive_conditional(): return ([static_token_by_value("If")], MockDirective.MockConditional) # Draws a valid directive containing a statement @composite def draw_directive_statement(draw): return ([draw(draw_token_unknown())], MockDirective.MockStatement) # Draws a valid directive @composite def draw_directive_valid(draw): return draw( one_of( [ just(static_directive_set()), just(static_directive_conditional()), draw_directive_statement(), ] ) ) # # Test functions # # Tests parsing a valid directive # We expect the following behaviour: # - Sets are detected and parsed # - Conditionals are detected and parsed # - Statements are detected and parsed @given(draw_directive_valid()) def test_parse_directive_valid(test_data): (tokens, expected) = test_data parser = MockParserValid().parse_directive return template_test_valid(parser, tokens, expected) # Tests parsing an empty directive # We expect the following behaviour: # - A NO_TOKEN parse error is raised # - The error context is PARSE_DIRECTIVE def test_parse_directive_empty(): tokens = [] parent_context = static_parse_context() context = ParseContext(ParseTask.PARSE_DIRECTIVE, None, parent_context) error = ParseErrorException(ParseError.NO_TOKEN, None, None, context) parser = MockParserValid().parse_directive template_test_invalid(parser, parent_context, tokens, error) # Tests error propagation # We expect the following behaviour: # - A mock error is raised for each case # - Our error context is used for the error @given(draw_directive_valid()) def test_parse_directive_error(test_data): (tokens, expected) = test_data parent_context = static_parse_context() context = ParseContext(ParseTask.PARSE_DIRECTIVE, tokens[0], parent_context) error = ParseErrorException(expected, None, None, context) parser = MockParserInvalid().parse_directive template_test_invalid(parser, parent_context, tokens, error)