""" Tests for main.py """ import json import re from io import StringIO from unittest import TestCase from unittest import mock from xsslint.linters import JavaScriptLinter, MakoTemplateLinter, PythonLinter, UnderscoreTemplateLinter from xsslint.main import _build_ruleset, _lint from xsslint.reporting import SummaryResults class TestXSSLinter(TestCase): """ Test some top-level linter functions """ def setUp(self): """ Setup patches on linters for testing. """ self.patch_is_valid_directory(MakoTemplateLinter) self.patch_is_valid_directory(JavaScriptLinter) self.patch_is_valid_directory(UnderscoreTemplateLinter) self.patch_is_valid_directory(PythonLinter) patcher = mock.patch('xsslint.main.is_skip_dir', return_value=False) patcher.start() self.addCleanup(patcher.stop) self.out = StringIO() self.template_linters = self._build_linters() self.ruleset = _build_ruleset(self.template_linters) self.summary_results = SummaryResults(self.ruleset) def patch_is_valid_directory(self, linter_class): """ Creates a mock patch for _is_valid_directory on a Linter to always return true. This avoids nested patch calls. Arguments: linter_class: The linter class to be patched """ patcher = mock.patch.object(linter_class, '_is_valid_directory', return_value=True) patch_start = patcher.start() self.addCleanup(patcher.stop) return patch_start def _build_linters(self): underscore_linter = UnderscoreTemplateLinter() python_linter = PythonLinter() javascript_linter = JavaScriptLinter(underscore_linter=underscore_linter) mako_linter = MakoTemplateLinter(javascript_linter=javascript_linter, python_linter=python_linter) return [mako_linter, underscore_linter, javascript_linter, python_linter] def test_lint_defaults(self): """ Tests the top-level linting with default options. """ _lint( 'scripts/xsslint/tests/templates', template_linters=self.template_linters, options={ 'list_files': False, 'verbose': False, 'rule_totals': False, 'summary_format': 'eslint', 'skip_dirs': () }, summary_results=self.summary_results, out=self.out, ) output = self.out.getvalue() # Assert violation details are displayed. assert re.search(f'test\\.html.*{self.ruleset.mako_missing_default.rule_id}', output) is not None assert re.search(f'test\\.js.*{self.ruleset.javascript_concat_html.rule_id}', output) is not None assert re.search(f'test\\.js.*{self.ruleset.underscore_not_escaped.rule_id}', output) is not None lines_with_rule = 0 lines_without_rule = 0 # Output with verbose setting only. for underscore_match in re.finditer(r'test\.underscore:.*\n', output): if re.search(self.ruleset.underscore_not_escaped.rule_id, underscore_match.group()) is not None: lines_with_rule += 1 else: lines_without_rule += 1 assert lines_with_rule >= 1 assert lines_without_rule == 0 assert re.search(f'test\\.py.*{self.ruleset.python_parse_error.rule_id}', output) is None assert re.search(f'test\\.py.*{self.ruleset.python_wrap_html.rule_id}', output) is not None # Assert no rule totals. assert re.search('{}:\\s*{} violations'.format(self.ruleset.python_parse_error.rule_id, 0), output) is None # Assert final total assert re.search('{} violations total'.format(5), output) is not None def test_lint_with_verbose(self): """ Tests the top-level linting with verbose option. """ _lint( 'scripts/xsslint/tests/templates', template_linters=self.template_linters, options={ 'list_files': False, 'verbose': True, 'rule_totals': False, 'summary_format': 'eslint', 'skip_dirs': () }, summary_results=self.summary_results, out=self.out, ) output = self.out.getvalue() lines_with_rule = 0 lines_without_rule = 0 # Output with verbose setting only. for underscore_match in re.finditer(r'test\.underscore:.*\n', output): if re.search(self.ruleset.underscore_not_escaped.rule_id, underscore_match.group()) is not None: lines_with_rule += 1 else: lines_without_rule += 1 assert lines_with_rule >= 1 assert lines_without_rule >= 1 # Assert no rule totals. assert re.search('{}:\\s*{} violations'.format(self.ruleset.python_parse_error.rule_id, 0), output) is None # Assert final total assert re.search('{} violations total'.format(5), output) is not None def test_lint_with_rule_totals(self): """ Tests the top-level linting with rule totals option. """ _lint( 'scripts/xsslint/tests/templates', template_linters=self.template_linters, options={ 'list_files': False, 'verbose': False, 'rule_totals': True, 'summary_format': 'eslint', 'skip_dirs': () }, summary_results=self.summary_results, out=self.out, ) output = self.out.getvalue() assert re.search(f'test\\.py.*{self.ruleset.python_wrap_html.rule_id}', output) is not None # Assert totals output. assert re.search('{}:\\s*{} violations'.format(self.ruleset.python_parse_error.rule_id, 0), output) is not None assert re.search('{}:\\s*{} violations'.format(self.ruleset.python_wrap_html.rule_id, 1), output) is not None assert re.search('{} violations total'.format(5), output) is not None def test_lint_with_json_output(self): """ Tests the top-level linting with JSON summary format. """ _lint( 'scripts/xsslint/tests/templates', template_linters=self.template_linters, options={ 'list_files': False, 'verbose': False, 'rule_totals': True, 'summary_format': 'json', 'skip_dirs': () }, summary_results=self.summary_results, out=self.out, ) output = self.out.getvalue() assert re.search(f'test\\.py.*{self.ruleset.python_wrap_html.rule_id}', output) is not None # Find something that looks like pretty-printed JSON json_match = re.search(r'\n\{.*\n\}', output, re.DOTALL) assert json_match is not None data = json.loads(json_match.group()) # Check for rule counts (including zero-instance ones) and total assert 1 == data['rules']['javascript-concat-html'] assert 0 == data['rules']['python-concat-html'] assert 5 == data['total'] def test_lint_with_list_files(self): """ Tests the top-level linting with list files option. """ _lint( 'scripts/xsslint/tests/templates', template_linters=self.template_linters, options={ 'list_files': True, 'verbose': False, 'rule_totals': False, 'summary_format': 'eslint', 'skip_dirs': () }, summary_results=self.summary_results, out=self.out, ) output = self.out.getvalue() # Assert file with rule is not output. assert re.search(f'test\\.py.*{self.ruleset.python_wrap_html.rule_id}', output) is None # Assert file is output. assert re.search('test\\.py', output) is not None # Assert no totals. assert re.search('{}:\\s*{} violations'.format(self.ruleset.python_parse_error.rule_id, 0), output) is None assert re.search('{} violations total'.format(7), output) is None