Пример #1
0
    def run(self, filename, file, file_naming_convention: str="snake"):
        """
        Checks whether the filename follows a certain naming-convention.

        :param file_naming_convention:
            The naming-convention. Supported values are:
            - ``camel`` (``thisIsCamelCase``)
            - ``pascal`` (``ThisIsPascalCase``)
            - ``snake`` (``this_is_snake_case``)
        """
        head, tail = os.path.split(filename)
        filename_without_extension, extension = os.path.splitext(tail)
        try:
            new_name = self._naming_convention[file_naming_convention](
                filename_without_extension)
        except KeyError:
            self.err("Invalid file-naming-convention provided: " +
                     file_naming_convention)
            return

        if new_name != filename_without_extension:
            diff = Diff(file, rename=os.path.join(head, new_name + extension))

            yield Result(
                self,
                "Filename does not follow {} naming-convention.".format(
                    file_naming_convention),
                diff.affected_code(filename),
                diffs={filename: diff})
Пример #2
0
    def run(self, filename, file, file_naming_convention: str = "snake"):
        """
        Checks whether the filename follows a certain naming-convention.

        :param file_naming_convention:
            The naming-convention. Supported values are:
            - ``camel`` (``thisIsCamelCase``)
            - ``pascal`` (``ThisIsPascalCase``)
            - ``snake`` (``this_is_snake_case``)
        """
        head, tail = os.path.split(filename)
        filename_without_extension, extension = os.path.splitext(tail)
        try:
            new_name = self._naming_convention[file_naming_convention](
                filename_without_extension)
        except KeyError:
            self.err("Invalid file-naming-convention provided: " +
                     file_naming_convention)
            return

        if new_name != filename_without_extension:
            diff = Diff(file, rename=os.path.join(head, new_name + extension))

            yield Result(
                self,
                "Filename does not follow {} naming-convention.".format(
                    file_naming_convention),
                diff.affected_code(filename),
                diffs={filename: diff})
Пример #3
0
    def run(self,
            filename,
            file,
            file_naming_convention: str = None,
            ignore_uppercase_filenames: bool = True):
        """
        Checks whether the filename follows a certain naming-convention.

        :param file_naming_convention:
            The naming-convention. Supported values are:
            - ``auto`` to guess the correct convention. Defaults to ``snake``
            if the correct convention cannot be guessed.
            - ``camel`` (``thisIsCamelCase``)
            - ``kebab`` (``this-is-kebab-case``)
            - ``pascal`` (``ThisIsPascalCase``)
            - ``snake`` (``this_is_snake_case``)
            - ``space`` (``This Is Space Case``)
        :param ignore_uppercase_filenames:
            Whether or not to ignore fully uppercase filenames completely,
            e.g. COPYING, LICENSE etc.
        """
        head, tail = os.path.split(filename)
        filename_without_extension, extension = os.path.splitext(tail)

        if file_naming_convention is None:
            self.warn('Please specify a file naming convention explicitly'
                      ' or use "auto".')
            file_naming_convention = 'auto'
        else:
            file_naming_convention = file_naming_convention.lower()

        if file_naming_convention == 'auto':
            if extension in self._language_naming_convention:
                file_naming_convention = self._language_naming_convention[
                    extension]
            else:
                self.warn('The file naming convention could not be guessed. '
                          'Using the default "snake" naming convention.')
                file_naming_convention = 'snake'

        try:
            new_name = self._naming_convention[file_naming_convention](
                filename_without_extension)
        except KeyError:
            self.err('Invalid file-naming-convention provided: ' +
                     file_naming_convention)
            return

        if ignore_uppercase_filenames and filename_without_extension.isupper():
            return

        if new_name != filename_without_extension:
            diff = Diff(file, rename=os.path.join(head, new_name + extension))

            yield Result(
                self,
                'Filename does not follow {} naming-convention.'.format(
                    file_naming_convention),
                diff.affected_code(filename),
                diffs={filename: diff})
Пример #4
0
    def run(self, filename, file,
            file_naming_convention: str=None,
            ignore_uppercase_filenames: bool=True):
        """
        Checks whether the filename follows a certain naming-convention.

        :param file_naming_convention:
            The naming-convention. Supported values are:
            - ``auto`` to guess the correct convention. Defaults to ``snake``
            if the correct convention cannot be guessed.
            - ``camel`` (``thisIsCamelCase``)
            - ``kebab`` (``this-is-kebab-case``)
            - ``pascal`` (``ThisIsPascalCase``)
            - ``snake`` (``this_is_snake_case``)
            - ``space`` (``This Is Space Case``)
        :param ignore_uppercase_filenames:
            Whether or not to ignore fully uppercase filenames completely,
            e.g. COPYING, LICENSE etc.
        """
        head, tail = os.path.split(filename)
        filename_without_extension, extension = os.path.splitext(tail)

        if file_naming_convention is None:
            self.warn('Please specify a file naming convention explicitly'
                      ' or use "auto".')
            file_naming_convention = 'auto'
        else:
            file_naming_convention = file_naming_convention.lower()

        if file_naming_convention == 'auto':
            if extension in self._language_naming_convention:
                file_naming_convention = self._language_naming_convention[
                    extension]
            else:
                self.warn('The file naming convention could not be guessed. '
                          'Using the default "snake" naming convention.')
                file_naming_convention = 'snake'

        try:
            new_name = self._naming_convention[file_naming_convention](
                filename_without_extension)
        except KeyError:
            self.err('Invalid file-naming-convention provided: ' +
                     file_naming_convention)
            return

        if ignore_uppercase_filenames and filename_without_extension.isupper():
            return

        if new_name != filename_without_extension:
            diff = Diff(file, rename=os.path.join(head, new_name + extension))

            yield Result(
                self,
                'Filename does not follow {} naming-convention.'.format(
                    file_naming_convention),
                diff.affected_code(filename),
                diffs={filename: diff})
Пример #5
0
    def correct_single_line_str(
        self,
        filename,
        file,
        sourcerange,
        preferred_quotation,
        force_preferred_quotation: bool = False,
    ):
        """
        Corrects a given single line string assuming it does not use the
        preferred quotation. If the preferred quotation mark is used inside the
        string, no correction will be made.

        This function will yield one or no Result objects.

        :param filename:
            The filename of the file to correct the line in.
        :param file:
            The file contents as list of lines.
        :param sourcerange:
            The sourcerange indicating where to find the string.
        :param preferred_quotation:
            ``'`` or ``"`` respectively.
        :param force_preferred_quotation:
            Decide whether the preferred quotes are compulsory
            to adhere or not.
        """
        str_contents = file[
            sourcerange.start.line -
            1][sourcerange.start.column:sourcerange.end.column - 1]

        if (preferred_quotation in str_contents
                and not force_preferred_quotation):
            return

        # Escape preferred quotes if present.
        str_contents = str_contents.replace(preferred_quotation,
                                            '\\' + preferred_quotation)

        before = file[sourcerange.start.line - 1][:sourcerange.start.column -
                                                  1]
        after = file[sourcerange.end.line - 1][sourcerange.end.column:]

        replacement = (before + preferred_quotation + str_contents +
                       preferred_quotation + after)

        diff = Diff(file)
        diff.change_line(sourcerange.start.line,
                         file[sourcerange.start.line - 1], replacement)
        yield Result(self, ('You do not use the preferred quotation marks.'
                            ' Preferred mark: {}'.format(preferred_quotation)),
                     diff.affected_code(filename),
                     diffs={filename: diff})
Пример #6
0
    def run(self,
            filename,
            file,
            file_naming_convention: str = 'snake',
            ignore_uppercase_filenames: bool = True):
        """
        Checks whether the filename follows a certain naming-convention.

        :param file_naming_convention:
            The naming-convention. Supported values are:
            - ``camel`` (``thisIsCamelCase``)
            - ``pascal`` (``ThisIsPascalCase``)
            - ``snake`` (``this_is_snake_case``)
            - ``space`` (``This Is Space Case``)
        :param ignore_uppercase_filenames:
            Whether or not to ignore fully uppercase filenames completely,
            e.g. COPYING, LICENSE etc.
        """
        head, tail = os.path.split(filename)
        filename_without_extension, extension = os.path.splitext(tail)
        try:
            new_name = self._naming_convention[file_naming_convention](
                filename_without_extension)
        except KeyError:
            self.err('Invalid file-naming-convention provided: ' +
                     file_naming_convention)
            return

        if ignore_uppercase_filenames and filename_without_extension.isupper():
            return

        if new_name != filename_without_extension:
            diff = Diff(file, rename=os.path.join(head, new_name + extension))

            yield Result(
                self,
                'Filename does not follow {} naming-convention.'.format(
                    file_naming_convention),
                diff.affected_code(filename),
                diffs={filename: diff})
Пример #7
0
    def correct_single_line_str(self, filename, file, sourcerange,
                                preferred_quotation):
        """
        Corrects a given single line string assuming it does not use the
        preferred quotation. If the preferred quotation mark is used inside the
        string, no correction will be made.

        This function will yield one or no Result objects.

        :param filename:
            The filename of the file to correct the line in.
        :param file:
            The file contents as list of lines.
        :param sourcerange:
            The sourcerange indicating where to find the string.
        :param preferred_quotation:
            ``'`` or ``"`` respectively.
        """
        str_contents = file[
            sourcerange.start.line -
            1][sourcerange.start.column:sourcerange.end.column - 1]

        if preferred_quotation in str_contents:
            return

        before = file[sourcerange.start.line - 1][:sourcerange.start.column -
                                                  1]
        after = file[sourcerange.end.line - 1][sourcerange.end.column:]

        replacement = (before + preferred_quotation + str_contents +
                       preferred_quotation + after)

        diff = Diff(file)
        diff.change_line(sourcerange.start.line,
                         file[sourcerange.start.line - 1], replacement)
        yield Result(self,
                     'You do not use the preferred quotation marks.',
                     diff.affected_code(filename),
                     diffs={filename: diff})
Пример #8
0
    def run(self, filename, file,
            file_naming_convention: str="snake",
            ignore_uppercase_filenames: bool=True):
        """
        Checks whether the filename follows a certain naming-convention.

        :param file_naming_convention:
            The naming-convention. Supported values are:
            - ``camel`` (``thisIsCamelCase``)
            - ``pascal`` (``ThisIsPascalCase``)
            - ``snake`` (``this_is_snake_case``)
            - ``space`` (``This Is Space Case``)
        :param ignore_uppercase_filenames:
            Whether or not to ignore fully uppercase filenames completely,
            e.g. COPYING, LICENSE etc.
        """
        head, tail = os.path.split(filename)
        filename_without_extension, extension = os.path.splitext(tail)
        try:
            new_name = self._naming_convention[file_naming_convention](
                filename_without_extension)
        except KeyError:
            self.err("Invalid file-naming-convention provided: " +
                     file_naming_convention)
            return

        if ignore_uppercase_filenames and filename_without_extension.isupper():
            return

        if new_name != filename_without_extension:
            diff = Diff(file, rename=os.path.join(head, new_name + extension))

            yield Result(
                self,
                "Filename does not follow {} naming-convention.".format(
                    file_naming_convention),
                diff.affected_code(filename),
                diffs={filename: diff})
Пример #9
0
class DiffTest(unittest.TestCase):

    def setUp(self):
        self.file = ['1', '2', '3', '4']
        self.uut = Diff(self.file)

    def test_add_lines(self):
        self.uut.add_lines(0, [])
        self.uut.add_lines(0, ['t'])
        self.uut.add_lines(0, [])

    def test_add_line(self):
        self.uut.add_line(0, 't')
        self.assertRaises(ConflictError, self.uut.add_line, 0, 't')
        self.assertEqual(self.uut.modified, ['t\n', '1\n', '2\n', '3\n', '4'])

    def test_double_addition(self):
        self.uut.add_lines(0, ['t'])

        # No double addition allowed
        self.assertRaises(ConflictError, self.uut.add_lines, 0, ['t'])
        self.assertRaises(IndexError, self.uut.add_lines, -1, ['t'])
        self.assertRaises(TypeError, self.uut.add_lines, 'str', ['t'])

    def test_delete_line(self):
        self.uut.delete_line(1)
        self.uut.delete_line(1)  # Double deletion possible without conflict
        additions, deletions = self.uut.stats()
        self.assertEqual(deletions, 1)
        self.assertRaises(IndexError, self.uut.delete_line, 0)
        self.assertRaises(IndexError, self.uut.delete_line, 10)

    def test_delete_lines(self):
        self.uut.delete_lines(1, 2)
        self.uut.delete_lines(2, 3)
        additions, deletions = self.uut.stats()
        self.assertEqual(deletions, 3)
        self.assertRaises(IndexError, self.uut.delete_lines, 0, 2)
        self.assertRaises(IndexError, self.uut.delete_lines, 1, 6)

    def test_change_line(self):
        self.assertEqual(len(self.uut), 0)
        self.uut.change_line(2, '1', '2')
        self.assertEqual(len(self.uut), 2)
        self.assertRaises(ConflictError, self.uut.change_line, 2, '1', '3')
        self.assertRaises(IndexError, self.uut.change_line, 0, '1', '2')

        self.uut.delete_line(1)
        # Line was deleted, unchangeable
        self.assertRaises(ConflictError, self.uut.change_line, 1, '1', '2')

    def test_capture_warnings(self):
        """
        Since this addresses the deprecated method, this testcase is
        temporary (until the old API is fully removed).
        """
        logger = logging.getLogger()
        with self.assertLogs(logger, 'DEBUG') as log:
            self.assertEqual(len(self.uut), 0)
            self.uut.change_line(2, '1', '2')
        self.assertEqual(log.output, [
            'DEBUG:root:Use of change_line method is deprecated. Instead '
            'use modify_line method, without the original_line argument'])

    def test_double_changes_with_same_diff(self):
        self.uut.modify_line(2, '2')

        # Double addition when diff is equal is allowed
        try:
            self.uut.modify_line(2, '2')
        except Exception:
            self.fail('We should not have a conflict on same diff!')

    def test_affected_code(self):
        self.assertEqual(self.uut.affected_code('file'), [])

        self.uut.add_lines(0, ['test'])
        affected_code = [
            SourceRange.from_values('file', start_line=1)]
        self.assertEqual(self.uut.affected_code('file'), affected_code)

        self.uut.delete_line(2)
        affected_code = [
            SourceRange.from_values('file', start_line=1),
            SourceRange.from_values('file', start_line=2)]
        self.assertEqual(self.uut.affected_code('file'), affected_code)

        self.uut.delete_line(3)
        affected_code = [
            SourceRange.from_values('file', start_line=1),
            SourceRange.from_values('file', start_line=2, end_line=3)]
        self.assertEqual(self.uut.affected_code('file'), affected_code)

        self.uut.delete_line(4)
        affected_code = [
            SourceRange.from_values('file', start_line=1),
            SourceRange.from_values('file', start_line=2, end_line=4)]
        self.assertEqual(self.uut.affected_code('file'), affected_code)

    def test_len(self):
        self.uut.delete_line(2)
        self.assertEqual(len(self.uut), 1)
        self.uut.add_lines(2, ['2.3', '2.5', '2.6'])
        self.assertEqual(len(self.uut), 4)
        self.uut.modify_line(1, '1.1')
        self.assertEqual(len(self.uut), 6)

    def test_stats(self):
        self.uut.delete_line(2)
        self.assertEqual(self.uut.stats(), (0, 1))
        self.uut.add_lines(2, ['2.3', '2.5', '2.6'])
        self.assertEqual(self.uut.stats(), (3, 1))
        self.uut.modify_line(1, '1.1')
        self.assertEqual(self.uut.stats(), (4, 2))

    def test_modified(self):
        result_file = ['0.1\n',
                       '0.2\n',
                       '1\n',
                       '1.1\n',
                       '3.changed\n',
                       '4']

        self.uut.delete_line(2)
        self.uut.add_lines(0, ['0.1', '0.2'])
        self.uut.add_lines(1, ['1.1'])
        self.uut.modify_line(3, '3.changed')

        self.assertEqual(self.uut.modified, result_file)

        self.uut.delete_line(len(self.file))
        del result_file[-1]
        result_file[-1] = result_file[-1].rstrip('\n')
        self.assertEqual(self.uut.modified, result_file)

        self.uut.delete_line(1)
        del result_file[2]
        self.assertEqual(self.uut.modified, result_file)

    def test_bool(self):
        self.assertFalse(self.uut)
        self.uut.add_line(4, '4')
        self.assertTrue(self.uut)
        self.uut.delete_line(4)
        self.assertFalse(self.uut)
        self.uut.modify_line(1, '1\n')
        self.assertFalse(self.uut)

        # test if it works with tuples.
        uutuple = Diff(('1', '2', '3', '4'))

        self.assertFalse(uutuple)
        uutuple.add_line(4, '4')
        self.assertTrue(uutuple)
        uutuple.delete_line(4)
        self.assertFalse(uutuple)
        uutuple.modify_line(1, '1\n')
        self.assertFalse(uutuple)

    def test_addition(self):
        self.assertRaises(TypeError, self.uut.__add__, 5)

        result_file = ['1\n',
                       '2\n',
                       '2']

        other = Diff(self.file)
        other.delete_line(1)
        other.modify_line(2, '2')
        other.add_lines(0, ['1'])

        self.uut.delete_line(1)
        self.uut.delete_line(3)
        self.uut.modify_line(4, '2')
        result = self.uut + other

        self.assertEqual(result.modified, result_file)
        # Make sure it didn't happen in place!
        self.assertNotEqual(self.uut.modified, result_file)

    def test_addition_rename(self):
        uut = Diff(self.file, rename=False)
        other = Diff(self.file, rename=False)
        self.assertEqual((other + uut).rename, False)

        other.rename = 'some.py'
        self.assertEqual((other + uut).rename, 'some.py')

        uut.rename = 'some.py'
        self.assertEqual((other + uut).rename, 'some.py')

        uut.rename = 'other.py'
        self.assertRaises(ConflictError, other.__add__, uut)

    def test_from_string_arrays(self):
        a = ['q\n', 'a\n', 'b\n', 'x\n', 'c\n', 'd\n']
        b = ['a\n', 'b\n', 'y\n', 'c\n', 'd\n', 'f\n']
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

        a = ['first\n', 'fourth\n']
        b = ['first\n', 'second\n', 'third\n', 'fourth\n']
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

        a = ['first\n', 'fourth\n']
        b = ['first_changed\n', 'second\n', 'third\n', 'fourth\n']
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

        a = ['first\n', 'second\n', 'third\n', 'fourth\n']
        b = ['first\n', 'fourth\n']
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

        a = ['first\n', 'second\n', 'third\n', 'fourth\n']
        b = ['first_changed\n', 'second_changed\n', 'fourth\n']
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

    def test_from_unified_diff_single_addition(self):
        source = ['single line']
        target = ['single line\n', 'another line added']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1 +1,2 @@',
                ' single line',
                '+another line added']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

    def test_from_unified_diff_single_deletion(self):
        source = ['two lines\n', 'to be removed']
        target = ['two lines\n']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,2 +1 @@',
                ' two lines',
                '-to be removed']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

    def test_from_unified_diff_single_modification(self):
        source = ['first\n', 'second']
        target = ['only_first_changed\n', 'second']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,2 +1,2 @@',
                '-first',
                '+only_first_changed',
                ' second']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

    def test_from_unified_diff_multiple_additions_different_orderings(self):
        source = ['A\n', 'B\n', 'C']
        target = ['A\n', 'Y\n', 'Z\n', 'B\n', 'C']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,3 +1,5 @@',
                ' A',
                '+Y',
                '+Z',
                ' B',
                ' C']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

        source = ['A\n', 'B\n', 'C']
        target = ['A\n', 'Y\n', 'Z\n', 'C']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,3 +1,4 @@',
                ' A',
                '+Y',
                '+Z',
                '-B',
                ' C']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

        source = ['A\n', 'B\n', 'C']
        target = ['Y\n', 'Z\n', 'C']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,3 +1,3 @@',
                '-A',
                '+Y',
                '+Z',
                '-B',
                ' C']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

        source = ['A\n', 'B\n', 'C']
        target = ['A\n', 'B\n', 'C\n', 'Y\n', 'Z']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -3 +3,3 @@',
                ' C',
                '+Y',
                '+Z']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

    def test_from_unified_diffrent_beginning_line_types(self):
        source = ['A\n', 'B\n', 'C']
        target = ['A\n', 'Y\n', 'B\n', 'C']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,3 +1,4 @@',
                ' A',
                '+Y',
                ' B',
                ' C']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

        source = ['A\n', 'B\n', 'C']
        target = ['B\n', 'C']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,3 +1,2 @@',
                '-A',
                ' B',
                ' C']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

        source = ['A\n', 'B\n', 'C']
        target = ['Z\n', 'A\n', 'B\n', 'C']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,2 +1,3 @@',
                '+Z',
                ' A',
                ' B']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

    def test_from_unified_diff_multiple_modifications(self):
        source = ['first\n', 'second']
        target = ['first_changed\n', 'second_changed']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,2 +1,2 @@',
                '-first',
                '-second',
                '+first_changed',
                '+second_changed']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

    def test_from_unified_diff_multiple_hunks(self):
        source = ['A\n', 'B\n', 'C\n', 'D\n', 'E\n', 'F\n', 'G']
        target = ['A\n', 'C\n', 'D\n', 'E\n', 'F\n']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,2 +1,1 @@',
                ' A',
                '-B',
                '@@ -3,5 +2,4 @@',
                ' C',
                ' D',
                ' E',
                ' F',
                '-G']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

    def test_from_unified_diff_incomplete_hunks_multiple_deletions(self):
        source = ['A\n', 'B\n', 'C\n', 'D\n', 'E\n', 'F\n', 'G']
        target = ['A\n', 'C\n', 'D\n', 'E\n', 'F\n']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,2 +1,1 @@',
                ' A',
                '-B',
                '@@ -5,3 +4,2 @@',
                ' E',
                ' F',
                '-G']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

    def test_from_unified_diff_incomplete_hunks_multiple_additions(self):
        source = ['A\n', 'C\n', 'D\n', 'E\n', 'G']
        target = ['A\n', 'B\n', 'C\n', 'D\n', 'E\n', 'F\n', 'G']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,1 +1,2 @@',
                ' A',
                '+B',
                '@@ -4,2 +5,3 @@',
                ' E',
                '+F',
                ' G']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

    def test_from_unified_diff_incomplete_hunks_multiple_modifications(self):
        source = ['A\n', 'B\n', 'C\n', 'D\n', 'E\n', 'F\n', 'G']
        target = ['A\n', 'B\n', 'Z\n', 'D\n', 'E\n', 'F\n', 'K']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,3 +1,3 @@',
                ' A',
                ' B',
                '-C',
                '+Z',
                '@@ -6,2 +5,2 @@',
                ' F',
                '-G',
                '+K']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

    def test_from_unified_diff_unmatched_line_to_delete(self):
        source = ['first', 'second']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,2 +1,2 @@',
                '-line_to_be_deleted_is_not_same',
                '+only_first_changed',
                ' second']
        diff_string = '\n'.join(diff)

        error_message = ('The line to delete does not match with '
                         'the line in the original file. '
                         'Line to delete: {!r}, '
                         'Original line #{!r}: {!r}')

        with self.assertRaisesRegex(
                RuntimeError,
                error_message.format(
                    'line_to_be_deleted_is_not_same',
                    1,
                    'first')):
            Diff.from_unified_diff(diff_string, source)

    def test_from_unified_diff_unmatched_context_line(self):
        source = ['first', 'second']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,2 +1,2 @@',
                ' context_line_is_not_same',
                ' second']
        diff_string = '\n'.join(diff)

        error_message = ('Context lines do not match. '
                         'Line from unified diff: {!r}, '
                         'Original line #{!r}: {!r}')

        with self.assertRaisesRegex(
            RuntimeError,
            error_message.format(
                'context_line_is_not_same',
                1,
                'first')):
            Diff.from_unified_diff(diff_string, source)

    def test_from_unified_diff_no_changes(self):
        source = ['first\n', 'second']
        target = ['first\n', 'second']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,2 +1,2 @@',
                ' first',
                ' second']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

    def test_from_unified_diff_no_changes_empty_diff(self):
        source = ['first\n', 'second']
        target = ['first\n', 'second']
        diff_string = ''
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

    def test_from_unified_diff_invalid_line_type_character(self):
        source = ['first', 'invalid starting character']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,2 +1,2 @@',
                ' first',
                '*invalid_starting_character']
        diff_string = '\n'.join(diff)
        with self.assertRaises(UnidiffParseError):
            self.uut = Diff.from_unified_diff(diff_string, source)

    def test_from_unified_diff_invalid_hunk(self):
        source = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,7 +1,5 @@',
                ' A',
                ' B',
                '-C',
                '+Z',
                '@@ -6,2 +5,2 @@',
                ' F',
                '-G',
                '+K']
        diff_string = '\n'.join(diff)
        with self.assertRaises(UnidiffParseError):
            self.uut = Diff.from_unified_diff(diff_string, source)

