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.syntax import SyntaxStream
from tests.parse.templates import template_parse_invalid, template_parse_valid
from tests.parse.test_parse import draw_parse_context
from tests.test_syntax import draw_token_by_value, draw_syntax_token


# Dummy parse_note implementation for testing note clearing
# This redefines skip_note to skip the StartNote and not do anything else
def clear_notes_skip_note_valid(stream, parent_context):
    stream.pop()
    return None


# Creates a NoteSkipper with clear_notes_skip_note_valid
def dummy_skipper_valid():
    skipper = NoteSkipper()
    skipper.skip_note = clear_notes_skip_note_valid
    return skipper


# Dummy parse_note implementation for testing error propgation
# This redefines skip_note to always throw an error
def clear_notes_skip_note_error(stream, parent_context):
    s = stream.peek()
    raise ParseErrorException(ParseError.TEST_ERROR, s, None, parent_context)


# Creates a NoteSkipper with clear_notes_skip_note_error
def dummy_skipper_invalid():
    skipper = NoteSkipper()
    skipper.skip_note = clear_notes_skip_note_error
    return skipper


# Draws a random token suitable for note clearing testing
@composite
def draw_clear_notes_value_token(draw):
    token = draw(draw_syntax_token())
    assume(token.value not in ["EndNote"])
    return token


# Draws tokens to make a valid soup to clear notes
@composite
def draw_syntax_clear_notes_valid(draw):
    tokens = draw(lists(draw_clear_notes_value_token()))
    output = []
    for token in tokens:
        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 tokens are passed through
@given(draw_syntax_clear_notes_valid())
def test_parse_clear_notes_valid(test_data):
    (tokens, result) = test_data
    stream = SyntaxStream(tokens)
    cleared = dummy_skipper_valid().clear_notes(stream, None)
    assert cleared == result


# Tests clear_notes passes through skip_note errors
# We expect the following behaviour:
# - When StartNote is encountered skip_note is called to skip the note
# - Any error skip_note gives is propagated through clear_notes
@template_parse_invalid(dummy_skipper_invalid().clear_notes)
def test_parse_clear_notes_startnote_propagation(draw):
    tokens = draw(lists(draw_clear_notes_value_token()))
    # Ensure we have a StartNote somewhere
    start = draw(draw_token_by_value("StartNote"))
    new_tokens = tokens + [start]
    parent_context = draw(draw_parse_context())
    context = ParseContext(ParseTask.CLEAR_NOTES, new_tokens[0], parent_context)
    for token in new_tokens:
        if token.value == "StartNote":
            error = ParseErrorException(ParseError.TEST_ERROR, token, None, context)
            return (new_tokens, error, parent_context)
    raise AssertionError("Unable to find StartNote?")


# Tests clear_notes errors when finding an EndNote
# We expect the following behaviour:
# - When EndNote is encountered a FOUND_ENDNOTE error is raised
@template_parse_invalid(dummy_skipper_valid().clear_notes)
def test_parse_clear_notes_invalid_endnote(draw):
    tokens = draw(lists(draw_clear_notes_value_token()))
    # Ensure we have an EndNote somewhere
    start = draw(draw_token_by_value("EndNote"))
    new_tokens = tokens + [start]
    parent_context = draw(draw_parse_context())
    context = ParseContext(ParseTask.CLEAR_NOTES, new_tokens[0], parent_context)
    for token in new_tokens:
        if token.value == "EndNote":
            error = ParseErrorException(ParseError.FOUND_ENDNOTE, token, None, context)
            return (new_tokens, error, parent_context)
    raise AssertionError("Unable to find EndNote?")