"""Testcases for cssutils.css.CSSCharsetRule""" import sys import re import xml.dom from cssutils.prodparser import ( Prod, Sequence, Choice, PreDef, ParseError, Exhausted, ProdParser, ) import pytest class TestProd: def test_init(self): "Prod.__init__(...)" p = Prod('min', lambda t, v: t == 1 and v == 2) assert str(p) == 'min' assert p.toStore is None assert p.optional is False p = Prod('optional', lambda t, v: True, optional=True) assert p.optional is True def test_initMatch(self): "Prod.__init__(...match=...)" p = Prod('min', lambda t, v: t == 1 and v == 2) assert p.match(1, 2) is True assert p.match(2, 2) is False assert p.match(1, 1) is False def test_initToSeq(self): "Prod.__init__(...toSeq=...)" # simply saves p = Prod('all', lambda t, tokens: True, toSeq=None) assert p.toSeq([1, 2], None) == (1, 2) # simply saves assert p.toSeq(['s1', 's2'], None) == ('s1', 's2') # simply saves # saves callback(val) p = Prod( 'all', lambda t, v: True, toSeq=lambda t, tokens: (1 == t[0], 3 == t[1]) ) assert p.toSeq([1, 3], None) == (True, True) assert p.toSeq([2, 4], None) == (False, False) def test_initToStore(self): "Prod.__init__(...toStore=...)" p = Prod('all', lambda t, v: True, toStore='key') # save as key s = {} p.toStore(s, 1) assert s['key'] == 1 # append to key s = {'key': []} p.toStore(s, 1) p.toStore(s, 2) assert s['key'] == [1, 2] # callback def doubleToStore(key): def toStore(store, item): store[key] = item * 2 return toStore p = Prod('all', lambda t, v: True, toStore=doubleToStore('key')) s = {'key': []} p.toStore(s, 1) assert s['key'] == 2 def test_matches(self): "Prod.matches(token)" p1 = Prod('p1', lambda t, v: t == 1 and v == 2) p2 = Prod('p2', lambda t, v: t == 1 and v == 2, optional=True) assert p1.matches([1, 2, 0, 0]) is True assert p2.matches([1, 2, 0, 0]) is True assert p1.matches([0, 0, 0, 0]) is False assert p2.matches([0, 0, 0, 0]) is False class TestSequence: def test_init(self): "Sequence.__init__()" p1 = Prod('p1', lambda t, v: t == 1) p2 = Prod('p2', lambda t, v: t == 2) seq = Sequence(p1, p2) assert 1 == seq._min assert 1 == seq._max def test_initminmax(self): "Sequence.__init__(...minmax=...)" p1 = Prod('p1', lambda t, v: t == 1) p2 = Prod('p2', lambda t, v: t == 2) s = Sequence(p1, p2, minmax=lambda: (2, 3)) assert 2 == s._min assert 3 == s._max s = Sequence(p1, p2, minmax=lambda: (0, None)) assert 0 == s._min try: # py2.6/3 m = sys.maxsize except AttributeError: # py<1.6 m = sys.maxsize assert m == s._max def test_optional(self): "Sequence.optional" p1 = Prod('p1', lambda t, v: t == 1) s = Sequence(p1, minmax=lambda: (1, 3)) assert not s.optional s = Sequence(p1, minmax=lambda: (0, 3)) assert s.optional s = Sequence(p1, minmax=lambda: (0, None)) assert s.optional def test_reset(self): "Sequence.reset()" p1 = Prod('p1', lambda t, v: t == 1) p2 = Prod('p2', lambda t, v: t == 2) seq = Sequence(p1, p2) t1 = (1, 0, 0, 0) t2 = (2, 0, 0, 0) assert p1 == seq.nextProd(t1) assert p2 == seq.nextProd(t2) with pytest.raises(Exhausted): seq.nextProd(t1) seq.reset() assert p1 == seq.nextProd(t1) def test_matches(self): "Sequence.matches()" p1 = Prod('p1', lambda t, v: t == 1) p2 = Prod('p2', lambda t, v: t == 2, optional=True) t1 = (1, 0, 0, 0) t2 = (2, 0, 0, 0) t3 = (3, 0, 0, 0) s = Sequence(p1, p2) assert s.matches(t1) assert not s.matches(t2) s = Sequence(p2, p1) assert s.matches(t1) assert s.matches(t2) s = Sequence(Choice(p1, p2)) assert s.matches(t1) assert s.matches(t2) assert not s.matches(t3) def test_nextProd(self): "Sequence.nextProd()" p1 = Prod('p1', lambda t, v: t == 1, optional=True) p2 = Prod('p2', lambda t, v: t == 2) t1 = (1, 0, 0, 0) t2 = (2, 0, 0, 0) tests = { # seq: list of list of (token, prod or error msg) (p1,): ( [(t1, p1)], [(t2, 'Extra token')], # as p1 optional [(t1, p1), (t1, 'Extra token')], [(t1, p1), (t2, 'Extra token')], ), (p2,): ( [(t2, p2)], [(t2, p2), (t2, 'Extra token')], [(t2, p2), (t1, 'Extra token')], [(t1, 'Missing token for production p2')], ), (p1, p2): ( [(t1, p1), (t2, p2)], [(t1, p1), (t1, 'Missing token for production p2')], ), } for seqitems, results in list(tests.items()): for result in results: seq = Sequence(*seqitems) for t, p in result: if isinstance(p, str): with pytest.raises(ParseError, match=p): seq.nextProd(t) else: assert p == seq.nextProd(t) tests = { # seq: list of list of (token, prod or error msg) # as p1 optional! (p1, p1): ( [(t1, p1)], [(t1, p1), (t1, p1)], [(t1, p1), (t1, p1)], [(t1, p1), (t1, p1), (t1, p1)], [(t1, p1), (t1, p1), (t1, p1), (t1, p1)], [(t1, p1), (t1, p1), (t1, p1), (t1, p1), (t1, 'Extra token')], ), (p1,): ( [(t1, p1)], [(t2, 'Extra token')], [(t1, p1), (t1, p1)], [(t1, p1), (t2, 'Extra token')], [(t1, p1), (t1, p1), (t1, 'Extra token')], [(t1, p1), (t1, p1), (t2, 'Extra token')], ), # as p2 NOT optional (p2,): ( [(t2, p2)], [(t1, 'Missing token for production p2')], [(t2, p2), (t2, p2)], [(t2, p2), (t1, 'No match for (1, 0, 0, 0) in Sequence(p2)')], [(t2, p2), (t2, p2), (t2, 'Extra token')], [(t2, p2), (t2, p2), (t1, 'Extra token')], ), (p1, p2): ( [(t1, p1), (t1, 'Missing token for production p2')], [(t2, p2), (t2, p2)], [(t2, p2), (t1, p1), (t2, p2)], [(t1, p1), (t2, p2), (t2, p2)], [(t1, p1), (t2, p2), (t1, p1), (t2, p2)], [(t2, p2), (t2, p2), (t2, 'Extra token')], [(t2, p2), (t1, p1), (t2, p2), (t1, 'Extra token')], [(t2, p2), (t1, p1), (t2, p2), (t2, 'Extra token')], [(t1, p1), (t2, p2), (t2, p2), (t1, 'Extra token')], [(t1, p1), (t2, p2), (t2, p2), (t2, 'Extra token')], [(t1, p1), (t2, p2), (t1, p1), (t2, p2), (t1, 'Extra token')], [(t1, p1), (t2, p2), (t1, p1), (t2, p2), (t2, 'Extra token')], ), } for seqitems, results in list(tests.items()): for result in results: seq = Sequence(minmax=lambda: (1, 2), *seqitems) for t, p in result: if isinstance(p, str): with pytest.raises(ParseError, match=re.escape(p)): seq.nextProd(t) else: assert p == seq.nextProd(t) class TestChoice: def test_init(self): "Choice.__init__()" p1 = Prod('p1', lambda t, v: t == 1) p2 = Prod('p2', lambda t, v: t == 2) t0 = (0, 0, 0, 0) t1 = (1, 0, 0, 0) t2 = (2, 0, 0, 0) ch = Choice(p1, p2) with pytest.raises( ParseError, match=re.escape('No match for (0, 0, 0, 0) in Choice(p1, p2)') ): ch.nextProd(t0) assert p1 == ch.nextProd(t1) with pytest.raises(Exhausted, match='Extra token'): ch.nextProd(t1) ch = Choice(p1, p2) assert p2 == ch.nextProd(t2) with pytest.raises(Exhausted, match='Extra token'): ch.nextProd(t2) ch = Choice(p2, p1) with pytest.raises( ParseError, match=re.escape('No match for (0, 0, 0, 0) in Choice(p2, p1)') ): ch.nextProd(t0) assert p1 == ch.nextProd(t1) with pytest.raises(Exhausted, match='Extra token'): ch.nextProd(t1) ch = Choice(p2, p1) assert p2 == ch.nextProd(t2) with pytest.raises(Exhausted, match='Extra token'): ch.nextProd(t2) def test_matches(self): "Choice.matches()" p1 = Prod('p1', lambda t, v: t == 1) p2 = Prod('p2', lambda t, v: t == 2, optional=True) t1 = (1, 0, 0, 0) t2 = (2, 0, 0, 0) t3 = (3, 0, 0, 0) c = Choice(p1, p2) assert c.matches(t1) assert c.matches(t2) assert not c.matches(t3) c = Choice(Sequence(p1), Sequence(p2)) assert c.matches(t1) assert c.matches(t2) assert not c.matches(t3) def test_nested(self): "Choice with nested Sequence" p1 = Prod('p1', lambda t, v: t == 1) p2 = Prod('p2', lambda t, v: t == 2) s1 = Sequence(p1, p1) s2 = Sequence(p2, p2) t0 = (0, 0, 0, 0) t1 = (1, 0, 0, 0) t2 = (2, 0, 0, 0) ch = Choice(s1, s2) expected = ( 'No match for (0, 0, 0, 0) in Choice(Sequence(p1, p1), Sequence(p2, p2))' ) with pytest.raises(ParseError, match=re.escape(expected)): ch.nextProd(t0) assert s1 == ch.nextProd(t1) with pytest.raises(Exhausted, match='Extra token'): ch.nextProd(t1) ch = Choice(s1, s2) assert s2 == ch.nextProd(t2) with pytest.raises(Exhausted, match='Extra token'): ch.nextProd(t1) def test_reset(self): "Choice.reset()" p1 = Prod('p1', lambda t, v: t == 1) p2 = Prod('p2', lambda t, v: t == 2) t1 = (1, 0, 0, 0) t2 = (2, 0, 0, 0) ch = Choice(p1, p2) assert p1 == ch.nextProd(t1) with pytest.raises(Exhausted): ch.nextProd(t1) ch.reset() assert p2 == ch.nextProd(t2) class TestProdParser: def setup_method(self): pass def test_parse_keepS(self): "ProdParser.parse(keepS)" p = ProdParser() # text, name, productions, store=None def prods(): return Sequence(PreDef.char(';', ';'), PreDef.char(':', ':')) w, seq, store, unused = p.parse('; :', 'test', prods(), keepS=True) assert w assert 3 == len(seq) w, seq, store, unused = p.parse('; :', 'test', prods(), keepS=False) assert w assert 2 == len(seq) def test_combi(self): "ProdParser.parse() 2" p1 = Prod('p1', lambda t, v: v == '1') p2 = Prod('p2', lambda t, v: v == '2') p3 = Prod('p3', lambda t, v: v == '3') tests = { '1 2': True, '1 2 1 2': True, '3': True, # '': 'No match in Choice(Sequence(p1, p2), p3)', '1': 'Missing token for production p2', '1 2 1': 'Missing token for production p2', '1 2 1 2 x': "No match: ('IDENT', 'x', 1, 9)", '1 2 1 2 1': "No match: ('NUMBER', '1', 1, 9)", '3 x': "No match: ('IDENT', 'x', 1, 3)", '3 3': "No match: ('NUMBER', '3', 1, 3)", } for text, exp in list(tests.items()): prods = Choice(Sequence(p1, p2, minmax=lambda: (1, 2)), p3) if exp is True: wellformed, seq, store, unused = ProdParser().parse(text, 'T', prods) assert wellformed == exp else: with pytest.raises(xml.dom.SyntaxErr, match=re.escape(f'T: {exp}')): ProdParser().parse( text, 'T', prods, ) tests = { '1 3': True, '1 1 3': True, '2 3': True, '1': 'Missing token for production p3', '1 1': 'Missing token for production p3', '1 3 3': "No match: ('NUMBER', '3', 1, 5)", '1 1 3 3': "No match: ('NUMBER', '3', 1, 7)", '2 3 3': "No match: ('NUMBER', '3', 1, 5)", '2': 'Missing token for production p3', '3': "Missing token for production Choice(Sequence(p1), p2): " "('NUMBER', '3', 1, 1)", } for text, exp in list(tests.items()): prods = Sequence(Choice(Sequence(p1, minmax=lambda: (1, 2)), p2), p3) if exp is True: wellformed, seq, store, unused = ProdParser().parse(text, 'T', prods) assert wellformed == exp else: with pytest.raises(xml.dom.SyntaxErr, match=re.escape(f'T: {exp}')): ProdParser().parse( text, 'T', prods, )