#    def test_from_clang_fixit(self):
#        try:
#            from clang.cindex import Index, LibclangError
#        except ImportError as err:
#            raise unittest.case.SkipTest(str(err))
#
#        joined_file = 'struct { int f0; }\nx = { f0 :1 };\n'
#        file = joined_file.splitlines(True)
#        fixed_file = ['struct { int f0; }\n', 'x = { .f0 = 1 };\n']
#        try:
#            tu = Index.create().parse('t.c', unsaved_files=[
#                ('t.c', joined_file)])
#        except LibclangError as err:
#            raise unittest.case.SkipTest(str(err))
#
#        fixit = tu.diagnostics[0].fixits[0]
#        clang_fixed_file = Diff.from_clang_fixit(fixit, file).modified
#        self.assertEqual(fixed_file, clang_fixed_file)

    def test_equality(self):
        a = ['first', 'second', 'third']
        b = ['first', 'third']
        diff_1 = Diff.from_string_arrays(a, b)

        c = ['first', 'second', 'third']
        d = ['first', 'third']

        diff_2 = Diff.from_string_arrays(c, d)

        self.assertEqual(diff_1, diff_2)

        # changing the original array should not influence
        # the diff
        a[1] = 'else'
        self.assertEqual(diff_1, diff_2)

        diff_1.rename = 'abcd'
        self.assertNotEqual(diff_1, diff_2)
        diff_1.rename = False

        diff_1.delete = True
        self.assertNotEqual(diff_1, diff_2)
        diff_1.delete = False

        diff_1.add_lines(1, ['1'])
        self.assertNotEqual(diff_1, diff_2)

    def test_json_export(self):
        JSONEncoder = create_json_encoder()
        a = ['first\n', 'second\n', 'third\n']
        b = ['first\n', 'third\n']
        diff = Diff.from_string_arrays(a, b)
        self.assertEqual(
            json.dumps(diff, cls=JSONEncoder, sort_keys=True),
            '"--- \\n'
            '+++ \\n'
            '@@ -1,3 +1,2 @@\\n'
            ' first\\n'
            '-second\\n'
            ' third\\n"')

    def test_rename(self):
        self.uut.rename = False
        self.uut.rename = '1234'
        with self.assertRaises(TypeError):
            self.uut.rename = True
        with self.assertRaises(TypeError):
            self.uut.rename = 1234

    def test_delete(self):
        self.uut.delete = True
        self.uut.delete = False
        # Double deletion is allowed
        self.uut.delete = False
        with self.assertRaises(TypeError):
            self.uut.delete = 'abcd'

        # If delete is True then modified returns an empty list
        self.uut.delete = True
        self.assertEqual(self.uut.modified, [])
        self.uut.delete = False

    def test_add_linebreaks(self):
        expected = ['1\n', '2\n', '3\n']

        self.assertEqual(
            Diff._add_linebreaks(['1', '2', '3']),
            expected)

        self.assertEqual(
            Diff._add_linebreaks(['1', '2\n', '3']),
            expected)

        self.assertEqual(
            Diff._add_linebreaks(expected),
            expected)

        self.assertEqual(Diff._add_linebreaks([]), [])

    def test_generate_linebreaks(self):
        eof_ln = ['1\n', '2\n', '3\n']
        no_eof_ln = ['1\n', '2\n', '3']

        self.assertEqual(
            Diff._generate_linebreaks(['1', '2', '3']),
            no_eof_ln)

        self.assertEqual(
            Diff._generate_linebreaks(['1', '2', '3\n']),
            eof_ln)

        self.assertEqual(
            Diff._generate_linebreaks(['1', '2\n', '3']),
            no_eof_ln)

        self.assertEqual(
            Diff._generate_linebreaks(no_eof_ln),
            no_eof_ln)

        self.assertEqual(
            Diff._generate_linebreaks(eof_ln),
            eof_ln)

        self.assertEqual(Diff._generate_linebreaks([]), [])
Пример #10
0
class DiffTest(unittest.TestCase):

    def setUp(self):
        self.file = ["1", "2", "3", "4"]
        self.uut = Diff(self.file)

    def test_add_lines(self):
        self.uut.add_lines(0, [])
        self.uut.add_lines(0, ["t"])
        self.uut.add_lines(0, [])

        # No double addition allowed
        self.assertRaises(ConflictError, self.uut.add_lines, 0, ["t"])
        self.assertRaises(ValueError, self.uut.add_lines, -1, ["t"])
        self.assertRaises(TypeError, self.uut.add_lines, "str", ["t"])

    def test_delete_line(self):
        self.uut.delete_line(1)
        self.uut.delete_line(1)  # Double deletion possible without conflict
        self.assertRaises(ValueError, self.uut.delete_line, 0)

    def test_change_line(self):
        self.assertEqual(len(self.uut), 0)
        self.uut.change_line(2, "1", "2")
        self.assertEqual(len(self.uut), 1)
        self.assertRaises(ConflictError, self.uut.change_line, 2, "1", "3")
        self.assertRaises(ValueError, self.uut.change_line, 0, "1", "2")

        self.uut.delete_line(1)
        # Line was deleted, unchangeable
        self.assertRaises(AssertionError, self.uut.change_line, 1, "1", "2")

    def test_affected_code(self):
        self.assertEqual(self.uut.affected_code("file"), [])

        self.uut.add_lines(0, ["test"])
        affected_code = [
            SourceRange.from_values("file", start_line=1)]
        self.assertEqual(self.uut.affected_code("file"), affected_code)

        self.uut.delete_line(2)
        affected_code = [
            SourceRange.from_values("file", start_line=1),
            SourceRange.from_values("file", start_line=2)]
        self.assertEqual(self.uut.affected_code("file"), affected_code)

        self.uut.delete_line(3)
        affected_code = [
            SourceRange.from_values("file", start_line=1),
            SourceRange.from_values("file", start_line=2, end_line=3)]
        self.assertEqual(self.uut.affected_code("file"), affected_code)

        self.uut.delete_line(6)
        affected_code = [
            SourceRange.from_values("file", start_line=1),
            SourceRange.from_values("file", start_line=2, end_line=3),
            SourceRange.from_values('file', start_line=6)]
        self.assertEqual(self.uut.affected_code("file"), affected_code)

    def test_modified(self):
        result_file = ["0.1",
                       "0.2",
                       "1",
                       "1.1",
                       "3.changed",
                       "4"]

        self.uut.delete_line(2)
        self.uut.add_lines(0, ["0.1", "0.2"])
        self.uut.add_lines(1, ["1.1"])
        self.uut.change_line(3, "3", "3.changed")
        self.assertEqual(self.uut.modified, result_file)
        self.assertEqual(self.uut.original, self.file)

        self.uut.delete_line(len(self.file))
        del result_file[len(result_file) - 1]
        self.assertEqual(self.uut.modified, result_file)

        self.uut.delete_line(1)
        del result_file[2]
        self.assertEqual(self.uut.modified, result_file)

    def test_addition(self):
        self.assertRaises(TypeError, self.uut.__add__, 5)

        result_file = ["1",
                       "2",
                       "2"]

        other = Diff(self.file)
        other.delete_line(1)
        other.change_line(2, "1", "2")
        other.add_lines(0, ["1"])

        self.uut.delete_line(1)
        self.uut.delete_line(3)
        self.uut.change_line(4, "4", "2")
        result = self.uut + other

        self.assertEqual(result.modified, result_file)
        # Make sure it didn't happen in place!
        self.assertNotEqual(self.uut.modified, result_file)

    def test_from_string_arrays(self):
        a = ["q", "a", "b", "x", "c", "d"]
        b = ["a", "b", "y", "c", "d", "f"]
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

        a = ["first", "fourth"]
        b = ["first", "second", "third", "fourth"]
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

        a = ["first", "fourth"]
        b = ["first_changed", "second", "third", "fourth"]
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

        a = ["first", "second", "third", "fourth"]
        b = ["first", "fourth"]
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

        a = ["first", "second", "third", "fourth"]
        b = ["first_changed", "second_changed", "fourth"]
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

    @skip_if_no_clang()
    def test_from_clang_fixit(self):
        joined_file = 'struct { int f0; }\nx = { f0 :1 };\n'
        file = joined_file.splitlines(True)
        fixed_file = ['struct { int f0; }\n', 'x = { .f0 = 1 };\n']

        tu = Index.create().parse('t.c', unsaved_files=[
            ('t.c', joined_file)])
        fixit = tu.diagnostics[0].fixits[0]

        clang_fixed_file = Diff.from_clang_fixit(fixit, file).modified
        self.assertEqual(fixed_file, clang_fixed_file)

    def test_equality(self):
        a = ["first", "second", "third"]
        b = ["first", "third"]
        diff_1 = Diff.from_string_arrays(a, b)

        a[1] = "else"
        diff_2 = Diff.from_string_arrays(a, b)
        self.assertEqual(diff_1, diff_2)

        diff_1.add_lines(1, ["1"])
        self.assertNotEqual(diff_1, diff_2)

    def test_json_export(self):
        a = ["first\n", "second\n", "third\n"]
        b = ["first\n", "third\n"]
        diff = Diff.from_string_arrays(a, b)
        self.assertEqual(
            json.dumps(diff, cls=JSONEncoder, sort_keys=True),
            '"--- \\n'
            '+++ \\n'
            '@@ -1,3 +1,2 @@\\n'
            ' first\\n'
            '-second\\n'
            ' third\\n"')
Пример #11
0
    def run(self,
            filename,
            file,
            max_filename_length: int = 260,
            file_naming_convention: str = None,
            ignore_uppercase_filenames: bool = True,
            filename_prefix: str = '',
            filename_suffix: str = ''):
        """
        Checks whether the filename follows a certain naming-convention.

        :param file_naming_convention:
            The naming-convention. Supported values are:
            - ``auto`` to guess the correct convention. Defaults to ``snake``
            if the correct convention cannot be guessed.
            - ``camel`` (``thisIsCamelCase``)
            - ``kebab`` (``this-is-kebab-case``)
            - ``pascal`` (``ThisIsPascalCase``)
            - ``snake`` (``this_is_snake_case``)
            - ``space`` (``This Is Space Case``)
        :param max_filename_length:
            Maximum filename length on both Windows and Unix-like systems.
        :param ignore_uppercase_filenames:
            Whether or not to ignore fully uppercase filenames completely,
            e.g. COPYING, LICENSE etc.
        :param filename_prefix:
            Check whether the filename uses a certain prefix.
            The file's extension is ignored.
        :param filename_suffix:
            Check whether the filename uses a certain suffix.
            The file's extension is ignored.
        """
        head, tail = os.path.split(filename)
        filename_without_extension, extension = os.path.splitext(tail)

        if file_naming_convention is None:
            self.warn('Please specify a file naming convention explicitly'
                      ' or use "auto".')
            file_naming_convention = 'auto'
        else:
            file_naming_convention = file_naming_convention.lower()

        if file_naming_convention == 'auto':
            if extension in self._language_naming_convention:
                file_naming_convention = self._language_naming_convention[
                    extension]
            else:
                self.warn('The file naming convention could not be guessed. '
                          'Using the default "snake" naming convention.')
                file_naming_convention = 'snake'

        messages = []

        try:
            new_name = self._naming_convention[file_naming_convention](
                filename_without_extension)
        except KeyError:
            self.err('Invalid file-naming-convention provided: ' +
                     file_naming_convention)
            return

        if new_name != filename_without_extension:
            messages.append(
                'Filename does not follow {} naming-convention.'.format(
                    file_naming_convention))

        if not filename_without_extension.startswith(filename_prefix):
            new_name = filename_prefix + new_name
            messages.append('Filename does not use the prefix {!r}.'.format(
                filename_prefix))

        if not filename_without_extension.endswith(filename_suffix):
            new_name = new_name + filename_suffix
            messages.append('Filename does not use the suffix {!r}.'.format(
                filename_suffix))

        if len(filename) > max_filename_length:
            messages.append('Filename is too long ({} > {}).'.format(
                len(filename), max_filename_length))

        if ignore_uppercase_filenames and filename_without_extension.isupper():
            return

        if messages:
            diff = Diff(file, rename=os.path.join(head, new_name + extension))
            message = ('\n'.join(
                '- ' + mes
                for mes in messages) if len(messages) > 1 else messages[0])

            yield Result(self,
                         message,
                         diff.affected_code(filename),
                         diffs={filename: diff})
