grammar-inference-engine/tests/test_gbnf.py
tobjend 19a1db48ef test: update tests for AST representation
Assertions now use isinstance() on AST nodes (Concat, Alt, Plus,
Optional, Star, Symbol, Empty) instead of comparing SORE strings.
2026-07-13 01:13:48 +02:00

143 lines
5.2 KiB
Python

"""Tests for AST → GBNF converter."""
import pytest
from bex.gbnf import to_gbnf, to_gbnf_with_rules
from bex.grammar import (
Symbol, Concat, Alt, Plus, Optional, Star, Epsilon, Empty,
)
_a = Symbol('a')
_b = Symbol('b')
_c = Symbol('c')
_d = Symbol('d')
class TestToGBNF:
def test_literal(self):
assert to_gbnf(Symbol('mockk')) == 'root ::= "mockk"'
def test_concat(self):
assert to_gbnf(Concat([Symbol('raise'), Symbol('ValueError')])) == 'root ::= "raise" "ValueError"'
def test_plus_group(self):
assert to_gbnf(Plus(Symbol('append'))) == 'root ::= "append"+'
def test_plus_concat(self):
assert to_gbnf(Concat([Symbol('raise'), Plus(Symbol('ValueError'))])) == 'root ::= "raise" "ValueError"+'
def test_nested_optional_plus(self):
g = Concat([Symbol('assertEquals'), Plus(Concat([Symbol('of'), Optional(Symbol('assertFailsWith'))]))])
assert to_gbnf(g) == 'root ::= "assertEquals" ("of" "assertFailsWith"?)+'
def test_long_concat(self):
g = Concat([Symbol('filesIn'), Symbol('filter'), Symbol('contains'),
Symbol('assertTrue'), Plus(Symbol('hasImport'))])
assert to_gbnf(g) == 'root ::= "filesIn" "filter" "contains" "assertTrue" "hasImport"+'
def test_simple_concat(self):
g = Concat([Symbol('trim'), Symbol('lowercase'), Plus(Symbol('warn'))])
assert to_gbnf(g) == 'root ::= "trim" "lowercase" "warn"+'
def test_flat_concat(self):
g = Concat([Symbol('DoclingConfig'), Symbol('assertThatThrownBy'),
Symbol('validateCriticalSettings'), Symbol('isInstanceOf'),
Symbol('hasMessageContaining')])
assert to_gbnf(g) == 'root ::= "DoclingConfig" "assertThatThrownBy" "validateCriticalSettings" "isInstanceOf" "hasMessageContaining"'
def test_simple_plus(self):
assert to_gbnf(Plus(Symbol('abort'))) == 'root ::= "abort"+'
def test_concat_with_plus(self):
assert to_gbnf(Concat([Symbol('return'), Plus(Symbol('url_for'))])) == 'root ::= "return" "url_for"+'
def test_star(self):
assert to_gbnf(Star(Symbol('foo'))) == 'root ::= "foo"*'
def test_optional(self):
assert to_gbnf(Optional(Symbol('bar'))) == 'root ::= "bar"?'
class TestToGBNFWithRules:
def test_named_rule(self):
g = Concat([Symbol('raise'), Plus(Symbol('ValueError'))])
result = to_gbnf_with_rules(g, name='my-pattern')
assert result == 'my-pattern ::= "raise" "ValueError"+'
def test_default_name(self):
result = to_gbnf_with_rules(Symbol('mockk'))
assert result == 'root ::= "mockk"'
def test_nested(self):
g = Concat([Symbol('assertEquals'), Plus(Concat([Symbol('of'), Optional(Symbol('assertFailsWith'))]))])
result = to_gbnf_with_rules(g)
assert result == 'root ::= "assertEquals" ("of" "assertFailsWith"?)+'
class TestGBNFDisjunction:
"""Test Alt nodes in GBNF output."""
def test_simple_disjunction(self):
assert to_gbnf(Alt([_a, _b])) == 'root ::= "a" | "b"'
def test_disjunction_with_rep(self):
assert to_gbnf(Plus(Alt([_a, _b]))) == 'root ::= ("a" | "b")+'
def test_disjunction_optional(self):
assert to_gbnf(Optional(Alt([_a, _b]))) == 'root ::= ("a" | "b")?'
def test_disjunction_star(self):
assert to_gbnf(Star(Alt([_a, _b]))) == 'root ::= ("a" | "b")*'
def test_disjunction_in_concat(self):
g = Concat([Plus(_a), Plus(Alt([Symbol('BAD_REQUEST'), Symbol('CONFLICT')]))])
assert to_gbnf(g) == 'root ::= "a"+ ("BAD_REQUEST" | "CONFLICT")+'
def test_four_way_disjunction(self):
g = Plus(Alt([Symbol('assertEquals'), Symbol('authorize'), Symbol('coEvery'), Symbol('coVerify')]))
assert to_gbnf(g) == 'root ::= ("assertEquals" | "authorize" | "coEvery" | "coVerify")+'
def test_disjunction_parenthesized_in_concat(self):
g = Concat([Plus(_a), Alt([_b, _c])])
assert to_gbnf(g) == 'root ::= "a"+ ("b" | "c")'
def test_disjunction_rep_then_disjunction(self):
g = Concat([Plus(Alt([_a, _b])), Alt([_c, _d])])
assert to_gbnf(g) == 'root ::= ("a" | "b")+ ("c" | "d")'
class TestGBNFCompoundRepetition:
"""Test compound repetition: nested quantifiers collapse correctly."""
def test_plus_question(self):
g = Plus(Optional(_a))
result = to_gbnf(g)
assert '"a"?+' in result or '"a"*' in result
def test_plus_star(self):
g = Plus(Star(_a))
result = to_gbnf(g)
assert '"a"*+' in result or '"a"*' in result
def test_question_plus(self):
g = Optional(Plus(_a))
result = to_gbnf(g)
assert '"a"?+' in result or '"a"+' in result
def test_flask_pattern(self):
g = Concat([
Symbol('return'),
Plus(Alt([Symbol('key'), Symbol('self')])),
Plus(Alt([Symbol('Markup'), Symbol('UUID')])),
Plus(Symbol('to_json')),
])
result = to_gbnf(g)
assert '"return"' in result
assert '"key"' in result
assert '"self"' in result
assert '"Markup"' in result
assert '"UUID"' in result
assert '"to_json"' in result
def test_double_plus(self):
g = Concat([Plus(_a), _b])
assert to_gbnf(g) == 'root ::= "a"+ "b"'