# SPDX-License-Identifier: LGPL-2.1-only # Copyright 2022 Jookia <contact@jookia.org> import enum from hypothesis import assume from hypothesis.strategies import composite, just, one_of from src.parse import ParseContext, ParseError, ParseErrorException, ParseTask, Parser from tests.parse.templates import template_parse_valid, template_parse_invalid from tests.test_token import ( draw_token_keyword, draw_token_unknown, static_token_by_value, ) # Values indicating what a parser did class ParserMockAction(enum.Enum): PARSE_BOOL = enum.auto() PARSE_TEXT = enum.auto() PARSE_REFERENCE = enum.auto() # Dummy Parser for testing value parsing # Instead of actually parsing values just return a static value to show what # the parser would normally do class ParserValueMockValid(Parser): def parse_bool(self, stream, context): stream.pop() return ParserMockAction.PARSE_BOOL def parse_text(self, stream, context): stream.pop() return ParserMockAction.PARSE_TEXT def parse_reference(self, stream, context): stream.pop() return ParserMockAction.PARSE_REFERENCE # Dummy Parser for testing error propagation # Uses parser mock values as errors class ParserValueMockError(Parser): def parse_bool(self, stream, context): raise ParseErrorException(ParserMockAction.PARSE_BOOL, None, None, context) def parse_text(self, stream, context): raise ParseErrorException(ParserMockAction.PARSE_TEXT, None, None, context) def parse_reference(self, stream, context): raise ParseErrorException(ParserMockAction.PARSE_REFERENCE, None, None, context) # Generates a strategy for a valid word and parser action def token_and_action(word, action): return just(([static_token_by_value(word)], action)) # Draws tokens for values based on literals @composite def draw_token_value_literal(draw): strategies = [ token_and_action("True", ParserMockAction.PARSE_BOOL), token_and_action("False", ParserMockAction.PARSE_BOOL), token_and_action("StartText", ParserMockAction.PARSE_TEXT), ] return draw(one_of(strategies)) # Draws tokens to make a value based on a reference @composite def draw_token_value_reference(draw): token = draw(draw_token_unknown()) return ([token], ParserMockAction.PARSE_REFERENCE) # Draws tokens for a valid value @composite def draw_token_value_valid(draw): strategies = [ draw_token_value_literal(), draw_token_value_reference(), ] return draw(one_of(strategies)) # Tests parsing a literal value # We expect the following behaviour: # - parse_value parses a Bool if it sees True or False # - parse_value parses a Text if it sees StartText # template_parse_valid provides general parsing properties @template_parse_valid(ParserValueMockValid().parse_value, draw_token_value_literal()) def test_parse_value_literal(): pass # Tests parsing a reference value # We expect the following behaviour: # - parse_value parses a Reference if it sees an unknown value # template_parse_valid provides general parsing properties @template_parse_valid(ParserValueMockValid().parse_value, draw_token_value_reference()) def test_parse_value_reference(): pass # Tests parsing a keyword as a value fails # We expect the following behaviour: # - Error if a keyword is encountered # - Have ParseError.RESERVED_NAME as the exception code # - Have ParseTask.PARSE_VALUE as the context's parse task @template_parse_invalid(Parser().parse_value) def test_parse_value_invalid_name(draw, parent_context): token = draw(draw_token_keyword()) assume(token.value != "StartText") context = ParseContext(ParseTask.PARSE_VALUE, token, parent_context) error = ParseErrorException(ParseError.RESERVED_NAME, token, None, context) return ([token], error) # Tests parsing empty value # We expect the following behaviour: # - Have ParseError.NO_TOKEN as the exception code # - Have ParseTask.PARSE_VALUE as the context's parse task @template_parse_invalid(Parser().parse_value) def test_parse_value_invalid_empty(draw, parent_context): context = ParseContext(ParseTask.PARSE_VALUE, None, parent_context) error = ParseErrorException(ParseError.NO_TOKEN, None, None, context) return ([], error) # Tests parse_value error propagation # We expect the following behaviour: # - Errors from parsing are propagated and have the correct context # - Have ParseTask.PARSE_VALUE as the context's parse task @template_parse_invalid(ParserValueMockError().parse_value) def test_parse_value_error_propagation(draw, parent_context): (tokens, action) = draw(draw_token_value_valid()) context = ParseContext(ParseTask.PARSE_VALUE, tokens[0], parent_context) error = ParseErrorException(action, None, None, context) return (tokens, error)