blob: 23062e86503699fc14ab05bc438ef497cd445196 [file] [log] [blame]
#!/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 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()