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.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


# 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)


# 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)
    skipper = NoteSkipper()
    skipper.skip_note = clear_notes_skip_note_valid
    cleared = skipper.clear_notes(stream, None)
    assert cleared == result


# Draws tokens to test clear_notes error propagation
@composite
def draw_syntax_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 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
@given(draw_syntax_clear_notes_startnote_propagation())
def test_parse_clear_notes_startnote_propagation(test_data):
    (tokens, error, parent_context) = test_data
    stream = SyntaxStream(tokens)
    skipper = NoteSkipper()
    skipper.skip_note = clear_notes_skip_note_error
    try:
        parsed = skipper.clear_notes(stream, parent_context)
        raise AssertionError("Parsed invalid data: %s" % (parsed))
    except ParseErrorException as e:
        assert e == error


# Draws tokens to test clear_notes EndNote invalid error
@composite
def draw_syntax_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?")


# Tests clear_notes errors when finding an EndNote
# We expect the following behaviour:
# - When EndNote is encountered a FOUND_ENDNOTE error is raised
@given(draw_syntax_clear_notes_invalid_endnote())
def test_parse_clear_notes_invalid_endnote(test_data):
    (tokens, error, parent_context) = test_data
    stream = SyntaxStream(tokens)
    skipper = NoteSkipper()
    skipper.skip_note = clear_notes_skip_note_valid
    try:
        parsed = skipper.clear_notes(stream, parent_context)
        raise AssertionError("Parsed invalid data: %s" % (parsed))
    except ParseErrorException as e:
        assert e == error