Пример #12
0
class DiffTest(unittest.TestCase):

    def setUp(self):
        self.file = ['1', '2', '3', '4']
        self.uut = Diff(self.file)

    def test_add_lines(self):
        self.uut.add_lines(0, [])
        self.uut.add_lines(0, ['t'])
        self.uut.add_lines(0, [])

    def test_add_line(self):
        self.uut.add_line(0, 't')
        self.assertRaises(ConflictError, self.uut.add_line, 0, 't')
        self.assertEqual(self.uut.modified, ['t', '1', '2', '3', '4'])

    def test_double_addition(self):
        self.uut.add_lines(0, ['t'])

        # No double addition allowed
        self.assertRaises(ConflictError, self.uut.add_lines, 0, ['t'])
        self.assertRaises(ValueError, self.uut.add_lines, -1, ['t'])
        self.assertRaises(TypeError, self.uut.add_lines, 'str', ['t'])

    def test_delete_line(self):
        self.uut.delete_line(1)
        self.uut.delete_line(1)  # Double deletion possible without conflict
        additions, deletions = self.uut.stats()
        self.assertEqual(deletions, 1)
        self.assertRaises(ValueError, self.uut.delete_line, 0)

    def test_delete_lines(self):
        self.uut.delete_lines(1, 10)
        self.uut.delete_lines(10, 20)
        additions, deletions = self.uut.stats()
        self.assertEqual(deletions, 20)
        self.assertRaises(ValueError, self.uut.delete_lines, 0, 10)

    def test_change_line(self):
        self.assertEqual(len(self.uut), 0)
        self.uut.change_line(2, '1', '2')
        self.assertEqual(len(self.uut), 2)
        self.assertRaises(ConflictError, self.uut.change_line, 2, '1', '3')
        self.assertRaises(ValueError, self.uut.change_line, 0, '1', '2')

        self.uut.delete_line(1)
        # Line was deleted, unchangeable
        self.assertRaises(ConflictError, self.uut.change_line, 1, '1', '2')

    def test_capture_warnings(self):
        """
        Since this addresses the deprecated method, this testcase is
        temporary (until the old API is fully removed).
        """
        logger = logging.getLogger()
        with self.assertLogs(logger, 'DEBUG') as log:
            self.assertEqual(len(self.uut), 0)
            self.uut.change_line(2, '1', '2')
        self.assertEqual(log.output, [
            'DEBUG:root:Use of change_line method is deprecated. Instead '
            'use modify_line method, without the original_line argument'])

    def test_double_changes_with_same_diff(self):
        self.uut.change_line(2, '1', '2')

        # Double addition when diff is equal is allowed
        try:
            self.uut.change_line(2, '1', '2')
        except Exception:
            self.fail('We should not have a conflict on same diff!')

    def test_affected_code(self):
        self.assertEqual(self.uut.affected_code('file'), [])

        self.uut.add_lines(0, ['test'])
        affected_code = [
            SourceRange.from_values('file', start_line=1)]
        self.assertEqual(self.uut.affected_code('file'), affected_code)

        self.uut.delete_line(2)
        affected_code = [
            SourceRange.from_values('file', start_line=1),
            SourceRange.from_values('file', start_line=2)]
        self.assertEqual(self.uut.affected_code('file'), affected_code)

        self.uut.delete_line(3)
        affected_code = [
            SourceRange.from_values('file', start_line=1),
            SourceRange.from_values('file', start_line=2, end_line=3)]
        self.assertEqual(self.uut.affected_code('file'), affected_code)

        self.uut.delete_line(6)
        affected_code = [
            SourceRange.from_values('file', start_line=1),
            SourceRange.from_values('file', start_line=2, end_line=3),
            SourceRange.from_values('file', start_line=6)]
        self.assertEqual(self.uut.affected_code('file'), affected_code)

    def test_len(self):
        self.uut.delete_line(2)
        self.assertEqual(len(self.uut), 1)
        self.uut.add_lines(2, ['2.3', '2.5', '2.6'])
        self.assertEqual(len(self.uut), 4)
        self.uut.change_line(1, '1', '1.1')
        self.assertEqual(len(self.uut), 6)

    def test_stats(self):
        self.uut.delete_line(2)
        self.assertEqual(self.uut.stats(), (0, 1))
        self.uut.add_lines(2, ['2.3', '2.5', '2.6'])
        self.assertEqual(self.uut.stats(), (3, 1))
        self.uut.change_line(1, '1', '1.1')
        self.assertEqual(self.uut.stats(), (4, 2))

    def test_modified(self):
        result_file = ['0.1',
                       '0.2',
                       '1',
                       '1.1',
                       '3.changed',
                       '4']

        self.uut.delete_line(2)
        self.uut.add_lines(0, ['0.1', '0.2'])
        self.uut.add_lines(1, ['1.1'])
        self.uut.change_line(3, '3', '3.changed')

        self.assertEqual(self.uut.modified, result_file)
        self.assertEqual(self.uut.original, self.file)

        self.uut.delete_line(len(self.file))
        del result_file[len(result_file) - 1]
        self.assertEqual(self.uut.modified, result_file)

        self.uut.delete_line(1)
        del result_file[2]
        self.assertEqual(self.uut.modified, result_file)

    def test_addition(self):
        self.assertRaises(TypeError, self.uut.__add__, 5)

        result_file = ['1',
                       '2',
                       '2']

        other = Diff(self.file)
        other.delete_line(1)
        other.change_line(2, '1', '2')
        other.add_lines(0, ['1'])

        self.uut.delete_line(1)
        self.uut.delete_line(3)
        self.uut.change_line(4, '4', '2')
        result = self.uut + other

        self.assertEqual(result.modified, result_file)
        # Make sure it didn't happen in place!
        self.assertNotEqual(self.uut.modified, result_file)

    def test_addition_rename(self):
        uut = Diff(self.file, rename=False)
        other = Diff(self.file, rename=False)
        self.assertEqual((other + uut).rename, False)

        other.rename = 'some.py'
        self.assertEqual((other + uut).rename, 'some.py')

        uut.rename = 'some.py'
        self.assertEqual((other + uut).rename, 'some.py')

        uut.rename = 'other.py'
        self.assertRaises(ConflictError, other.__add__, uut)

    def test_from_string_arrays(self):
        a = ['q', 'a', 'b', 'x', 'c', 'd']
        b = ['a', 'b', 'y', 'c', 'd', 'f']
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

        a = ['first', 'fourth']
        b = ['first', 'second', 'third', 'fourth']
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

        a = ['first', 'fourth']
        b = ['first_changed', 'second', 'third', 'fourth']
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

        a = ['first', 'second', 'third', 'fourth']
        b = ['first', 'fourth']
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

        a = ['first', 'second', 'third', 'fourth']
        b = ['first_changed', 'second_changed', 'fourth']
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

    def test_from_clang_fixit(self):
        try:
            from clang.cindex import Index, LibclangError
        except ImportError as err:
            raise SkipTest(str(err))

        joined_file = 'struct { int f0; }\nx = { f0 :1 };\n'
        file = joined_file.splitlines(True)
        fixed_file = ['struct { int f0; }\n', 'x = { .f0 = 1 };\n']
        try:
            tu = Index.create().parse('t.c', unsaved_files=[
                ('t.c', joined_file)])
        except LibclangError as err:
            raise SkipTest(str(err))

        fixit = tu.diagnostics[0].fixits[0]
        clang_fixed_file = Diff.from_clang_fixit(fixit, file).modified
        self.assertEqual(fixed_file, clang_fixed_file)

    def test_equality(self):
        a = ['first', 'second', 'third']
        b = ['first', 'third']
        diff_1 = Diff.from_string_arrays(a, b)

        a[1] = 'else'
        diff_2 = Diff.from_string_arrays(a, b)
        self.assertEqual(diff_1, diff_2)

        diff_1.rename = 'abcd'
        self.assertNotEqual(diff_1, diff_2)
        diff_1.rename = False

        diff_1.delete = True
        self.assertNotEqual(diff_1, diff_2)
        diff_1.delete = False

        diff_1.add_lines(1, ['1'])
        self.assertNotEqual(diff_1, diff_2)

    def test_json_export(self):
        JSONEncoder = create_json_encoder()
        a = ['first\n', 'second\n', 'third\n']
        b = ['first\n', 'third\n']
        diff = Diff.from_string_arrays(a, b)
        self.assertEqual(
            json.dumps(diff, cls=JSONEncoder, sort_keys=True),
            '"--- \\n'
            '+++ \\n'
            '@@ -1,3 +1,2 @@\\n'
            ' first\\n'
            '-second\\n'
            ' third\\n"')

    def test_rename(self):
        self.uut.rename = False
        self.uut.rename = '1234'
        with self.assertRaises(TypeError):
            self.uut.rename = True
        with self.assertRaises(TypeError):
            self.uut.rename = 1234

    def test_delete(self):
        self.uut.delete = True
        self.uut.delete = False
        # Double deletion is allowed
        self.uut.delete = False
        with self.assertRaises(TypeError):
            self.uut.delete = 'abcd'

        # If delete is True then modified returns an empty list
        self.uut.delete = True
        self.assertEqual(self.uut.modified, [])
        self.uut.delete = False
