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

from hypothesis import given
from hypothesis.strategies import composite, integers

from src.parse import ParseErrorException
from src.token import TokenStream
from tests.parse.test_parse import static_parse_context
from tests.test_token import draw_token_random


# Inserts an element randomly between the first and last token of a list
def insert_random_within(draw, list, data):
    pos = draw(integers(min_value=1, max_value=(len(list) - 1)))
    new_data = list[0:pos] + [data] + list[pos:]
    return new_data


# Tests that something parses correctly
# We expect the following behaviour:
# - The decoration supplies a generator for test data and expected output
# - The decorated function is unused
# - Only the supplied tokens are parsed
# - The supplied tokens parse to the expected value
# - The Token's value is the expected value
# - The Token's location is the first token's location
def template_parse_valid(parser, draw):
    @given(draw_token_random(), draw)
    def do(canary, test_data):
        (tokens, expected) = test_data
        stream = TokenStream(tokens + [canary])
        parsed = parser(stream, None)
        if expected is None:
            assert parsed is None
        else:
            assert parsed is not None
            assert parsed == expected
        assert stream.pop() == canary
        assert stream.pop() is None

    return lambda func: do


# Tests that something parses correctly with a custom parser
# We expect the following behaviour:
# - The decorated function supplies a parser, test data and expected data
# - Only the supplied tokens are parsed
# - The supplied tokens parse to the expected value
# - The Token's value is the expected value
# - The Token's location is the first token's location
def template_parse_valid_composite(func):
    @given(draw_token_random(), composite(func)())
    def do(canary, test_data):
        (parser, tokens, expected) = test_data
        stream = TokenStream(tokens + [canary])
        parsed = parser(stream, None)
        if expected is None:
            assert parsed is None
        else:
            assert parsed is not None
            assert parsed == expected
        assert stream.pop() == canary
        assert stream.pop() is None

    return do


# Test that something parses incorrectly
# We expect the following behaviour:
# - The decoration supplies a parser function
# - The decorated function takes a parse context
# - The decorated function generates input tokens and an error
# - Parsing causes an error
# - The parse error is as expected
def template_parse_invalid(parser):
    # Wrapper to add parse_context to our test_data
    @composite
    def wrapper(draw, func):
        context = static_parse_context()
        (tokens, error) = draw(composite(func)(context))
        return (tokens, error, context)

    # test_data is the output of wrapper
    def do(test_data):
        (tokens, error, context) = test_data
        stream = TokenStream(tokens)
        try:
            parsed = parser(stream, context)
            raise AssertionError("Parsed invalid data: %s" % (parsed))
        except ParseErrorException as e:
            assert e == error

    return lambda func: given(wrapper(func))(do)


# Test that something parses incorrectly with a custom parser
# We expect the following behaviour:
# - The decorated function takes a parse context
# - The decorated function generates a parser, input tokens and an error
# - Parsing causes an error
# - The parse error is as expected
def template_parse_invalid_composite(func):
    # Wrapper to add parse_context to our test_data
    @composite
    def wrapper(draw):
        context = static_parse_context()
        (parser, tokens, error) = draw(composite(func)(context))
        return (parser, tokens, error, context)

    # test_data is the output of wrapper
    @given(wrapper())
    def do(test_data):
        (parser, tokens, error, context) = test_data
        stream = TokenStream(tokens)
        try:
            parsed = parser(stream, context)
            raise AssertionError("Parsed invalid data: %s" % (parsed))
        except ParseErrorException as e:
            assert e == error

    return do