# 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_parse_invalid 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(draw, parent_context, value, error): tokens = draw(lists(draw_clear_notes_value())) # Ensure we have a value somewhere start = static_token_by_value(value) new_tokens = tokens + [start] context = ParseContext(ParseTask.CLEAR_NOTES, new_tokens[0], parent_context) for token in new_tokens: if token.value == value: error = ParseErrorException(error, token, None, context) return (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 @template_parse_invalid(NoteSkipperMockInvalid().clear_notes) def test_parse_clear_notes_startnote_propagation(draw, parent_context): return error_on_token(draw, parent_context, "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 @template_parse_invalid(NoteSkipperMockValid().clear_notes) def test_parse_clear_notes_invalid_endnote(draw, parent_context): return error_on_token(draw, parent_context, "EndNote", ParseError.FOUND_ENDNOTE)