Пример #13
0
class DiffTest(unittest.TestCase):

    def setUp(self):
        self.file = ["1", "2", "3", "4"]
        self.uut = Diff(self.file)

    def test_add_lines(self):
        self.uut.add_lines(0, [])
        self.uut.add_lines(0, ["t"])
        self.uut.add_lines(0, [])

        # No double addition allowed
        self.assertRaises(ConflictError, self.uut.add_lines, 0, ["t"])
        self.assertRaises(ValueError, self.uut.add_lines, -1, ["t"])
        self.assertRaises(TypeError, self.uut.add_lines, "str", ["t"])

    def test_delete_line(self):
        self.uut.delete_line(1)
        self.uut.delete_line(1)  # Double deletion possible without conflict
        self.assertRaises(ValueError, self.uut.delete_line, 0)

    def test_change_line(self):
        self.assertEqual(len(self.uut), 0)
        self.uut.change_line(2, "1", "2")
        self.assertEqual(len(self.uut), 2)
        self.assertRaises(ConflictError, self.uut.change_line, 2, "1", "3")
        self.assertRaises(ValueError, self.uut.change_line, 0, "1", "2")

        self.uut.delete_line(1)
        # Line was deleted, unchangeable
        self.assertRaises(ConflictError, self.uut.change_line, 1, "1", "2")

    def test_affected_code(self):
        self.assertEqual(self.uut.affected_code("file"), [])

        self.uut.add_lines(0, ["test"])
        affected_code = [
            SourceRange.from_values("file", start_line=1)]
        self.assertEqual(self.uut.affected_code("file"), affected_code)

        self.uut.delete_line(2)
        affected_code = [
            SourceRange.from_values("file", start_line=1),
            SourceRange.from_values("file", start_line=2)]
        self.assertEqual(self.uut.affected_code("file"), affected_code)

        self.uut.delete_line(3)
        affected_code = [
            SourceRange.from_values("file", start_line=1),
            SourceRange.from_values("file", start_line=2, end_line=3)]
        self.assertEqual(self.uut.affected_code("file"), affected_code)

        self.uut.delete_line(6)
        affected_code = [
            SourceRange.from_values("file", start_line=1),
            SourceRange.from_values("file", start_line=2, end_line=3),
            SourceRange.from_values('file', start_line=6)]
        self.assertEqual(self.uut.affected_code("file"), affected_code)

    def test_len(self):
        self.uut.delete_line(2)
        self.assertEqual(len(self.uut), 1)
        self.uut.add_lines(2, ["2.3", "2.5", "2.6"])
        self.assertEqual(len(self.uut), 4)
        self.uut.change_line(1, "1", "1.1")
        self.assertEqual(len(self.uut), 6)

    def test_stats(self):
        self.uut.delete_line(2)
        self.assertEqual(self.uut.stats(), (0, 1))
        self.uut.add_lines(2, ["2.3", "2.5", "2.6"])
        self.assertEqual(self.uut.stats(), (3, 1))
        self.uut.change_line(1, "1", "1.1")
        self.assertEqual(self.uut.stats(), (4, 2))

    def test_modified(self):
        result_file = ["0.1",
                       "0.2",
                       "1",
                       "1.1",
                       "3.changed",
                       "4"]

        self.uut.delete_line(2)
        self.uut.add_lines(0, ["0.1", "0.2"])
        self.uut.add_lines(1, ["1.1"])
        self.uut.change_line(3, "3", "3.changed")

        self.assertEqual(self.uut.modified, result_file)
        self.assertEqual(self.uut.original, self.file)

        self.uut.delete_line(len(self.file))
        del result_file[len(result_file) - 1]
        self.assertEqual(self.uut.modified, result_file)

        self.uut.delete_line(1)
        del result_file[2]
        self.assertEqual(self.uut.modified, result_file)

    def test_addition(self):
        self.assertRaises(TypeError, self.uut.__add__, 5)

        result_file = ["1",
                       "2",
                       "2"]

        other = Diff(self.file)
        other.delete_line(1)
        other.change_line(2, "1", "2")
        other.add_lines(0, ["1"])

        self.uut.delete_line(1)
        self.uut.delete_line(3)
        self.uut.change_line(4, "4", "2")
        result = self.uut + other

        self.assertEqual(result.modified, result_file)
        # Make sure it didn't happen in place!
        self.assertNotEqual(self.uut.modified, result_file)

    def test_addition_rename(self):
        uut = Diff(self.file, rename=False)
        other = Diff(self.file, rename=False)
        self.assertEqual((other + uut).rename, False)

        other.rename = "some.py"
        self.assertEqual((other + uut).rename, "some.py")

        uut.rename = "some.py"
        self.assertEqual((other + uut).rename, "some.py")

        uut.rename = "other.py"
        self.assertRaises(ConflictError, other.__add__, uut)

    def test_from_string_arrays(self):
        a = ["q", "a", "b", "x", "c", "d"]
        b = ["a", "b", "y", "c", "d", "f"]
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

        a = ["first", "fourth"]
        b = ["first", "second", "third", "fourth"]
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

        a = ["first", "fourth"]
        b = ["first_changed", "second", "third", "fourth"]
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

        a = ["first", "second", "third", "fourth"]
        b = ["first", "fourth"]
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

        a = ["first", "second", "third", "fourth"]
        b = ["first_changed", "second_changed", "fourth"]
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

    def test_from_clang_fixit(self):
        try:
            from clang.cindex import Index, LibclangError
        except ImportError as err:
            raise SkipTest(str(err))

        joined_file = 'struct { int f0; }\nx = { f0 :1 };\n'
        file = joined_file.splitlines(True)
        fixed_file = ['struct { int f0; }\n', 'x = { .f0 = 1 };\n']
        try:
            tu = Index.create().parse('t.c', unsaved_files=[
                ('t.c', joined_file)])
        except LibclangError as err:
            raise SkipTest(str(err))

        fixit = tu.diagnostics[0].fixits[0]
        clang_fixed_file = Diff.from_clang_fixit(fixit, file).modified
        self.assertEqual(fixed_file, clang_fixed_file)

    def test_equality(self):
        a = ["first", "second", "third"]
        b = ["first", "third"]
        diff_1 = Diff.from_string_arrays(a, b)

        a[1] = "else"
        diff_2 = Diff.from_string_arrays(a, b)
        self.assertEqual(diff_1, diff_2)

        diff_1.rename = "abcd"
        self.assertNotEqual(diff_1, diff_2)
        diff_1.rename = False

        diff_1.delete = True
        self.assertNotEqual(diff_1, diff_2)
        diff_1.delete = False

        diff_1.add_lines(1, ["1"])
        self.assertNotEqual(diff_1, diff_2)

    def test_json_export(self):
        JSONEncoder = create_json_encoder()
        a = ["first\n", "second\n", "third\n"]
        b = ["first\n", "third\n"]
        diff = Diff.from_string_arrays(a, b)
        self.assertEqual(
            json.dumps(diff, cls=JSONEncoder, sort_keys=True),
            '"--- \\n'
            '+++ \\n'
            '@@ -1,3 +1,2 @@\\n'
            ' first\\n'
            '-second\\n'
            ' third\\n"')

    def test_rename(self):
        self.uut.rename = False
        self.uut.rename = "1234"
        with self.assertRaises(TypeError):
            self.uut.rename = True
        with self.assertRaises(TypeError):
            self.uut.rename = 1234

    def test_delete(self):
        self.uut.delete = True
        self.uut.delete = False
        # Double deletion is allowed
        self.uut.delete = False
        with self.assertRaises(TypeError):
            self.uut.delete = "abcd"

        # If delete is True then modified returns an empty list
        self.uut.delete = True
        self.assertEqual(self.uut.modified, [])
        self.uut.delete = False
