Newer
Older
NewLang / tests / parse / test_clear_notes.py
# 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


# Error when finding a specific token in a stream of notes
# Used for testing clear_notes otherwise
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
@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
@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)