| #!/usr/bin/env python3 |
| # Copyright 2022 The Pigweed Authors |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| # use this file except in compliance with the License. You may obtain a copy of |
| # the License at |
| # |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| # License for the specific language governing permissions and limitations under |
| # the License. |
| """Tests for keep_sorted.""" |
| |
| from pathlib import Path |
| import tempfile |
| import textwrap |
| from typing import Dict, Sequence |
| import unittest |
| from unittest.mock import MagicMock |
| |
| from pw_presubmit import keep_sorted |
| |
| # Only include these literals here so keep_sorted doesn't try to reorder later |
| # test lines. |
| START = keep_sorted.START |
| END = keep_sorted.END |
| |
| # pylint: disable=attribute-defined-outside-init |
| # pylint: disable=too-many-public-methods |
| |
| |
| class TestKeepSorted(unittest.TestCase): |
| """Test KeepSorted class""" |
| |
| def _run(self, contents: str) -> None: |
| self.ctx = MagicMock() |
| self.ctx.fail = MagicMock() |
| |
| with tempfile.TemporaryDirectory() as tempdir: |
| path = Path(tempdir) / 'foo' |
| |
| with path.open('w') as outs: |
| outs.write(contents) |
| |
| self.errors: Dict[Path, Sequence[str]] = {} |
| |
| # pylint: disable=protected-access |
| self.sorter = keep_sorted._FileSorter(self.ctx, path, self.errors) |
| |
| # pylint: enable=protected-access |
| |
| self.sorter.sort() |
| |
| # Truncate the file so it's obvious whether write() changed |
| # anything. |
| with path.open('w') as outs: |
| outs.write('') |
| |
| self.sorter.write(path) |
| with path.open() as ins: |
| self.contents = ins.read() |
| |
| def assert_errors(self): |
| self.assertTrue(self.errors) |
| |
| def assert_no_errors(self): |
| self.assertFalse(self.errors) |
| |
| def test_missing_end(self) -> None: |
| with self.assertRaises(keep_sorted.KeepSortedParsingError): |
| self._run(f'{START}\n') |
| |
| def test_missing_start(self) -> None: |
| with self.assertRaises(keep_sorted.KeepSortedParsingError): |
| self._run(f'{END}: end\n') |
| |
| def test_repeated_start(self) -> None: |
| with self.assertRaises(keep_sorted.KeepSortedParsingError): |
| self._run(f'{START}\n{START}\n') |
| |
| def test_unrecognized_directive(self) -> None: |
| with self.assertRaises(keep_sorted.KeepSortedParsingError): |
| self._run(f'{START} foo bar baz\n2\n1\n{END}\n') |
| |
| def test_repeated_valid_directive(self) -> None: |
| with self.assertRaises(keep_sorted.KeepSortedParsingError): |
| self._run(f'{START} ignore-case ignore-case\n2\n1\n{END}\n') |
| |
| def test_already_sorted(self) -> None: |
| self._run(f'{START}\n1\n2\n3\n4\n{END}\n') |
| self.assert_no_errors() |
| self.assertEqual(self.contents, '') |
| |
| def test_not_sorted(self) -> None: |
| self._run(f'{START}\n4\n3\n2\n1\n{END}\n') |
| self.assert_errors() |
| self.assertEqual(self.contents, f'{START}\n1\n2\n3\n4\n{END}\n') |
| |
| def test_prefix_sorted(self) -> None: |
| self._run(f'foo\nbar\n{START}\n1\n2\n{END}\n') |
| self.assert_no_errors() |
| self.assertEqual(self.contents, '') |
| |
| def test_prefix_not_sorted(self) -> None: |
| self._run(f'foo\nbar\n{START}\n2\n1\n{END}\n') |
| self.assert_errors() |
| self.assertEqual(self.contents, f'foo\nbar\n{START}\n1\n2\n{END}\n') |
| |
| def test_suffix_sorted(self) -> None: |
| self._run(f'{START}\n1\n2\n{END}\nfoo\nbar\n') |
| self.assert_no_errors() |
| self.assertEqual(self.contents, '') |
| |
| def test_suffix_not_sorted(self) -> None: |
| self._run(f'{START}\n2\n1\n{END}\nfoo\nbar\n') |
| self.assert_errors() |
| self.assertEqual(self.contents, f'{START}\n1\n2\n{END}\nfoo\nbar\n') |
| |
| def test_not_sorted_case_sensitive(self) -> None: |
| self._run(f'{START}\na\nD\nB\nc\n{END}\n') |
| self.assert_errors() |
| self.assertEqual(self.contents, f'{START}\nB\nD\na\nc\n{END}\n') |
| |
| def test_not_sorted_case_insensitive(self) -> None: |
| self._run(f'{START} ignore-case\na\nD\nB\nc\n{END}\n') |
| self.assert_errors() |
| self.assertEqual( |
| self.contents, f'{START} ignore-case\na\nB\nc\nD\n{END}\n' |
| ) |
| |
| def test_remove_dupes(self) -> None: |
| self._run(f'{START}\n1\n2\n2\n1\n{END}\n') |
| self.assert_errors() |
| self.assertEqual(self.contents, f'{START}\n1\n2\n{END}\n') |
| |
| def test_allow_dupes(self) -> None: |
| self._run(f'{START} allow-dupes\n1\n2\n2\n1\n{END}\n') |
| self.assert_errors() |
| self.assertEqual( |
| self.contents, f'{START} allow-dupes\n1\n1\n2\n2\n{END}\n' |
| ) |
| |
| def test_case_insensitive_dupes(self) -> None: |
| self._run(f'{START} ignore-case\na\nB\nA\n{END}\n') |
| self.assert_errors() |
| self.assertEqual( |
| self.contents, f'{START} ignore-case\nA\na\nB\n{END}\n' |
| ) |
| |
| def test_ignored_prefixes(self) -> None: |
| self._run(f'{START} ignore-prefix=foo,bar\na\nb\nfoob\nbarc\n{END}\n') |
| self.assert_no_errors() |
| |
| def test_ignored_longest_prefixes(self) -> None: |
| self._run(f'{START} ignore-prefix=1,123\na\n123b\nb\n1c\n{END}\n') |
| self.assert_no_errors() |
| |
| def test_ignored_prefixes_whitespace(self) -> None: |
| self._run( |
| f'{START} ignore-prefix=foo,bar\n' f' a\n b\n foob\n barc\n{END}\n' |
| ) |
| self.assert_no_errors() |
| |
| def test_ignored_prefixes_insensitive(self) -> None: |
| self._run( |
| f'{START} ignore-prefix=foo,bar ignore-case\n' |
| f'a\nB\nfooB\nbarc\n{END}\n' |
| ) |
| self.assert_no_errors() |
| |
| def test_python_comment_marks_sorted(self) -> None: |
| self._run(f'# {START}\n1\n2\n# {END}\n') |
| self.assert_no_errors() |
| |
| def test_python_comment_marks_not_sorted(self) -> None: |
| self._run(f'# {START}\n2\n1\n# {END}\n') |
| self.assert_errors() |
| self.assertEqual(self.contents, f'# {START}\n1\n2\n# {END}\n') |
| |
| def test_python_comment_sticky_sorted(self) -> None: |
| self._run(f'# {START}\n# A\n1\n2\n# {END}\n') |
| self.assert_no_errors() |
| |
| def test_python_comment_sticky_not_sorted(self) -> None: |
| self._run(f'# {START}\n2\n# A\n1\n# {END}\n') |
| self.assert_errors() |
| self.assertEqual(self.contents, f'# {START}\n# A\n1\n2\n# {END}\n') |
| |
| def test_python_comment_sticky_disabled(self) -> None: |
| self._run(f'# {START} sticky-comments=no\n1\n# B\n2\n# {END}\n') |
| self.assert_errors() |
| self.assertEqual( |
| self.contents, f'# {START} sticky-comments=no\n# B\n1\n2\n# {END}\n' |
| ) |
| |
| def test_cpp_comment_marks_sorted(self) -> None: |
| self._run(f'// {START}\n1\n2\n// {END}\n') |
| self.assert_no_errors() |
| |
| def test_cpp_comment_marks_not_sorted(self) -> None: |
| self._run(f'// {START}\n2\n1\n// {END}\n') |
| self.assert_errors() |
| self.assertEqual(self.contents, f'// {START}\n1\n2\n// {END}\n') |
| |
| def test_cpp_comment_sticky_sorted(self) -> None: |
| self._run(f'// {START}\n1\n// B\n2\n// {END}\n') |
| self.assert_no_errors() |
| |
| def test_cpp_comment_sticky_not_sorted(self) -> None: |
| self._run(f'// {START}\n// B\n2\n1\n// {END}\n') |
| self.assert_errors() |
| self.assertEqual(self.contents, f'// {START}\n1\n// B\n2\n// {END}\n') |
| |
| def test_cpp_comment_sticky_disabled(self) -> None: |
| self._run(f'// {START} sticky-comments=no\n1\n// B\n2\n// {END}\n') |
| self.assert_errors() |
| self.assertEqual( |
| self.contents, |
| f'// {START} sticky-comments=no\n// B\n1\n2\n// {END}\n', |
| ) |
| |
| def test_custom_comment_sticky_sorted(self) -> None: |
| self._run(f'{START} sticky-comments=%\n1\n% B\n2\n{END}\n') |
| self.assert_no_errors() |
| |
| def test_custom_comment_sticky_not_sorted(self) -> None: |
| self._run(f'{START} sticky-comments=%\n% B\n2\n1\n{END}\n') |
| self.assert_errors() |
| self.assertEqual( |
| self.contents, f'{START} sticky-comments=%\n1\n% B\n2\n{END}\n' |
| ) |
| |
| def test_multiline_comment_sticky_sorted(self) -> None: |
| self._run(f'# {START}\n# B\n# A\n1\n2\n# {END}\n') |
| self.assert_no_errors() |
| |
| def test_multiline_comment_sticky_not_sorted(self) -> None: |
| self._run(f'# {START}\n# B\n# A\n2\n1\n# {END}\n') |
| self.assert_errors() |
| self.assertEqual(self.contents, f'# {START}\n1\n# B\n# A\n2\n# {END}\n') |
| |
| def test_comment_sticky_sorted_fallback_sorted(self) -> None: |
| self._run(f'# {START}\n# A\n1\n# B\n1\n# {END}\n') |
| self.assert_no_errors() |
| |
| def test_comment_sticky_sorted_fallback_not_sorted(self) -> None: |
| self._run(f'# {START}\n# B\n1\n# A\n1\n# {END}\n') |
| self.assert_errors() |
| self.assertEqual(self.contents, f'# {START}\n# A\n1\n# B\n1\n# {END}\n') |
| |
| def test_comment_sticky_sorted_fallback_dupes(self) -> None: |
| self._run(f'# {START} allow-dupes\n# A\n1\n# A\n1\n# {END}\n') |
| self.assert_no_errors() |
| |
| def test_different_comment_sticky_not_sorted(self) -> None: |
| self._run(f'# {START} sticky-comments=%\n% A\n1\n# B\n2\n# {END}\n') |
| self.assert_errors() |
| self.assertEqual( |
| self.contents, |
| f'# {START} sticky-comments=%\n# B\n% A\n1\n2\n# {END}\n', |
| ) |
| |
| def test_continuation_sorted(self) -> None: |
| initial = textwrap.dedent( |
| f""" |
| # {START} |
| baz |
| abc |
| foo |
| bar |
| # {END} |
| """.lstrip( |
| '\n' |
| ) |
| ) |
| |
| self._run(initial) |
| self.assert_no_errors() |
| |
| def test_continuation_not_sorted(self) -> None: |
| initial = textwrap.dedent( |
| f""" |
| # {START} |
| foo |
| bar |
| baz |
| abc |
| # {END} |
| """.lstrip( |
| '\n' |
| ) |
| ) |
| |
| expected = textwrap.dedent( |
| f""" |
| # {START} |
| baz |
| abc |
| foo |
| bar |
| # {END} |
| """.lstrip( |
| '\n' |
| ) |
| ) |
| |
| self._run(initial) |
| self.assert_errors() |
| self.assertEqual(self.contents, expected) |
| |
| def test_indented_continuation_sorted(self) -> None: |
| # Intentionally not using textwrap.dedent(). |
| initial = f""" |
| # {START} |
| baz |
| abc |
| foo |
| bar |
| # {END}""".lstrip( |
| '\n' |
| ) |
| |
| self._run(initial) |
| self.assert_no_errors() |
| |
| def test_indented_continuation_not_sorted(self) -> None: |
| # Intentionally not using textwrap.dedent(). |
| initial = f""" |
| # {START} |
| foo |
| bar |
| baz |
| abc |
| # {END}""".lstrip( |
| '\n' |
| ) |
| |
| expected = f""" |
| # {START} |
| baz |
| abc |
| foo |
| bar |
| # {END}""".lstrip( |
| '\n' |
| ) |
| |
| self._run(initial) |
| self.assert_errors() |
| self.assertEqual(self.contents, expected) |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |