frappe/env/lib/python3.12/site-packages/cssutils/tests/test_util.py

579 lines
20 KiB
Python
Raw Normal View History

"""Testcases for cssutils.util"""
from email.message import Message
import re
import urllib.request
import urllib.error
import urllib.parse
from unittest import mock
from cssutils.util import Base, ListSeq, _readUrl, _defaultFetcher, LazyRegex
import pytest
class TestListSeq:
def test_all(self):
"util.ListSeq"
ls = ListSeq()
assert 0 == len(ls)
# append()
with pytest.raises(NotImplementedError):
ls.append(1)
# set
with pytest.raises(NotImplementedError):
ls.__setitem__(0, 1)
# hack:
ls.seq.append(1)
ls.seq.append(2)
# len
assert 2 == len(ls)
# __contains__
assert True is (1 in ls)
# get
assert 1 == ls[0]
assert 2 == ls[1]
# del
del ls[0]
assert 1 == len(ls)
assert False is (1 in ls)
# for in
for x in ls:
assert 2 == x
class TestBase:
def test_normalize(self):
"Base._normalize()"
b = Base()
tests = {
'abcdefg ABCDEFG äöü߀ AÖÜ': 'abcdefg abcdefg äöü߀ aöü',
r'\ga\Ga\\\ ': r'gaga\ ',
r'0123456789': '0123456789',
# unicode escape seqs should have been done by
# the tokenizer...
}
for test, exp in list(tests.items()):
assert b._normalize(test) == exp
# static too
assert Base._normalize(test) == exp
def test_tokenupto(self): # noqa: C901
"Base._tokensupto2()"
# tests nested blocks of {} [] or ()
b = Base()
tests = [
('default', 'a[{1}]({2}) { } NOT', 'a[{1}]({2}) { }', False),
('default', 'a[{1}]({2}) { } NOT', 'a[{1}]func({2}) { }', True),
('blockstartonly', 'a[{1}]({2}) { NOT', 'a[{1}]({2}) {', False),
('blockstartonly', 'a[{1}]({2}) { NOT', 'a[{1}]func({2}) {', True),
('propertynameendonly', 'a[(2)1] { }2 : a;', 'a[(2)1] { }2 :', False),
('propertynameendonly', 'a[(2)1] { }2 : a;', 'a[func(2)1] { }2 :', True),
(
'propertyvalueendonly',
'a{;{;}[;](;)}[;{;}[;](;)](;{;}[;](;)) 1; NOT',
'a{;{;}[;](;)}[;{;}[;](;)](;{;}[;](;)) 1;',
False,
),
(
'propertyvalueendonly',
'a{;{;}[;](;)}[;{;}[;](;)](;{;}[;](;)) 1; NOT',
'a{;{;}[;]func(;)}[;{;}[;]func(;)]func(;{;}[;]func(;)) 1;',
True,
),
(
'funcendonly',
'a{[1]}([3])[{[1]}[2]([3])]) NOT',
'a{[1]}([3])[{[1]}[2]([3])])',
False,
),
(
'funcendonly',
'a{[1]}([3])[{[1]}[2]([3])]) NOT',
'a{[1]}func([3])[{[1]}[2]func([3])])',
True,
),
(
'selectorattendonly',
'[a[()]{()}([()]{()}())] NOT',
'[a[()]{()}([()]{()}())]',
False,
),
(
'selectorattendonly',
'[a[()]{()}([()]{()}())] NOT',
'[a[func()]{func()}func([func()]{func()}func())]',
True,
),
# issue 50
('withstarttoken [', 'a];x', '[a];', False),
]
for typ, values, exp, paransasfunc in tests:
def maketokens(valuelist):
# returns list of tuples
return [('TYPE', v, 0, 0) for v in valuelist]
tokens = maketokens(list(values))
if paransasfunc:
for i, t in enumerate(tokens):
if '(' == t[1]:
tokens[i] = ('FUNCTION', 'func(', t[2], t[3])
if 'default' == typ:
restokens = b._tokensupto2(tokens)
elif 'blockstartonly' == typ:
restokens = b._tokensupto2(tokens, blockstartonly=True)
elif 'propertynameendonly' == typ:
restokens = b._tokensupto2(tokens, propertynameendonly=True)
elif 'propertyvalueendonly' == typ:
restokens = b._tokensupto2(tokens, propertyvalueendonly=True)
elif 'funcendonly' == typ:
restokens = b._tokensupto2(tokens, funcendonly=True)
elif 'selectorattendonly' == typ:
restokens = b._tokensupto2(tokens, selectorattendonly=True)
elif 'withstarttoken [' == typ:
restokens = b._tokensupto2(tokens, ('CHAR', '[', 0, 0))
res = ''.join([t[1] for t in restokens])
assert exp == res
class Test_readUrl:
"""needs mock"""
def test_readUrl(self):
"""util._readUrl()"""
# for additional tests see test_parse.py
url = 'http://example.com/test.css'
def make_fetcher(r):
# normally r == encoding, content
def fetcher(url):
return r
return fetcher
tests = {
# defaultFetcher returns: readUrl returns
None: (None, None, None),
(None, ''): ('utf-8', 5, ''),
(None, '€'.encode()): ('utf-8', 5, '€'),
('utf-8', '€'.encode()): ('utf-8', 1, '€'),
('ISO-8859-1', 'ä'.encode('iso-8859-1')): ('ISO-8859-1', 1, 'ä'),
('ASCII', b'a'): ('ASCII', 1, 'a'),
}
for r, exp in list(tests.items()):
assert _readUrl(url, fetcher=make_fetcher(r)) == exp
tests = {
# (overrideEncoding, parentEncoding, (httpencoding, content)):
# readUrl returns
# ===== 0. OVERRIDE WINS =====
# override + parent + http
('latin1', 'ascii', ('utf-16', b'')): ('latin1', 0, ''),
('latin1', 'ascii', ('utf-16', b'123')): ('latin1', 0, '123'),
('latin1', 'ascii', ('utf-16', 'ä'.encode('iso-8859-1'))): (
'latin1',
0,
'ä',
),
('latin1', 'ascii', ('utf-16', b'a')): ('latin1', 0, 'a'),
# + @charset
('latin1', 'ascii', ('utf-16', b'@charset "ascii";')): (
'latin1',
0,
'@charset "latin1";',
),
('latin1', 'ascii', ('utf-16', '@charset "utf-8";ä'.encode('latin1'))): (
'latin1',
0,
'@charset "latin1";ä',
),
('latin1', 'ascii', ('utf-16', '@charset "utf-8";ä'.encode())): (
'latin1',
0,
'@charset "latin1";\xc3\xa4',
), # read as latin1!
# override only
('latin1', None, None): (None, None, None),
('latin1', None, (None, b'')): ('latin1', 0, ''),
('latin1', None, (None, b'123')): ('latin1', 0, '123'),
('latin1', None, (None, 'ä'.encode('iso-8859-1'))): ('latin1', 0, 'ä'),
('latin1', None, (None, b'a')): ('latin1', 0, 'a'),
# + @charset
('latin1', None, (None, b'@charset "ascii";')): (
'latin1',
0,
'@charset "latin1";',
),
('latin1', None, (None, '@charset "utf-8";ä'.encode('latin1'))): (
'latin1',
0,
'@charset "latin1";ä',
),
('latin1', None, (None, '@charset "utf-8";ä'.encode())): (
'latin1',
0,
'@charset "latin1";\xc3\xa4',
), # read as latin1!
# override + parent
('latin1', 'ascii', None): (None, None, None),
('latin1', 'ascii', (None, b'')): ('latin1', 0, ''),
('latin1', 'ascii', (None, b'123')): ('latin1', 0, '123'),
('latin1', 'ascii', (None, 'ä'.encode('iso-8859-1'))): ('latin1', 0, 'ä'),
('latin1', 'ascii', (None, b'a')): ('latin1', 0, 'a'),
# + @charset
('latin1', 'ascii', (None, b'@charset "ascii";')): (
'latin1',
0,
'@charset "latin1";',
),
('latin1', 'ascii', (None, '@charset "utf-8";ä'.encode('latin1'))): (
'latin1',
0,
'@charset "latin1";ä',
),
('latin1', 'ascii', (None, '@charset "utf-8";ä'.encode())): (
'latin1',
0,
'@charset "latin1";\xc3\xa4',
), # read as latin1!
# override + http
('latin1', None, ('utf-16', b'')): ('latin1', 0, ''),
('latin1', None, ('utf-16', b'123')): ('latin1', 0, '123'),
('latin1', None, ('utf-16', 'ä'.encode('iso-8859-1'))): ('latin1', 0, 'ä'),
('latin1', None, ('utf-16', b'a')): ('latin1', 0, 'a'),
# + @charset
('latin1', None, ('utf-16', b'@charset "ascii";')): (
'latin1',
0,
'@charset "latin1";',
),
('latin1', None, ('utf-16', '@charset "utf-8";ä'.encode('latin1'))): (
'latin1',
0,
'@charset "latin1";ä',
),
('latin1', None, ('utf-16', '@charset "utf-8";ä'.encode())): (
'latin1',
0,
'@charset "latin1";\xc3\xa4',
), # read as latin1!
# ===== 1. HTTP WINS =====
(None, 'ascii', ('latin1', b'')): ('latin1', 1, ''),
(None, 'ascii', ('latin1', b'123')): ('latin1', 1, '123'),
(None, 'ascii', ('latin1', 'ä'.encode('iso-8859-1'))): ('latin1', 1, 'ä'),
(None, 'ascii', ('latin1', b'a')): ('latin1', 1, 'a'),
# + @charset
(None, 'ascii', ('latin1', b'@charset "ascii";')): (
'latin1',
1,
'@charset "latin1";',
),
(None, 'ascii', ('latin1', '@charset "utf-8";ä'.encode('latin1'))): (
'latin1',
1,
'@charset "latin1";ä',
),
(None, 'ascii', ('latin1', '@charset "utf-8";ä'.encode())): (
'latin1',
1,
'@charset "latin1";\xc3\xa4',
), # read as latin1!
# ===== 2. @charset WINS =====
(None, 'ascii', (None, b'@charset "latin1";')): (
'latin1',
2,
'@charset "latin1";',
),
(None, 'ascii', (None, '@charset "latin1";ä'.encode('latin1'))): (
'latin1',
2,
'@charset "latin1";ä',
),
(None, 'ascii', (None, '@charset "latin1";ä'.encode())): (
'latin1',
2,
'@charset "latin1";\xc3\xa4',
), # read as latin1!
# ===== 2. BOM WINS =====
(None, 'ascii', (None, 'ä'.encode('utf-8-sig'))): (
'utf-8-sig',
2,
'\xe4',
), # read as latin1!
(None, 'ascii', (None, '@charset "utf-8";ä'.encode('utf-8-sig'))): (
'utf-8-sig',
2,
'@charset "utf-8";\xe4',
), # read as latin1!
(None, 'ascii', (None, '@charset "latin1";ä'.encode('utf-8-sig'))): (
'utf-8-sig',
2,
'@charset "utf-8";\xe4',
), # read as latin1!
# ===== 4. parentEncoding WINS =====
(None, 'latin1', (None, b'')): ('latin1', 4, ''),
(None, 'latin1', (None, b'123')): ('latin1', 4, '123'),
(None, 'latin1', (None, 'ä'.encode('iso-8859-1'))): ('latin1', 4, 'ä'),
(None, 'latin1', (None, b'a')): ('latin1', 4, 'a'),
(None, 'latin1', (None, 'ä'.encode())): (
'latin1',
4,
'\xc3\xa4',
), # read as latin1!
# ===== 5. default WINS which in this case is None! =====
(None, None, (None, b'')): ('utf-8', 5, ''),
(None, None, (None, b'123')): ('utf-8', 5, '123'),
(None, None, (None, b'a')): ('utf-8', 5, 'a'),
(None, None, (None, 'ä'.encode())): (
'utf-8',
5,
'ä',
), # read as utf-8
(
None,
None,
(None, 'ä'.encode('iso-8859-1')),
): ( # trigger UnicodeDecodeError!
'utf-8',
5,
None,
),
}
for (override, parent, r), exp in list(tests.items()):
assert (
_readUrl(
url,
overrideEncoding=override,
parentEncoding=parent,
fetcher=make_fetcher(r),
)
== exp
)
def test_defaultFetcher(self): # noqa: C901
"""util._defaultFetcher"""
class Response:
"""urllib2.Reponse mock"""
def __init__(self, url, contenttype, content, exception=None, args=None):
self.url = url
m = Message()
m['content-type'] = contenttype
self.mimetype = m.get_content_type()
self.charset = m.get_param('charset', None)
self.text = content
self.exception = exception
self.args = args
def geturl(self):
return self.url
def info(self):
mimetype, charset = self.mimetype, self.charset
class Info:
# py2x
def gettype(self):
return mimetype
def getparam(self, name=None):
return charset
# py 3x
get_content_type = gettype
get_content_charset = getparam # here always charset!
return Info()
def read(self):
# returns fake text or raises fake exception
if not self.exception:
return self.text
else:
raise self.exception(*self.args)
def urlopen(url, contenttype=None, content=None, exception=None, args=None):
# return an mock which returns parameterized Response
def x(*ignored):
if exception:
raise exception(*args)
else:
return Response(
url, contenttype, content, exception=exception, args=args
)
return x
urlopenpatch = 'urllib.request.urlopen'
# positive tests
tests = {
# content-type, contentstr: encoding, contentstr
('text/css', '€'.encode()): (None, '€'.encode()),
('text/css;charset=utf-8', '€'.encode()): (
'utf-8',
'€'.encode(),
),
('text/css;charset=ascii', 'a'): ('ascii', 'a'),
}
url = 'http://example.com/test.css'
for (contenttype, content), exp in list(tests.items()):
@mock.patch(urlopenpatch, new=urlopen(url, contenttype, content))
def do(url):
return _defaultFetcher(url)
assert exp == do(url)
# wrong mimetype
@mock.patch(urlopenpatch, new=urlopen(url, 'text/html', 'a'))
def do(url):
return _defaultFetcher(url)
with pytest.raises(ValueError):
do(url)
# calling url results in fake exception
# py2 ~= py3 raises error earlier than urlopen!
tests = {
'1': (ValueError, ['invalid value for url']),
# _readUrl('mailto:a.css')
'mailto:e4': (urllib.error.URLError, ['urlerror']),
# cannot resolve x, IOError
'http://x': (urllib.error.URLError, ['ioerror']),
}
for url, (exception, args) in list(tests.items()):
@mock.patch(urlopenpatch, new=urlopen(url, exception=exception, args=args))
def do(url):
return _defaultFetcher(url)
with pytest.raises(exception):
do(url)
urlrequestpatch = 'urllib.request.Request'
tests = {
# _readUrl('http://cthedot.de/__UNKNOWN__.css')
'e2': (urllib.error.HTTPError, ['u', 500, 'server error', {}, None]),
'e3': (urllib.error.HTTPError, ['u', 404, 'not found', {}, None]),
}
for url, (exception, args) in list(tests.items()):
@mock.patch(
urlrequestpatch, new=urlopen(url, exception=exception, args=args)
)
def do(url):
return _defaultFetcher(url)
with pytest.raises(exception):
do(url)
class TestLazyRegex:
"""Tests for cssutils.util.LazyRegex."""
def setup_method(self):
self.lazyre = LazyRegex('f.o')
def test_public_interface(self):
methods = [
'search',
'match',
'split',
'sub',
'subn',
'findall',
'finditer',
'pattern',
'flags',
'groups',
'groupindex',
]
for method in methods:
assert hasattr(self.lazyre, method), 'expected %r public attribute' % method
def test_ensure(self):
assert self.lazyre.matcher is None
self.lazyre.ensure()
assert self.lazyre.matcher is not None
def test_calling(self):
assert self.lazyre('bar') is None
match = self.lazyre('foobar')
assert match.group() == 'foo'
def test_matching(self):
assert self.lazyre.match('bar') is None
match = self.lazyre.match('foobar')
assert match.group() == 'foo'
def test_matching_with_position_parameters(self):
assert self.lazyre.match('foo', 1) is None
assert self.lazyre.match('foo', 0, 2) is None
def test_searching(self):
assert self.lazyre.search('rafuubar') is None
match = self.lazyre.search('rafoobar')
assert match.group() == 'foo'
def test_searching_with_position_parameters(self):
assert self.lazyre.search('rafoobar', 3) is None
assert self.lazyre.search('rafoobar', 0, 4) is None
match = self.lazyre.search('rafoofuobar', 4)
assert match.group() == 'fuo'
def test_split(self):
assert self.lazyre.split('rafoobarfoobaz') == ['ra', 'bar', 'baz']
assert self.lazyre.split('rafoobarfoobaz', 1) == ['ra', 'barfoobaz']
def test_findall(self):
assert self.lazyre.findall('rafoobarfuobaz') == ['foo', 'fuo']
def test_finditer(self):
result = self.lazyre.finditer('rafoobarfuobaz')
assert [m.group() for m in result] == ['foo', 'fuo']
def test_sub(self):
assert self.lazyre.sub('bar', 'foofoo') == 'barbar'
assert self.lazyre.sub(lambda x: 'baz', 'foofoo') == 'bazbaz'
def test_subn(self):
subbed = self.lazyre.subn('bar', 'foofoo')
assert subbed == ('barbar', 2)
subbed = self.lazyre.subn(lambda x: 'baz', 'foofoo')
assert subbed == ('bazbaz', 2)
def test_groups(self):
lazyre = LazyRegex('(.)(.)')
assert lazyre.groups is None
lazyre.ensure()
assert lazyre.groups == 2
def test_groupindex(self):
lazyre = LazyRegex('(?P<foo>.)')
assert lazyre.groupindex is None
lazyre.ensure()
assert lazyre.groupindex == {'foo': 1}
def test_flags(self):
self.lazyre.ensure()
assert self.lazyre.flags == re.compile('.').flags
def test_pattern(self):
assert self.lazyre.pattern == 'f.o'