Add test coverage for parsers in tools/lib.

Now, `tools/test-all` calls a new program called `tools/tests-tools`
that runs unit tests in `test_css_parser.py` and 'test_template_parser.py`.

This puts 100% line coverage on tools/lib/css_parser.py.
This puts about 50% line coverage on tools/lib/template_parser.py.
This commit is contained in:
Steve Howell 2016-08-03 15:35:53 -07:00 committed by Tim Abbott
parent 5af47e0eef
commit 7cc1b1ebc4
5 changed files with 251 additions and 0 deletions

View File

@ -15,6 +15,7 @@ function run {
}
run ./tools/clean-repo
run ./tools/test-tools
run ./tools/lint-all
run ./tools/test-migrations
run ./tools/test-js-with-node

47
tools/test-tools Executable file
View File

@ -0,0 +1,47 @@
#!/usr/bin/env python
from __future__ import absolute_import
from __future__ import print_function
import optparse
import os
import sys
import unittest
if __name__ == '__main__':
parser = optparse.OptionParser()
parser.add_option('--coverage', dest='coverage',
action="store_true",
default=False, help='Compute test coverage.')
(options, _) = parser.parse_args()
def dir_join(dir1, dir2):
# type: (str, str) -> str
return os.path.abspath(os.path.join(dir1, dir2))
tools_dir = os.path.dirname(os.path.abspath(__file__))
root_dir = dir_join(tools_dir, '..')
tools_test_dir = dir_join(tools_dir, 'tests')
sys.path.insert(0, root_dir)
loader = unittest.TestLoader() # type: ignore # https://github.com/python/typeshed/issues/372
if options.coverage:
import coverage
cov = coverage.Coverage(omit="*/zulip-venv-cache/*")
cov.start()
suite = loader.discover(start_dir=tools_test_dir, top_level_dir=root_dir)
runner = unittest.TextTestRunner(verbosity=2)
result = runner.run(suite) # type: ignore # https://github.com/python/typeshed/issues/372
if result.errors or result.failures:
raise Exception('Test failed!')
if options.coverage:
cov.stop()
cov.save()
cov.html_report(directory='var/tools_coverage')
print("HTML report saved to var/tools_coverage")
print('SUCCESS')

0
tools/tests/__init__.py Normal file
View File

View File

@ -0,0 +1,169 @@
from __future__ import absolute_import
from __future__ import print_function
from typing import cast
import sys
import unittest
try:
from tools.lib.css_parser import (
CssParserException,
CssSection,
parse,
)
except ImportError:
print('ERROR!!! You need to run this via tools/test-tools.')
sys.exit(1)
class ParserTestHappyPath(unittest.TestCase):
def test_basic_parse(self):
# type: () -> None
my_selector = 'li.foo'
my_block = '''{
color: red;
}'''
my_css = my_selector + ' ' + my_block
res = parse(my_css)
self.assertEqual(res.text(), my_css)
section = cast(CssSection, res.sections[0])
block = section.declaration_block
self.assertEqual(block.text().strip(), my_block)
declaration = block.declarations[0]
self.assertEqual(declaration.css_property, 'color')
self.assertEqual(declaration.css_value.text().strip(), 'red')
def test_same_line_comment(self):
# type: () -> None
my_css = '''
li.hide {
display: none; /* comment here */
/* Not to be confused
with this comment */
color: green;
}'''
res = parse(my_css)
section = cast(CssSection, res.sections[0])
block = section.declaration_block
declaration = block.declarations[0]
self.assertIn('/* comment here */', declaration.text())
def test_multi_line_selector(self):
# type: () -> None
my_css = '''
h1,
h2,
h3 {
top: 0
}'''
res = parse(my_css)
section = res.sections[0]
selectors = section.selector_list.selectors
self.assertEqual(len(selectors), 3)
def test_comment_at_end(self):
# type: () -> None
'''
This test verifies the current behavior, which is to
attach comments to the preceding rule, but we should
probably change it so the comments gets attached to
the next block, if possible.
'''
my_css = '''
p {
color: black;
}
/* comment at the end of the text */
'''
res = parse(my_css)
self.assertEqual(len(res.sections), 1)
section = res.sections[0]
self.assertIn('comment at the end', section.post_fluff)
def test_media_block(self):
# type: () -> None
my_css = '''
@media (max-width: 300px) {
h5 {
margin: 0;
}
}'''
res = parse(my_css)
self.assertEqual(len(res.sections), 1)
self.assertEqual(res.text(), my_css)
class ParserTestSadPath(unittest.TestCase):
'''
Use this class for tests that verify the parser will
appropriately choke on malformed CSS.
We prevent some things that are technically legal
in CSS, like having comments in the middle of list
of selectors. Some of this is just for expediency;
some of this is to enforce consistent formatting.
'''
def _assert_error(self, my_css, error):
# See https://github.com/python/typeshed/issues/372
# for why we have to ingore types here.
with self.assertRaisesRegexp(CssParserException, error): # type: ignore
parse(my_css)
def test_unexpected_end_brace(self):
# type: () -> None
my_css = '''
@media (max-width: 975px) {
body {
color: red;
}
}} /* whoops */'''
error = 'unexpected }'
self._assert_error(my_css, error)
def test_empty_section(self):
# type: () -> None
my_css = '''
/* nothing to see here, move along */
'''
error = 'unexpected empty section'
self._assert_error(my_css, error)
def test_missing_colon(self):
# type: () -> None
my_css = '''
.hide
{
display none /* no colon here */
}'''
error = 'We expect a colon here'
self._assert_error(my_css, error)
def test_unclosed_comment(self):
# type: () -> None
my_css = ''' /* comment with no end'''
error = 'unclosed comment'
self._assert_error(my_css, error)
def test_missing_selectors(self):
# type: () -> None
my_css = '''
/* no selectors here */
{
bottom: 0
}'''
error = 'Missing selector'
self._assert_error(my_css, error)
def test_disallow_comments_in_selectors(self):
# type: () -> None
my_css = '''
h1,
h2, /* comment here not allowed by Zulip */
h3 {
top: 0
}'''
error = 'Comments in selector section are not allowed'
self._assert_error(my_css, error)

View File

@ -0,0 +1,34 @@
from __future__ import absolute_import
from __future__ import print_function
import sys
import unittest
try:
from tools.lib.template_parser import (
is_django_block_tag,
validate,
)
except ImportError:
print('ERROR!!! You need to run this via tools/test-tools.')
sys.exit(1)
class ParserTest(unittest.TestCase):
def test_is_django_block_tag(self):
# type: () -> None
self.assertTrue(is_django_block_tag('block'))
self.assertFalse(is_django_block_tag('not a django tag'))
def test_validate_vanilla_html(self):
# type: () -> None
'''
Verify that validate() does not raise errors for
well-formed HTML.
'''
my_html = '''
<table>
<tr>
<td>foo</td>
</tr>
</table>'''
validate(text=my_html)