# SPDX-License-Identifier: LGPL-2.1-only # Copyright 2022 Jookia <contact@jookia.org> from hypothesis import assume, given from hypothesis.strategies import composite, lists from src.parse import ( NoteSkipper, ParseContext, ParseError, ParseErrorException, ParseTask, ) from src.token import TokenStream from tests.parse.templates import template_test_invalid from tests.parse.test_error import static_parse_context from tests.test_token import static_token_by_value, draw_token_random # Dummy NoteSkipper for testing note clearing # This redefines skip_note to skip the StartNote and not do anything else # Effectively this turns notes in to a single token for our tests, not needing # a terminating EndNote class NoteSkipperMockValid(NoteSkipper): def skip_note(self, stream, context): stream.pop() return None # Dummy NoteSkipper for testing error propagation # This redefines skip_note to always throw an error class NoteSkipperMockInvalid(NoteSkipper): def skip_note(self, stream, context): s = stream.peek() raise ParseErrorException(ParseError.TEST_ERROR, s, None, context) # Draws a random token suitable for note clearing testing @composite def draw_clear_notes_value(draw): token = draw(draw_token_random()) assume(token.value != "EndNote") return token # Draws token to make a valid soup to clear notes @composite def draw_token_clear_notes_valid(draw): tokens = draw(lists(draw_clear_notes_value())) output = [] for token in tokens: # Our modified parse_note only parses the StartNote, so we expect # the output to only remove StartNote values if token.value != "StartNote": output.append(token) return (tokens, output) # Tests clear_notes works correctly # We expect the following behaviour: # - When StartNote is encountered skip_note is called to skip the note # - Other token is passed through @given(draw_token_clear_notes_valid()) def test_parse_clear_notes_valid(test_data): (tokens, result) = test_data stream = TokenStream(tokens) cleared = NoteSkipperMockValid().clear_notes(stream, None) assert cleared == result # Check that a specific token in a stream triggers an error def error_on_token(parser, tokens, value, error_value): # Ensure we have a value somewhere start = static_token_by_value(value) new_tokens = tokens + [start] parent_context = static_parse_context() context = ParseContext(ParseTask.CLEAR_NOTES, new_tokens[0], parent_context) for token in new_tokens: if token.value == value: error = ParseErrorException(error_value, token, None, context) template_test_invalid(parser, parent_context, new_tokens, error) # Tests clear_notes passes through skip_note errors # We expect the following behaviour: # - When a StartNote token is encountered skip_note is called to skip the note # - Any error skip_note gives is propagated through clear_notes # - Have ParseTask.CLEAR_NOTES as the context's parse task @given(lists(draw_clear_notes_value())) def test_parse_clear_notes_startnote_propagation(tokens): parser = NoteSkipperMockInvalid().clear_notes error_on_token(parser, tokens, "StartNote", ParseError.TEST_ERROR) # Tests clear_notes errors when finding an EndNote # We expect the following behaviour: # - When an EndNote token is encountered a FOUND_ENDNOTE error is raised # - Have ParseTask.CLEAR_NOTES as the context's parse task @given(lists(draw_clear_notes_value())) def test_parse_clear_notes_invalid_endnote(tokens): parser = NoteSkipperMockValid().clear_notes error_on_token(parser, tokens, "EndNote", ParseError.FOUND_ENDNOTE)