Newer
Older
NewLang / tests / parse / test_file.py
# SPDX-License-Identifier: LGPL-2.1-only
# Copyright 2022 Jookia <contact@jookia.org>

# File syntax consists of the following:
# - One or more directives
#
# Parsing gives the following:
# A list of directives - All directives in the file
#
# The following error contexts are used:
# PARSE_FILE - Used when parsing the file
#
# No parse errors are generated.
#
# The following parsers are used and have their errors
# and data structures propagated:
# parse_directive - Used to parse a directive

import enum

from hypothesis import assume, given
from hypothesis.strategies import composite, data, integers, just, lists

from src.token import TokenStream
from src.parse import (
    ParseContext,
    ParseError,
    ParseErrorException,
    ParseTask,
    Parser,
    read_token,
)
from tests.parse.templates import (
    template_test_invalid,
)
from tests.test_token import (
    draw_token_random,
    static_token_by_value,
)
from tests.parse.test_parse import static_parse_context

#
# Helper functions
#


# Values used by the mocked parser
class MockDirective(enum.Enum):
    MockDirective = enum.auto()


# Mocks and tests the parse_directive parser
# Instead of parsing directives, return a mock value
# it instead returns a mock value
class MockParser(Parser):
    def parse_directive(self, stream, parent_context):
        read_token(stream, "MockDirective", parent_context)
        return MockDirective.MockDirective


# A valid directive
def static_directive():
    return ([static_token_by_value("MockDirective")], MockDirective.MockDirective)


# A valid file
@composite
def draw_file_valid(draw):
    directives = draw(lists(just(static_directive())))
    all_tokens = []
    all_expected = []
    for (tokens, expected) in directives:
        all_tokens += tokens
        all_expected.append(expected)
    return (all_tokens, all_expected)


#
# Test functions
#

# Tests parsing a valid file
# We expect the following behaviour:
# - All directives are parsed
# - No tokens are left after parsing
@given(draw_file_valid())
def test_parse_file_valid(test_data):
    (tokens, expected) = test_data
    stream = TokenStream(tokens.copy())
    parsed = MockParser().parse_file(stream, None)
    assert parsed == expected
    assert stream.pop() is None


# Tests parsing a invalid file
# We expect the following behaviour:
# - The error context is PARSE_FILE
# - A wrong directive error is propagated
@given(draw_file_valid(), data())
def test_parse_file_invalid(test_data, data):
    (tokens, _) = test_data
    assume(len(tokens) > 0)
    new_token = data.draw(draw_token_random(), "error token")
    assume(new_token.value != "MockDirective")
    max_chosen = len(tokens) - 1
    chosen = data.draw(
        integers(min_value=0, max_value=max_chosen), "error token position"
    )
    new_tokens = tokens[:chosen] + [new_token] + tokens[chosen + 1 :]
    parent_context = static_parse_context()
    context = ParseContext(ParseTask.PARSE_FILE, new_tokens[0], parent_context)
    error = ParseErrorException(
        ParseError.WRONG_TOKEN, new_token, "MockDirective", context
    )
    parser = MockParser().parse_file
    template_test_invalid(parser, parent_context, new_tokens, error)