diff --git a/tests/parse/test_parse.py b/tests/parse/test_parse.py index 9db1818..b4d88c7 100644 --- a/tests/parse/test_parse.py +++ b/tests/parse/test_parse.py @@ -16,10 +16,9 @@ NoteSkipper, ParseError, ParseErrorException, - Parser, parse, ) -from src.syntax import Syntax, SyntaxStream, SyntaxType +from src.syntax import SyntaxStream, SyntaxType from tests.test_syntax import ( draw_token_by_value, draw_token_classified, @@ -70,129 +69,6 @@ assert (except1 == except2) == equals -# Draws a random token suitable for text building -@composite -def draw_text_value_token(draw): - token = draw(draw_syntax_token()) - assume(token.value not in ["StartText", "EndText"]) - return token - - -# Draws tokens to make a valid text string and its value -@composite -def draw_syntax_text_valid(draw): - tokens = draw(lists(draw_text_value_token())) - buffer = "" - for token in tokens: - buffer += token.value + " " - value = buffer[:-1] # Drop trailing space - start = draw(draw_token_by_value("StartText")) - end = draw(draw_token_by_value("EndText")) - all_tokens = [start] + tokens + [end] - result = Syntax(value, start.location, SyntaxType.TEXT) - return (all_tokens, result) - - -# Tests parse_text works correctly -# We expect the following behaviour: -# - Only the text expression is parsed -# - The resulting text is the value of tokens between StartText and EndText -# - The value of the tokens is joined by U+0020 SPACE code points -# - The Syntax's value is the resulting text -# - The Syntax's type is SyntaxType.TEXT -# - The Syntax's location is the StartText location -@given(draw_syntax_random(), draw_syntax_text_valid()) -def test_parse_text_valid(canary, test_data): - (tokens, result) = test_data - stream = SyntaxStream(tokens + [canary]) - parsed = Parser().parse_text(stream) - assert parsed is not None - assert parsed == result - assert stream.pop() == canary - assert stream.pop() is None - - -# Generate text without StartText -# We expect the following behaviour: -# - Error if there is no StartText node at all -# - Error if StartText is not a SyntaxType.TOKEN -# - Error if StartText's token value is not "StartText" -@composite -def draw_syntax_text_invalid_nostarttext(draw): - (tokens, _) = draw(draw_syntax_text_valid()) - if draw(booleans()): - token = draw(draw_syntax_random()) - assume(not (token.type == SyntaxType.TOKEN and token.value == "StartText")) - new_tokens = [token] + tokens[1:0] - if token.type == SyntaxType.TOKEN: - error = ParseErrorException(ParseError.WRONG_TOKEN, token, "StartText") - else: - error = ParseErrorException(ParseError.NOT_TOKEN, token, None) - return (new_tokens, error) - else: - error = ParseErrorException(ParseError.NO_TOKEN, None, None) - return ([], error) - - -# Generate text with invalid content tokens -# We expect the following behaviour: -# - Error if a content token is not a SyntaxType.TOKEN -@composite -def draw_syntax_text_invalid_invalidcontent(draw): - (tokens, _) = draw(draw_syntax_text_valid()) - token = draw(draw_syntax_random()) - assume(token.type != SyntaxType.TOKEN) - new_tokens = insert_random(draw, tokens, token) - error = ParseErrorException(ParseError.NOT_TOKEN, token, None) - return (new_tokens, error) - - -# Generate text with a StartText token in it -# We expect the following behaviour: -# - Error if a StartText token is in the text content -@composite -def draw_syntax_text_invalid_extrastarttext(draw): - (tokens, _) = draw(draw_syntax_text_valid()) - start = draw(draw_token_by_value("StartText")) - new_tokens = insert_random(draw, tokens, start) - error = ParseErrorException(ParseError.FOUND_STARTTEXT, start, None) - return (new_tokens, error) - - -# Generate text without EndText -# We expect the following behaviour: -# - Error if there is no EndText node at all -@composite -def draw_syntax_text_invalid_noendtext(draw): - (tokens, _) = draw(draw_syntax_text_valid()) - error = ParseErrorException(ParseError.NO_TOKEN, None, None) - return (tokens[0:-1], error) - - -# Generate an invalid text case -@composite -def draw_syntax_text_invalid(draw): - strategies = [ - draw_syntax_text_invalid_nostarttext(), - draw_syntax_text_invalid_invalidcontent(), - draw_syntax_text_invalid_extrastarttext(), - draw_syntax_text_invalid_noendtext(), - ] - return draw(one_of(strategies)) - - -# Test that parse_text errors in invalid cases -@given(draw_syntax_text_invalid()) -def test_parse_text_invalid(test_data): - (tokens, error) = test_data - stream = SyntaxStream(tokens) - try: - parsed = Parser().parse_text(stream) - raise AssertionError("Parsed invalid data: %s" % (parsed)) - except ParseErrorException as e: - assert e == error - - # Draws a random token suitable for note building @composite def draw_note_value_token(draw): diff --git a/tests/parse/test_text.py b/tests/parse/test_text.py new file mode 100644 index 0000000..8823765 --- /dev/null +++ b/tests/parse/test_text.py @@ -0,0 +1,153 @@ +# SPDX-License-Identifier: LGPL-2.1-only +# Copyright 2022 Jookia + +from hypothesis import assume, given +from hypothesis.strategies import ( + booleans, + composite, + integers, + lists, + one_of, +) + +from src.parse import ( + ParseError, + ParseErrorException, + Parser, +) +from src.syntax import Syntax, SyntaxStream, SyntaxType +from tests.test_syntax import ( + draw_token_by_value, + draw_syntax_random, + draw_syntax_token, +) + + +# Inserts an element at a random place in a list +def insert_random(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 + + +# Draws a random token suitable for text building +@composite +def draw_text_value_token(draw): + token = draw(draw_syntax_token()) + assume(token.value not in ["StartText", "EndText"]) + return token + + +# Draws tokens to make a valid text string and its value +@composite +def draw_syntax_text_valid(draw): + tokens = draw(lists(draw_text_value_token())) + buffer = "" + for token in tokens: + buffer += token.value + " " + value = buffer[:-1] # Drop trailing space + start = draw(draw_token_by_value("StartText")) + end = draw(draw_token_by_value("EndText")) + all_tokens = [start] + tokens + [end] + result = Syntax(value, start.location, SyntaxType.TEXT) + return (all_tokens, result) + + +# Tests parse_text works correctly +# We expect the following behaviour: +# - Only the text expression is parsed +# - The resulting text is the value of tokens between StartText and EndText +# - The value of the tokens is joined by U+0020 SPACE code points +# - The Syntax's value is the resulting text +# - The Syntax's type is SyntaxType.TEXT +# - The Syntax's location is the StartText location +@given(draw_syntax_random(), draw_syntax_text_valid()) +def test_parse_text_valid(canary, test_data): + (tokens, result) = test_data + stream = SyntaxStream(tokens + [canary]) + parsed = Parser().parse_text(stream) + assert parsed is not None + assert parsed == result + assert stream.pop() == canary + assert stream.pop() is None + + +# Generate text without StartText +# We expect the following behaviour: +# - Error if there is no StartText node at all +# - Error if StartText is not a SyntaxType.TOKEN +# - Error if StartText's token value is not "StartText" +@composite +def draw_syntax_text_invalid_nostarttext(draw): + (tokens, _) = draw(draw_syntax_text_valid()) + if draw(booleans()): + token = draw(draw_syntax_random()) + assume(not (token.type == SyntaxType.TOKEN and token.value == "StartText")) + new_tokens = [token] + tokens[1:0] + if token.type == SyntaxType.TOKEN: + error = ParseErrorException(ParseError.WRONG_TOKEN, token, "StartText") + else: + error = ParseErrorException(ParseError.NOT_TOKEN, token, None) + return (new_tokens, error) + else: + error = ParseErrorException(ParseError.NO_TOKEN, None, None) + return ([], error) + + +# Generate text with invalid content tokens +# We expect the following behaviour: +# - Error if a content token is not a SyntaxType.TOKEN +@composite +def draw_syntax_text_invalid_invalidcontent(draw): + (tokens, _) = draw(draw_syntax_text_valid()) + token = draw(draw_syntax_random()) + assume(token.type != SyntaxType.TOKEN) + new_tokens = insert_random(draw, tokens, token) + error = ParseErrorException(ParseError.NOT_TOKEN, token, None) + return (new_tokens, error) + + +# Generate text with a StartText token in it +# We expect the following behaviour: +# - Error if a StartText token is in the text content +@composite +def draw_syntax_text_invalid_extrastarttext(draw): + (tokens, _) = draw(draw_syntax_text_valid()) + start = draw(draw_token_by_value("StartText")) + new_tokens = insert_random(draw, tokens, start) + error = ParseErrorException(ParseError.FOUND_STARTTEXT, start, None) + return (new_tokens, error) + + +# Generate text without EndText +# We expect the following behaviour: +# - Error if there is no EndText node at all +@composite +def draw_syntax_text_invalid_noendtext(draw): + (tokens, _) = draw(draw_syntax_text_valid()) + error = ParseErrorException(ParseError.NO_TOKEN, None, None) + return (tokens[0:-1], error) + + +# Generate an invalid text case +@composite +def draw_syntax_text_invalid(draw): + strategies = [ + draw_syntax_text_invalid_nostarttext(), + draw_syntax_text_invalid_invalidcontent(), + draw_syntax_text_invalid_extrastarttext(), + draw_syntax_text_invalid_noendtext(), + ] + return draw(one_of(strategies)) + + +# Test that parse_text errors in invalid cases +@given(draw_syntax_text_invalid()) +def test_parse_text_invalid(test_data): + (tokens, error) = test_data + stream = SyntaxStream(tokens) + try: + parsed = Parser().parse_text(stream) + raise AssertionError("Parsed invalid data: %s" % (parsed)) + except ParseErrorException as e: + assert e == error