Пример #14
0
class DiffTest(unittest.TestCase):

    def setUp(self):
        self.file = ['1', '2', '3', '4']
        self.uut = Diff(self.file)

    def test_add_lines(self):
        self.uut.add_lines(0, [])
        self.uut.add_lines(0, ['t'])
        self.uut.add_lines(0, [])

    def test_add_line(self):
        self.uut.add_line(0, 't')
        self.assertRaises(ConflictError, self.uut.add_line, 0, 't')
        self.assertEqual(self.uut.modified, ['t', '1', '2', '3', '4'])

    def test_double_addition(self):
        self.uut.add_lines(0, ['t'])

        # No double addition allowed
        self.assertRaises(ConflictError, self.uut.add_lines, 0, ['t'])
        self.assertRaises(IndexError, self.uut.add_lines, -1, ['t'])
        self.assertRaises(TypeError, self.uut.add_lines, 'str', ['t'])

    def test_delete_line(self):
        self.uut.delete_line(1)
        self.uut.delete_line(1)  # Double deletion possible without conflict
        additions, deletions = self.uut.stats()
        self.assertEqual(deletions, 1)
        self.assertRaises(IndexError, self.uut.delete_line, 0)
        self.assertRaises(IndexError, self.uut.delete_line, 10)

    def test_delete_lines(self):
        self.uut.delete_lines(1, 2)
        self.uut.delete_lines(2, 3)
        additions, deletions = self.uut.stats()
        self.assertEqual(deletions, 3)
        self.assertRaises(IndexError, self.uut.delete_lines, 0, 2)
        self.assertRaises(IndexError, self.uut.delete_lines, 1, 6)

    def test_change_line(self):
        self.assertEqual(len(self.uut), 0)
        self.uut.change_line(2, '1', '2')
        self.assertEqual(len(self.uut), 2)
        self.assertRaises(ConflictError, self.uut.change_line, 2, '1', '3')
        self.assertRaises(IndexError, self.uut.change_line, 0, '1', '2')

        self.uut.delete_line(1)
        # Line was deleted, unchangeable
        self.assertRaises(ConflictError, self.uut.change_line, 1, '1', '2')

    def test_capture_warnings(self):
        """
        Since this addresses the deprecated method, this testcase is
        temporary (until the old API is fully removed).
        """
        logger = logging.getLogger()
        with self.assertLogs(logger, 'DEBUG') as log:
            self.assertEqual(len(self.uut), 0)
            self.uut.change_line(2, '1', '2')
        self.assertEqual(log.output, [
            'DEBUG:root:Use of change_line method is deprecated. Instead '
            'use modify_line method, without the original_line argument'])

    def test_double_changes_with_same_diff(self):
        self.uut.change_line(2, '1', '2')

        # Double addition when diff is equal is allowed
        try:
            self.uut.change_line(2, '1', '2')
        except Exception:
            self.fail('We should not have a conflict on same diff!')

    def test_affected_code(self):
        self.assertEqual(self.uut.affected_code('file'), [])

        self.uut.add_lines(0, ['test'])
        affected_code = [
            SourceRange.from_values('file', start_line=1)]
        self.assertEqual(self.uut.affected_code('file'), affected_code)

        self.uut.delete_line(2)
        affected_code = [
            SourceRange.from_values('file', start_line=1),
            SourceRange.from_values('file', start_line=2)]
        self.assertEqual(self.uut.affected_code('file'), affected_code)

        self.uut.delete_line(3)
        affected_code = [
            SourceRange.from_values('file', start_line=1),
            SourceRange.from_values('file', start_line=2, end_line=3)]
        self.assertEqual(self.uut.affected_code('file'), affected_code)

        self.uut.delete_line(4)
        affected_code = [
            SourceRange.from_values('file', start_line=1),
            SourceRange.from_values('file', start_line=2, end_line=4)]
        self.assertEqual(self.uut.affected_code('file'), affected_code)

    def test_len(self):
        self.uut.delete_line(2)
        self.assertEqual(len(self.uut), 1)
        self.uut.add_lines(2, ['2.3', '2.5', '2.6'])
        self.assertEqual(len(self.uut), 4)
        self.uut.change_line(1, '1', '1.1')
        self.assertEqual(len(self.uut), 6)

    def test_stats(self):
        self.uut.delete_line(2)
        self.assertEqual(self.uut.stats(), (0, 1))
        self.uut.add_lines(2, ['2.3', '2.5', '2.6'])
        self.assertEqual(self.uut.stats(), (3, 1))
        self.uut.change_line(1, '1', '1.1')
        self.assertEqual(self.uut.stats(), (4, 2))

    def test_modified(self):
        result_file = ['0.1',
                       '0.2',
                       '1',
                       '1.1',
                       '3.changed',
                       '4']

        self.uut.delete_line(2)
        self.uut.add_lines(0, ['0.1', '0.2'])
        self.uut.add_lines(1, ['1.1'])
        self.uut.change_line(3, '3', '3.changed')

        self.assertEqual(self.uut.modified, result_file)
        self.assertEqual(self.uut.original, self.file)

        self.uut.delete_line(len(self.file))
        del result_file[len(result_file) - 1]
        self.assertEqual(self.uut.modified, result_file)

        self.uut.delete_line(1)
        del result_file[2]
        self.assertEqual(self.uut.modified, result_file)

    def test_bool(self):
        self.assertFalse(self.uut)
        self.uut.add_line(4, '4')
        self.assertTrue(self.uut)
        self.uut.delete_line(4)
        self.assertFalse(self.uut)

        # test if it works with tuples.
        uutuple = Diff(('1', '2', '3', '4'))

        self.assertFalse(uutuple)
        uutuple.add_line(4, '4')
        self.assertTrue(uutuple)
        uutuple.delete_line(4)
        self.assertFalse(uutuple)

    def test_addition(self):
        self.assertRaises(TypeError, self.uut.__add__, 5)

        result_file = ['1',
                       '2',
                       '2']

        other = Diff(self.file)
        other.delete_line(1)
        other.change_line(2, '1', '2')
        other.add_lines(0, ['1'])

        self.uut.delete_line(1)
        self.uut.delete_line(3)
        self.uut.change_line(4, '4', '2')
        result = self.uut + other

        self.assertEqual(result.modified, result_file)
        # Make sure it didn't happen in place!
        self.assertNotEqual(self.uut.modified, result_file)

    def test_addition_rename(self):
        uut = Diff(self.file, rename=False)
        other = Diff(self.file, rename=False)
        self.assertEqual((other + uut).rename, False)

        other.rename = 'some.py'
        self.assertEqual((other + uut).rename, 'some.py')

        uut.rename = 'some.py'
        self.assertEqual((other + uut).rename, 'some.py')

        uut.rename = 'other.py'
        self.assertRaises(ConflictError, other.__add__, uut)

    def test_from_string_arrays(self):
        a = ['q', 'a', 'b', 'x', 'c', 'd']
        b = ['a', 'b', 'y', 'c', 'd', 'f']
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

        a = ['first', 'fourth']
        b = ['first', 'second', 'third', 'fourth']
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

        a = ['first', 'fourth']
        b = ['first_changed', 'second', 'third', 'fourth']
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

        a = ['first', 'second', 'third', 'fourth']
        b = ['first', 'fourth']
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

        a = ['first', 'second', 'third', 'fourth']
        b = ['first_changed', 'second_changed', 'fourth']
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

    def test_from_unified_diff_single_addition(self):
        source = ['single line']
        target = ['single line', 'another line added']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1 +1,2 @@',
                ' single line',
                '+another line added']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

    def test_from_unified_diff_single_deletion(self):
        source = ['two lines', 'to be removed']
        target = ['two lines']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,2 +1 @@',
                ' two lines',
                '-to be removed']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

    def test_from_unified_diff_single_modification(self):
        source = ['first', 'second']
        target = ['only_first_changed', 'second']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,2 +1,2 @@',
                '-first',
                '+only_first_changed',
                ' second']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

    def test_from_unified_diff_multiple_additions_different_orderings(self):
        source = ['A', 'B', 'C']
        target = ['A', 'Y', 'Z', 'B', 'C']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,3 +1,5 @@',
                ' A',
                '+Y',
                '+Z',
                ' B',
                ' C']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

        source = ['A', 'B', 'C']
        target = ['A', 'Y', 'Z', 'C']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,3 +1,5 @@',
                ' A',
                '+Y',
                '+Z',
                '-B',
                ' C']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

        source = ['A', 'B', 'C']
        target = ['Y', 'Z', 'C']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,3 +1,5 @@',
                '-A',
                '+Y',
                '+Z',
                '-B',
                ' C']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

        source = ['A', 'B', 'C']
        target = ['A', 'B', 'C', 'Y', 'Z']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -3 +3,3 @@',
                ' C',
                '+Y',
                '+Z']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

    def test_from_unified_diffrent_beginning_line_types(self):
        source = ['A', 'B', 'C']
        target = ['A', 'Y', 'B', 'C']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,3 +1,4 @@',
                ' A',
                '+Y',
                ' B',
                ' C']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

        source = ['A', 'B', 'C']
        target = ['B', 'C']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,3 +1,2 @@',
                '-A',
                ' B',
                ' C']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

        source = ['A', 'B', 'C']
        target = ['Z', 'A', 'B', 'C']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,2 +1,3 @@',
                '+Z',
                ' A',
                ' B']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

    def test_from_unified_diff_multiple_modifications(self):
        source = ['first', 'second']
        target = ['first_changed', 'second_changed']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,2 +1,2 @@',
                '-first',
                '-second',
                '+first_changed',
                '+second_changed']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

    def test_from_unified_diff_multiple_hunks(self):
        source = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
        target = ['A', 'C', 'D', 'E', 'F']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,2 +1,1 @@',
                ' A',
                '-B',
                '@@ -3,5 +2,4 @@',
                ' C',
                ' D',
                ' E',
                ' F',
                '-G']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

    def test_from_unified_diff_incomplete_hunks_multiple_deletions(self):
        source = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
        target = ['A', 'C', 'D', 'E', 'F']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,2 +1,1 @@',
                ' A',
                '-B',
                '@@ -5,3 +4,2 @@',
                ' E',
                ' F',
                '-G']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

    def test_from_unified_diff_incomplete_hunks_multiple_additions(self):
        source = ['A', 'C', 'D', 'E', 'G']
        target = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,1 +1,2 @@',
                ' A',
                '+B',
                '@@ -4,2 +5,3 @@',
                ' E',
                '+F',
                ' G']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

    def test_from_unified_diff_incomplete_hunks_multiple_modifications(self):
        source = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
        target = ['A', 'B', 'Z', 'D', 'E', 'F', 'K']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,3 +1,3 @@',
                ' A',
                ' B',
                '-C',
                '+Z',
                '@@ -6,2 +5,2 @@',
                ' F',
                '-G',
                '+K']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

    def test_from_unified_diff_unmatched_line_to_delete(self):
        source = ['first', 'second']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,2 +1,2 @@',
                '-line_to_be_deleted_is_not_same',
                '+only_first_changed',
                ' second']
        diff_string = '\n'.join(diff)

        error_message = ('The line to delete does not match with '
                         'the line in the original file. '
                         'Line to delete: {!r}, '
                         'Original line #{!r}: {!r}')

        with self.assertRaisesRegex(
                RuntimeError,
                error_message.format(
                    'line_to_be_deleted_is_not_same',
                    1,
                    'first')):
            Diff.from_unified_diff(diff_string, source)

    def test_from_unified_diff_unmatched_context_line(self):
        source = ['first', 'second']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,2 +1,2 @@',
                ' context_line_is_not_same',
                ' second']
        diff_string = '\n'.join(diff)

        error_message = ('Context lines do not match. '
                         'Line from unified diff: {!r}, '
                         'Original line #{!r}: {!r}')

        with self.assertRaisesRegex(
            RuntimeError,
            error_message.format(
                'context_line_is_not_same',
                1,
                'first')):
            Diff.from_unified_diff(diff_string, source)

    def test_from_unified_diff_no_changes(self):
        source = ['first', 'second']
        target = ['first', 'second']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,2 +1,2 @@',
                ' first',
                ' second']
        diff_string = '\n'.join(diff)
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

    def test_from_unified_diff_no_changes_empty_diff(self):
        source = ['first', 'second']
        target = ['first', 'second']
        diff_string = ''
        self.uut = Diff.from_unified_diff(diff_string, source)
        self.assertEqual(self.uut.original, source)
        self.assertEqual(self.uut.modified, target)

    def test_from_unified_diff_invalid_line_type_character(self):
        source = ['first', 'invalid starting character']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,2 +1,2 @@',
                ' first',
                '*invalid_starting_character']
        diff_string = '\n'.join(diff)
        with self.assertRaises(UnidiffParseError):
            self.uut = Diff.from_unified_diff(diff_string, source)

    def test_from_unified_diff_invalid_hunk(self):
        source = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
        diff = ['--- a/testfile',
                '+++ b/testfile',
                '@@ -1,7 +1,5 @@',
                ' A',
                ' B',
                '-C',
                '+Z',
                '@@ -6,2 +5,2 @@',
                ' F',
                '-G',
                '+K']
        diff_string = '\n'.join(diff)
        with self.assertRaises(UnidiffParseError):
            self.uut = Diff.from_unified_diff(diff_string, source)

    def test_from_clang_fixit(self):
        try:
            from clang.cindex import Index, LibclangError
        except ImportError as err:
            raise unittest.case.SkipTest(str(err))

        joined_file = 'struct { int f0; }\nx = { f0 :1 };\n'
        file = joined_file.splitlines(True)
        fixed_file = ['struct { int f0; }\n', 'x = { .f0 = 1 };\n']
        try:
            tu = Index.create().parse('t.c', unsaved_files=[
                ('t.c', joined_file)])
        except LibclangError as err:
            raise unittest.case.SkipTest(str(err))

        fixit = tu.diagnostics[0].fixits[0]
        clang_fixed_file = Diff.from_clang_fixit(fixit, file).modified
        self.assertEqual(fixed_file, clang_fixed_file)

    def test_equality(self):
        a = ['first', 'second', 'third']
        b = ['first', 'third']
        diff_1 = Diff.from_string_arrays(a, b)

        c = ['first', 'second', 'third']
        d = ['first', 'third']

        diff_2 = Diff.from_string_arrays(c, d)

        self.assertEqual(diff_1, diff_2)

        # changing the original array should not influence
        # the diff
        a[1] = 'else'
        self.assertEqual(diff_1, diff_2)

        diff_1.rename = 'abcd'
        self.assertNotEqual(diff_1, diff_2)
        diff_1.rename = False

        diff_1.delete = True
        self.assertNotEqual(diff_1, diff_2)
        diff_1.delete = False

        diff_1.add_lines(1, ['1'])
        self.assertNotEqual(diff_1, diff_2)

    def test_json_export(self):
        JSONEncoder = create_json_encoder()
        a = ['first\n', 'second\n', 'third\n']
        b = ['first\n', 'third\n']
        diff = Diff.from_string_arrays(a, b)
        self.assertEqual(
            json.dumps(diff, cls=JSONEncoder, sort_keys=True),
            '"--- \\n'
            '+++ \\n'
            '@@ -1,3 +1,2 @@\\n'
            ' first\\n'
            '-second\\n'
            ' third\\n"')

    def test_rename(self):
        self.uut.rename = False
        self.uut.rename = '1234'
        with self.assertRaises(TypeError):
            self.uut.rename = True
        with self.assertRaises(TypeError):
            self.uut.rename = 1234

    def test_delete(self):
        self.uut.delete = True
        self.uut.delete = False
        # Double deletion is allowed
        self.uut.delete = False
        with self.assertRaises(TypeError):
            self.uut.delete = 'abcd'

        # If delete is True then modified returns an empty list
        self.uut.delete = True
        self.assertEqual(self.uut.modified, [])
        self.uut.delete = False
Пример #15
0
class DiffTest(unittest.TestCase):
    def setUp(self):
        self.file = ['1', '2', '3', '4']
        self.uut = Diff(self.file)

    def test_add_lines(self):
        self.uut.add_lines(0, [])
        self.uut.add_lines(0, ['t'])
        self.uut.add_lines(0, [])

    def test_add_line(self):
        self.uut.add_line(0, 't')
        self.assertRaises(ConflictError, self.uut.add_line, 0, 't')
        self.assertEqual(self.uut.modified, ['t', '1', '2', '3', '4'])

    def test_double_addition(self):
        self.uut.add_lines(0, ['t'])

        # No double addition allowed
        self.assertRaises(ConflictError, self.uut.add_lines, 0, ['t'])
        self.assertRaises(IndexError, self.uut.add_lines, -1, ['t'])
        self.assertRaises(TypeError, self.uut.add_lines, 'str', ['t'])

    def test_delete_line(self):
        self.uut.delete_line(1)
        self.uut.delete_line(1)  # Double deletion possible without conflict
        additions, deletions = self.uut.stats()
        self.assertEqual(deletions, 1)
        self.assertRaises(IndexError, self.uut.delete_line, 0)
        self.assertRaises(IndexError, self.uut.delete_line, 10)

    def test_delete_lines(self):
        self.uut.delete_lines(1, 2)
        self.uut.delete_lines(2, 3)
        additions, deletions = self.uut.stats()
        self.assertEqual(deletions, 3)
        self.assertRaises(IndexError, self.uut.delete_lines, 0, 2)
        self.assertRaises(IndexError, self.uut.delete_lines, 1, 6)

    def test_change_line(self):
        self.assertEqual(len(self.uut), 0)
        self.uut.change_line(2, '1', '2')
        self.assertEqual(len(self.uut), 2)
        self.assertRaises(ConflictError, self.uut.change_line, 2, '1', '3')
        self.assertRaises(IndexError, self.uut.change_line, 0, '1', '2')

        self.uut.delete_line(1)
        # Line was deleted, unchangeable
        self.assertRaises(ConflictError, self.uut.change_line, 1, '1', '2')

    def test_capture_warnings(self):
        """
        Since this addresses the deprecated method, this testcase is
        temporary (until the old API is fully removed).
        """
        logger = logging.getLogger()
        with self.assertLogs(logger, 'DEBUG') as log:
            self.assertEqual(len(self.uut), 0)
            self.uut.change_line(2, '1', '2')
        self.assertEqual(log.output, [
            'DEBUG:root:Use of change_line method is deprecated. Instead '
            'use modify_line method, without the original_line argument'
        ])

    def test_double_changes_with_same_diff(self):
        self.uut.change_line(2, '1', '2')

        # Double addition when diff is equal is allowed
        try:
            self.uut.change_line(2, '1', '2')
        except Exception:
            self.fail('We should not have a conflict on same diff!')

    def test_affected_code(self):
        self.assertEqual(self.uut.affected_code('file'), [])

        self.uut.add_lines(0, ['test'])
        affected_code = [SourceRange.from_values('file', start_line=1)]
        self.assertEqual(self.uut.affected_code('file'), affected_code)

        self.uut.delete_line(2)
        affected_code = [
            SourceRange.from_values('file', start_line=1),
            SourceRange.from_values('file', start_line=2)
        ]
        self.assertEqual(self.uut.affected_code('file'), affected_code)

        self.uut.delete_line(3)
        affected_code = [
            SourceRange.from_values('file', start_line=1),
            SourceRange.from_values('file', start_line=2, end_line=3)
        ]
        self.assertEqual(self.uut.affected_code('file'), affected_code)

        self.uut.delete_line(4)
        affected_code = [
            SourceRange.from_values('file', start_line=1),
            SourceRange.from_values('file', start_line=2, end_line=4)
        ]
        self.assertEqual(self.uut.affected_code('file'), affected_code)

    def test_len(self):
        self.uut.delete_line(2)
        self.assertEqual(len(self.uut), 1)
        self.uut.add_lines(2, ['2.3', '2.5', '2.6'])
        self.assertEqual(len(self.uut), 4)
        self.uut.change_line(1, '1', '1.1')
        self.assertEqual(len(self.uut), 6)

    def test_stats(self):
        self.uut.delete_line(2)
        self.assertEqual(self.uut.stats(), (0, 1))
        self.uut.add_lines(2, ['2.3', '2.5', '2.6'])
        self.assertEqual(self.uut.stats(), (3, 1))
        self.uut.change_line(1, '1', '1.1')
        self.assertEqual(self.uut.stats(), (4, 2))

    def test_modified(self):
        result_file = ['0.1', '0.2', '1', '1.1', '3.changed', '4']

        self.uut.delete_line(2)
        self.uut.add_lines(0, ['0.1', '0.2'])
        self.uut.add_lines(1, ['1.1'])
        self.uut.change_line(3, '3', '3.changed')

        self.assertEqual(self.uut.modified, result_file)
        self.assertEqual(self.uut.original, self.file)

        self.uut.delete_line(len(self.file))
        del result_file[len(result_file) - 1]
        self.assertEqual(self.uut.modified, result_file)

        self.uut.delete_line(1)
        del result_file[2]
        self.assertEqual(self.uut.modified, result_file)

    def test_addition(self):
        self.assertRaises(TypeError, self.uut.__add__, 5)

        result_file = ['1', '2', '2']

        other = Diff(self.file)
        other.delete_line(1)
        other.change_line(2, '1', '2')
        other.add_lines(0, ['1'])

        self.uut.delete_line(1)
        self.uut.delete_line(3)
        self.uut.change_line(4, '4', '2')
        result = self.uut + other

        self.assertEqual(result.modified, result_file)
        # Make sure it didn't happen in place!
        self.assertNotEqual(self.uut.modified, result_file)

    def test_addition_rename(self):
        uut = Diff(self.file, rename=False)
        other = Diff(self.file, rename=False)
        self.assertEqual((other + uut).rename, False)

        other.rename = 'some.py'
        self.assertEqual((other + uut).rename, 'some.py')

        uut.rename = 'some.py'
        self.assertEqual((other + uut).rename, 'some.py')

        uut.rename = 'other.py'
        self.assertRaises(ConflictError, other.__add__, uut)

    def test_from_string_arrays(self):
        a = ['q', 'a', 'b', 'x', 'c', 'd']
        b = ['a', 'b', 'y', 'c', 'd', 'f']
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

        a = ['first', 'fourth']
        b = ['first', 'second', 'third', 'fourth']
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

        a = ['first', 'fourth']
        b = ['first_changed', 'second', 'third', 'fourth']
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

        a = ['first', 'second', 'third', 'fourth']
        b = ['first', 'fourth']
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

        a = ['first', 'second', 'third', 'fourth']
        b = ['first_changed', 'second_changed', 'fourth']
        self.uut = Diff.from_string_arrays(a, b)
        self.assertEqual(self.uut.modified, b)

    def test_from_clang_fixit(self):
        try:
            from clang.cindex import Index, LibclangError
        except ImportError as err:
            raise unittest.case.SkipTest(str(err))

        joined_file = 'struct { int f0; }\nx = { f0 :1 };\n'
        file = joined_file.splitlines(True)
        fixed_file = ['struct { int f0; }\n', 'x = { .f0 = 1 };\n']
        try:
            tu = Index.create().parse('t.c',
                                      unsaved_files=[('t.c', joined_file)])
        except LibclangError as err:
            raise unittest.case.SkipTest(str(err))

        fixit = tu.diagnostics[0].fixits[0]
        clang_fixed_file = Diff.from_clang_fixit(fixit, file).modified
        self.assertEqual(fixed_file, clang_fixed_file)

    def test_equality(self):
        a = ['first', 'second', 'third']
        b = ['first', 'third']
        diff_1 = Diff.from_string_arrays(a, b)

        a[1] = 'else'
        diff_2 = Diff.from_string_arrays(a, b)
        self.assertEqual(diff_1, diff_2)

        diff_1.rename = 'abcd'
        self.assertNotEqual(diff_1, diff_2)
        diff_1.rename = False

        diff_1.delete = True
        self.assertNotEqual(diff_1, diff_2)
        diff_1.delete = False

        diff_1.add_lines(1, ['1'])
        self.assertNotEqual(diff_1, diff_2)

    def test_json_export(self):
        JSONEncoder = create_json_encoder()
        a = ['first\n', 'second\n', 'third\n']
        b = ['first\n', 'third\n']
        diff = Diff.from_string_arrays(a, b)
        self.assertEqual(
            json.dumps(diff, cls=JSONEncoder, sort_keys=True), '"--- \\n'
            '+++ \\n'
            '@@ -1,3 +1,2 @@\\n'
            ' first\\n'
            '-second\\n'
            ' third\\n"')

    def test_rename(self):
        self.uut.rename = False
        self.uut.rename = '1234'
        with self.assertRaises(TypeError):
            self.uut.rename = True
        with self.assertRaises(TypeError):
            self.uut.rename = 1234

    def test_delete(self):
        self.uut.delete = True
        self.uut.delete = False
        # Double deletion is allowed
        self.uut.delete = False
        with self.assertRaises(TypeError):
            self.uut.delete = 'abcd'

        # If delete is True then modified returns an empty list
        self.uut.delete = True
        self.assertEqual(self.uut.modified, [])
        self.uut.delete = False