def test_apply(self):
        uut = ShowAppliedPatchesAction()

        with make_temp() as f_a, make_temp() as f_b, make_temp() as f_c:

            file_dict = {
                f_a: ['1\n', '2\n', '3\n'],
                f_b: ['1\n', '2\n', '3\n'],
                f_c: ['1\n', '2\n', '3\n']
            }
            expected_file_dict = {
                f_a: ['1\n', '3_changed\n'],
                f_b: ['1\n', '2\n', '3_changed\n'],
                f_c: ['1\n', '2\n', '3\n']
            }

            with make_temp() as testfile_path:
                file_diff_dict = {}
                file_dict = {testfile_path: ['1\n', '2\n', '3\n']}
                diff = Diff(file_dict[testfile_path])
                diff.delete_line(2)
                diff.change_line(3, '3\n', '3_changed\n')
                result = Result('origin', 'msg', diffs={f_a: diff},
                                applied_actions={'ApplyPatchAction': [Result(
                                    'origin', 'message',
                                    diffs={testfile_path: diff}),
                                    file_dict, file_diff_dict, Section('')]})

                self.assertTrue(uut.apply(result,
                                          file_dict,
                                          file_diff_dict))
    def test_print_result_no_input(self):
        with make_temp() as testfile_path:
            file_dict = {testfile_path: ["1\n", "2\n", "3\n"]}
            diff = Diff(file_dict[testfile_path])
            diff.delete_line(2)
            diff.change_line(3, "3\n", "3_changed\n")
            with simulate_console_inputs(1, 2, 3) as generator, retrieve_stdout() as stdout:
                ApplyPatchAction.is_applicable = staticmethod(lambda *args: True)
                print_results_no_input(
                    self.log_printer,
                    Section("someSection"),
                    [Result("origin", "message", diffs={testfile_path: diff})],
                    file_dict,
                    self.file_diff_dict,
                    color=False,
                )
                self.assertEqual(generator.last_input, -1)
                self.assertEqual(
                    stdout.getvalue(),
                    """
Project wide:
|    | [NORMAL] origin:
|    | message
""",
                )
Exemple #3
0
    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_finalize_backup_fail(self):
        def raise_error(file, mode=""):
            raise PermissionException

        _open = builtins.open
        _copy2 = shutil.copy2

        try:
            builtins.open = raise_error
            builtins.__dict__["open"] = raise_error
            shutil.copy2 = lambda src, dst: self.assertEqual(src+".orig", dst)

            diff = Diff()
            diff.delete_line(2)

            # Should catch the backup permission error during write-back.
            finalize({"f": diff}, {"f": ["1", "2", "3"]})

            # Test logging output.
            finalize({"f": diff}, {"f": ["1", "2"]}, log_printer=None)

            logger = LogPrinter(StringPrinter())
            finalize({"f": diff}, {"f": ["1"]}, log_printer=logger)
            self.assertIn("Can't backup, writing patch to file f failed.",
                          logger.printer.string)

        finally:
            builtins.open = _open
            shutil.copy2 = _copy2
    def test_redirect_threshold(self):
        long_url_redirect = """
        https://bitbucket.org/api/301
        https://bitbucket.org/api/302
        """.splitlines()

        short_url_redirect = """
        http://httpbin.org/status/301
        """.splitlines()

        with requests_mock.Mocker() as m:
            m.add_matcher(custom_matcher)

            self.check_validity(self.uut, long_url_redirect,
                                settings={'follow_redirects': 'true'})

            with prepare_file(short_url_redirect, None,
                              create_tempfile=False) as (lines, _):
                diff = Diff(lines)
                diff.modify_line(2,
                                 '        http://httpbin.org/get\n')

            self.check_results(
                self.uut,
                short_url_redirect,
                [Result.from_values(
                    'InvalidLinkBear',
                    'This link redirects to http://httpbin.org/get',
                    severity=RESULT_SEVERITY.NORMAL,
                    line=2,
                    file='short_url_redirect_text',
                    diffs={'short_url_redirect_text': diff},
                )],
                settings={'follow_redirects': 'true'},
                filename='short_url_redirect_text')
    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})
    def process_output(self, output, filename, file):
        output = json.loads(output)

        for issue in output:
            diff = Diff(file)
            from_lines = issue['from'].splitlines()
            to_lines = issue['to'].splitlines()
            assert len(from_lines) == len(to_lines)
            for other_lines in range(1, len(from_lines)):
                assert from_lines[other_lines] == to_lines[other_lines]
            line_nr = issue['startLine']
            line_to_change = file[line_nr-1]
            newline = line_to_change.replace(from_lines[0], to_lines[0])
            diff.change_line(line_nr, line_to_change, newline)

            yield Result.from_values(
                origin=self,
                message=issue['hint'],
                file=filename,
                severity=self.severity_map[issue['severity']],
                line=issue['startLine'],
                column=issue['startColumn'],
                end_line=issue['endLine'],
                end_column=issue['endColumn'],
                diffs={filename: diff})
Exemple #8
0
    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)
Exemple #9
0
    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)
Exemple #10
0
    def apply(self, result, original_file_dict, file_diff_dict, language: str,
              no_orig: bool=False):
        """
        Add ignore comment
        """

        ignore_comment = self.get_ignore_comment(result.origin, language)

        if not ignore_comment:
            return file_diff_dict

        source_range = next(filter(lambda sr: exists(sr.file),
                                   result.affected_code))
        filename = source_range.file

        ignore_diff = Diff(original_file_dict[filename])
        ignore_diff.change_line(
            source_range.start.line,
            original_file_dict[filename][source_range.start.line-1],
            original_file_dict[filename][source_range.start.line-1].rstrip() +
            '  ' + ignore_comment)

        if filename in file_diff_dict:
            ignore_diff = file_diff_dict[filename] + ignore_diff
        else:
            if not no_orig and isfile(filename):
                shutil.copy2(filename, filename + '.orig')

        file_diff_dict[filename] = ignore_diff

        new_filename = ignore_diff.rename if ignore_diff.rename else filename
        with open(new_filename, mode='w', encoding='utf-8') as file:
            file.writelines(ignore_diff.modified)

        return file_diff_dict
Exemple #11
0
    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_apply_rename(self):
        # Initial file contents, *before* a patch was applied
        file_dict = {
            self.fa: ["1\n", "2\n", "3\n"]}

        # A patch that was applied for some reason to make things complicated
        file_diff_dict = {}
        diff = Diff(file_dict[self.fa], rename=self.fa+".renamed")
        diff.change_line(3, "3\n", "3_changed\n")
        ApplyPatchAction().apply(
            Result("origin", "msg", diffs={self.fa: diff}),
            file_dict,
            file_diff_dict)
        # End file contents after the patch and the OpenEditorAction was
        # applied
        expected_file_dict = {
            self.fa: ["1\n", "3_changed\n"]}

        section = Section("")
        section.append(Setting("editor", ""))
        uut = OpenEditorAction()
        subprocess.call = self.fake_edit
        diff_dict = uut.apply_from_section(
            Result.from_values("origin", "msg", self.fa),
            file_dict,
            file_diff_dict,
            section)

        for filename in diff_dict:
            file_dict[filename] = (
                file_diff_dict[filename].modified)

        self.assertEqual(file_dict, expected_file_dict)
        open(self.fa, 'w').close()
def generate_spacing_diff(file, filename, line, line_number,
                          match_object, required_spacing):
    """
    Generate a diff for incorrectly spaced control or variable tags.

    :param match_object:     A Match object containing the groups ``open``,
                             ``close`` containing the opening and closing
                             delimiters of a Jinja2 tag and ``content``
                             containing everything in between.
    :param required_spacing: The number of spaces expected after the ``open``
                             delimiter and before the ``close`` delimiter
    """
    diff = Diff(file)

    content_before = line[:match_object.start('open')]
    content_after = line[match_object.end('close'):]

    spacing = ' ' * required_spacing
    replacement = (
        '{before}{open}{spacing}{content}{spacing}{close}{after}'.format(
            before=content_before,
            spacing=spacing,
            after=content_after,
            content=match_object.group('content').strip(),
            open=match_object.group('open'),
            close=match_object.group('close')))
    diff.change_line(
        line_number,
        line,
        replacement)
    return {filename: diff}
    def test_is_applicable_conflict(self):
        diff = Diff(["1\n", "2\n", "3\n"])
        diff.add_lines(2, ['a line'])

        conflict_result = Result("", "", diffs={'f': diff})
        # Applying the same diff twice will result in a conflict
        self.assertFalse(
            ApplyPatchAction.is_applicable(conflict_result, {}, {'f': diff}))
Exemple #15
0
    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 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})
    def run(self, filename, file,
            timeout: int=DEFAULT_TIMEOUT,
            ignore_regex: str="[.\/]example\.com"):
        """
        Find links in any text file and check if they are valid.

        A link is considered valid if the server responds with a 2xx code.

        This bear can automatically fix redirects, but ignores redirect
        URLs that have a huge difference with the original URL.

        :param timeout:      Request timeout period.
        :param ignore_regex: A regex for urls to ignore.
        """
        for line_number, link, code in InvalidLinkBear.find_links_in_file(
                file, timeout, ignore_regex):
            if code is None:
                yield Result.from_values(
                    origin=self,
                    message=('Broken link - unable to connect to '
                             '{url}').format(url=link),
                    file=filename,
                    line=line_number,
                    severity=RESULT_SEVERITY.MAJOR)
            elif not 200 <= code < 300:
                # HTTP status 404, 410 or 50x
                if code in (404, 410) or 500 <= code < 600:
                    yield Result.from_values(
                        origin=self,
                        message=('Broken link - unable to connect to {url} '
                                 '(HTTP Error: {code})'
                                 ).format(url=link, code=code),
                        file=filename,
                        line=line_number,
                        severity=RESULT_SEVERITY.NORMAL)
                if 300 <= code < 400:  # HTTP status 30x
                    redirect_url = requests.head(link, allow_redirects=True).url
                    matcher = SequenceMatcher(
                        None, redirect_url, link)
                    if (matcher.real_quick_ratio() > 0.7 and
                            matcher.ratio()) > 0.7:
                        diff = Diff(file)
                        current_line = file[line_number - 1]
                        start = current_line.find(link)
                        end = start + len(link)
                        replacement = current_line[:start] + \
                            redirect_url + current_line[end:]
                        diff.change_line(line_number, current_line, replacement)

                        yield Result.from_values(
                            self,
                            'This link redirects to ' + redirect_url,
                            diffs={filename: diff},
                            file=filename,
                            line=line_number,
                            severity=RESULT_SEVERITY.NORMAL)
    def test_is_applicable_conflict(self):
        diff = Diff(['1\n', '2\n', '3\n'])
        diff.add_lines(2, ['a line'])

        conflict_result = Result('', '', diffs={'f': diff})
        # Applying the same diff twice will result in a conflict
        self.assertIn(
            'Two or more patches conflict with each other: ',
            ApplyPatchAction.is_applicable(conflict_result, {}, {'f': diff})
        )
    def test_add(self):
        file_dict = {
            "f_a": ["1", "2", "3"],
            "f_b": ["1", "2", "3"],
            "f_c": ["1", "2", "3"]
        }
        expected_file_dict = {
            "f_a": ["1", "3_changed"],
            "f_b": ["1", "2", "3_changed"],
            "f_c": ["1", "2", "3"]
        }

        diff = Diff()
        diff.delete_line(2)
        uut1 = PatchResult("origin", "msg", {"f_a": diff})

        diff = Diff()
        diff.change_line(3, "3", "3_changed")
        uut2 = PatchResult("origin", "msg", {"f_a": diff})

        diff = Diff()
        diff.change_line(3, "3", "3_changed")
        uut3 = PatchResult("origin", "msg", {"f_b": diff})

        uut1 += uut2 + uut3
        uut1.apply(file_dict)

        self.assertEqual(file_dict, expected_file_dict)
    def test_apply(self):
        uut = ApplyPatchAction()
        file_dict = {
            "f_a": ["1", "2", "3"],
            "f_b": ["1", "2", "3"],
            "f_c": ["1", "2", "3"]
        }
        expected_file_dict = {
            "f_a": ["1", "3_changed"],
            "f_b": ["1", "2", "3_changed"],
            "f_c": ["1", "2", "3"]
        }

        file_diff_dict = {}

        diff = Diff()
        diff.delete_line(2)
        uut.apply_from_section(PatchResult("origin", "msg", {"f_a": diff}), file_dict, file_diff_dict, Section("t"))

        diff = Diff()
        diff.change_line(3, "3", "3_changed")
        uut.apply_from_section(PatchResult("origin", "msg", {"f_a": diff}), file_dict, file_diff_dict, Section("t"))

        diff = Diff()
        diff.change_line(3, "3", "3_changed")
        uut.apply(PatchResult("origin", "msg", {"f_b": diff}), file_dict, file_diff_dict)

        for filename in file_diff_dict:
            file_dict[filename] = file_diff_dict[filename].apply(file_dict[filename])

        self.assertEqual(file_dict, expected_file_dict)
Exemple #21
0
    def test_apply(self):
        file_dict = {"f_a": ["1", "2", "3"], "f_b": ["1", "2", "3"]}
        expected_file_dict = {"f_a": ["1", "3_changed"], "f_b": ["1", "2", "3"]}
        diff = Diff(file_dict["f_a"])
        diff.delete_line(2)
        diff.change_line(3, "3", "3_changed")

        uut = Result("origin", "msg", diffs={"f_a": diff})
        uut.apply(file_dict)

        self.assertEqual(file_dict, expected_file_dict)
Exemple #22
0
    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_apply(self):
        uut = ApplyPatchAction()
        fh_a, f_a = mkstemp()
        fh_b, f_b = mkstemp()
        fh_c, f_c = mkstemp()
        os.close(fh_a)
        os.close(fh_b)
        os.close(fh_c)

        file_dict = {
            f_a: ["1\n", "2\n", "3\n"],
            f_b: ["1\n", "2\n", "3\n"],
            f_c: ["1\n", "2\n", "3\n"]
        }
        expected_file_dict = {
            f_a: ["1\n", "3_changed\n"],
            f_b: ["1\n", "2\n", "3_changed\n"],
            f_c: ["1\n", "2\n", "3\n"]
        }

        file_diff_dict = {}

        diff = Diff(file_dict[f_a])
        diff.delete_line(2)
        uut.apply_from_section(Result("origin", "msg", diffs={f_a: diff}),
                               file_dict,
                               file_diff_dict,
                               Section("t"))

        diff = Diff(file_dict[f_a])
        diff.change_line(3, "3\n", "3_changed\n")
        uut.apply_from_section(Result("origin", "msg", diffs={f_a: diff}),
                               file_dict,
                               file_diff_dict,
                               Section("t"))

        diff = Diff(file_dict[f_b])
        diff.change_line(3, "3\n", "3_changed\n")
        uut.apply(Result("origin", "msg", diffs={f_b: diff}),
                  file_dict,
                  file_diff_dict)

        for filename in file_diff_dict:
            file_dict[filename] = file_diff_dict[filename].modified

        self.assertEqual(file_dict, expected_file_dict)
        with open(f_a) as fa:
            self.assertEqual(file_dict[f_a], fa.readlines())
        with open(f_b) as fb:
            self.assertEqual(file_dict[f_b], fb.readlines())
        with open(f_c) as fc:
            # File c is unchanged and should be untouched
            self.assertEqual([], fc.readlines())

        os.remove(f_a)
        os.remove(f_b)
        os.remove(f_c)
    def test_acquire_actions_and_apply_single(self):
        with make_temp() as testfile_path:
            file_dict = {testfile_path: ['1\n', '2\n', '3\n']}
            diff = Diff(file_dict[testfile_path])
            diff.delete_line(2)
            diff.change_line(3, '3\n', '3_changed\n')
            with simulate_console_inputs('a', 'n') as generator:
                with retrieve_stdout() as sio:
                    ApplyPatchAction.is_applicable = staticmethod(
                        lambda *args: True)
                    acquire_actions_and_apply(self.console_printer,
                                              Section(''),
                                              self.file_diff_dict,
                                              Result(
                                                  'origin', 'message',
                                                  diffs={testfile_path: diff}),
                                              file_dict, apply_single=True)
                    self.assertEqual(generator.last_input, -1)
                    self.assertIn('', sio.getvalue())

            class InvalidateTestAction(ResultAction):

                is_applicable = staticmethod(lambda *args: True)

                def apply(*args, **kwargs):
                    ApplyPatchAction.is_applicable = staticmethod(
                        lambda *args: 'ApplyPatchAction cannot be applied.')

            old_applypatch_is_applicable = ApplyPatchAction.is_applicable
            ApplyPatchAction.is_applicable = staticmethod(lambda *args: True)
            cli_actions = [ApplyPatchAction(), InvalidateTestAction()]

            with simulate_console_inputs('a') as generator:
                with retrieve_stdout() as sio:
                    acquire_actions_and_apply(self.console_printer,
                                              Section(''),
                                              self.file_diff_dict,
                                              Result(
                                                  'origin', 'message',
                                                  diffs={testfile_path: diff}),
                                              file_dict,
                                              cli_actions=cli_actions,
                                              apply_single=True)
                    self.assertEqual(generator.last_input, -1)

                    action_fail = 'Failed to execute the action'
                    self.assertNotIn(action_fail, sio.getvalue())

                    apply_path_desc = ApplyPatchAction().get_metadata().desc
                    self.assertEqual(sio.getvalue().count(apply_path_desc), 0)

            ApplyPatchAction.is_applicable = old_applypatch_is_applicable
Exemple #25
0
    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_acquire_actions_and_apply(self):
        with make_temp() as testfile_path:
            file_dict = {testfile_path: ["1\n", "2\n", "3\n"]}
            diff = Diff(file_dict[testfile_path])
            diff.delete_line(2)
            diff.change_line(3, "3\n", "3_changed\n")
            with simulate_console_inputs(1, 0) as generator, \
                    retrieve_stdout() as sio:
                ApplyPatchAction.is_applicable = staticmethod(
                        lambda *args: True)
                acquire_actions_and_apply(self.console_printer,
                                          self.log_printer,
                                          Section(""),
                                          self.file_diff_dict,
                                          Result("origin", "message", diffs={
                                           testfile_path: diff}),
                                          file_dict)
                self.assertEqual(generator.last_input, 1)
                self.assertIn(ApplyPatchAction.success_message, sio.getvalue())

            class InvalidateTestAction(ResultAction):

                is_applicable = staticmethod(lambda *args: True)

                def apply(*args, **kwargs):
                    ApplyPatchAction.is_applicable = staticmethod(
                        lambda *args: False)

            old_applypatch_is_applicable = ApplyPatchAction.is_applicable
            ApplyPatchAction.is_applicable = staticmethod(lambda *args: True)
            cli_actions = [ApplyPatchAction(), InvalidateTestAction()]

            with simulate_console_inputs(2, 1, 0) as generator, \
                    retrieve_stdout() as sio:
                acquire_actions_and_apply(self.console_printer,
                                          self.log_printer,
                                          Section(""),
                                          self.file_diff_dict,
                                          Result("origin", "message",
                                                 diffs={testfile_path: diff}),
                                          file_dict,
                                          cli_actions=cli_actions)
                self.assertEqual(generator.last_input, 2)

                action_fail = "Failed to execute the action"
                self.assertNotIn(action_fail, sio.getvalue())

                apply_path_desc = ApplyPatchAction().get_metadata().desc
                self.assertEqual(sio.getvalue().count(apply_path_desc), 1)

            ApplyPatchAction.is_applicable = old_applypatch_is_applicable
Exemple #27
0
    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 process_output(self, output, filename, file):
        if not file:
            return

        output = json.loads(output)
        lines = "".join(file)

        assert len(output) == 1

        for result in output[0]['messages']:
            if 'fix' not in result:
                diffs = None
            else:
                fix = result['fix']
                start, end = fix['range']
                replacement_text = fix['text']
                new_output = lines[:start] + replacement_text + lines[end:]
                diffs = {filename: Diff.from_string_arrays(
                    lines.splitlines(True), new_output.splitlines(True))}

            origin = (
                "{class_name} ({rule})".format(class_name=type(self).__name__,
                                               rule=result['ruleId'])
                if result['ruleId'] is not None else self)
            yield Result.from_values(
                origin=origin, message=result['message'],
                file=filename, line=result['line'], diffs=diffs,
                severity=self.severity_map[result['severity']])
    def run(self, filename, file,
            max_line_length: int=80,
            tab_width: int=SpacingHelper.DEFAULT_TAB_WIDTH,
            pep_ignore: typed_list(str)=(),
            pep_select: typed_list(str)=(),
            local_pep8_config: bool=False):
        """
        Detects and fixes PEP8 incompliant code. This bear will not change
        functionality of the code in any way.

        :param max_line_length:   Maximum number of characters for a line.
        :param tab_width:         Number of spaces per indent level.
        :param pep_ignore:        A list of errors/warnings to ignore.
        :param pep_select:        A list of errors/warnings to exclusively
                                  apply.
        :param local_pep8_config: Set to true if autopep8 should use a config
                                  file as if run normally from this directory.
        """
        options = {"ignore": pep_ignore,
                   "select": pep_select,
                   "max_line_length": max_line_length,
                   "indent_size": tab_width}

        corrected = autopep8.fix_code(''.join(file),
                                      apply_config=local_pep8_config,
                                      options=options).splitlines(True)

        diffs = Diff.from_string_arrays(file, corrected).split_diff()

        for diff in diffs:
            yield Result(self,
                         "The code does not comply to PEP8.",
                         affected_code=(diff.range(filename),),
                         diffs={filename: diff})
Exemple #30
0
    def run(self,
            filename,
            file,
            max_line_length: int = 79,
            indent_size: int = SpacingHelper.DEFAULT_TAB_WIDTH,
            allow_multiline_lambdas: bool = False,
            blank_line_before_nested_class_or_def: bool = False,
            continuation_tab_width: int = SpacingHelper.DEFAULT_TAB_WIDTH,
            dedent_closing_brackets: bool = False,
            indent_dictionary_value: bool = False,
            coalesce_brackets: bool = False,
            join_multiple_lines: bool = True,
            spaces_around_power_operator: bool = True,
            spaces_before_comment: int = 2,
            space_between_ending_comma_and_closing_bracket: bool = False,
            split_arguments_when_comma_terminated: bool = False,
            split_before_bitwise_operator: bool = False,
            split_before_first_argument: bool = False,
            split_before_logical_operator: bool = False,
            split_before_named_assigns: bool = True,
            use_spaces: bool = True,
            based_on_style: str = 'pep8'):
        """
        :param max_line_length:
            Maximum number of characters for a line.
        :param indent_size:
            Number of spaces per indentation level.
        :param allow_multiline_lambdas:
            Allows lambdas to be formatted on more than one line.
        :param blank_line_before_nested_class_or_def:
            Inserts a blank line before a ``def`` or ``class`` immediately
            nested within another ``def`` or ``class``.
        :param continuation_tab_width:
            Indent width used for line continuations.
        :param dedent_closing_brackets:
            Puts closing brackets on a separate line, dedented, if the
            bracketed expression can't fit in a single line. Applies to all
            kinds of brackets, including function definitions and calls.
        :param indent_dictionary_value:
            Indents the dictionary value if it cannot fit on the same line as
            the dictionary key.
        :param coalesce_brackets:
            Prevents splitting consecutive brackets. Only relevant when
            ``dedent_closing_brackets`` is set.
            Example:
            If ``True``,

            ```
            call_func_that_takes_a_dict(
                {
                    'key1': 'value1',
                    'key2': 'value2',
                }
            )
            ```
            would reformat to:
            ```
            call_func_that_takes_a_dict({
                'key1': 'value1',
                'key2': 'value2',
            })
            ```
        :param join_multiple_lines:
            Joins short lines into one line.
        :param spaces_around_power_operator:
            Set to ``True`` to prefer using spaces around ``**``.
        :param spaces_before_comment:
            The number of spaces required before a trailing comment.
        :param space_between_ending_comma_and_closing_bracket:
            Inserts a space between the ending comma and closing bracket of a
            list, etc.
        :param split_arguments_when_comma_terminated:
            Splits before arguments if the argument list is terminated by a
            comma.
        :param split_before_bitwise_operator:
            Set to ``True`` to prefer splitting before ``&``, ``|`` or ``^``
            rather than after.
        :param split_before_first_argument:
            If an argument / parameter list is going to be split, then split
            before the first argument.
        :param split_before_logical_operator:
            Set to ``True`` to prefer splitting before ``and`` or ``or`` rather
            than after.
        :param split_before_named_assigns:
            Splits named assignments into individual lines.
        :param use_spaces:
            Uses spaces for indentation.
        :param based_on_style:
            The formatting style to be used as reference.
        """

        options = """
[style]
indent_width = {indent_size}
column_limit = {max_line_length}
allow_multiline_lambdas = {allow_multiline_lambdas}
continuation_indent_width = {continuation_tab_width}
dedent_closing_brackets = {dedent_closing_brackets}
indent_dictionary_value = {indent_dictionary_value}
join_multiple_lines = {join_multiple_lines}
spaces_around_power_operator = {spaces_around_power_operator}
spaces_before_comment = {spaces_before_comment}
coalesce_brackets = {coalesce_brackets}
split_before_bitwise_operator = {split_before_bitwise_operator}
split_before_first_argument = {split_before_first_argument}
split_before_logical_operator = {split_before_logical_operator}
split_before_named_assigns = {split_before_named_assigns}
based_on_style = {based_on_style}
blank_line_before_nested_class_or_def = {blank_line_before_nested_class_or_def}
split_arguments_when_comma_terminated = {split_arguments_when_comma_terminated}
space_between_ending_comma_and_closing_bracket= \
{space_between_ending_comma_and_closing_bracket}
"""
        options += 'use_tabs = ' + str(not use_spaces)
        options = options.format(**locals())

        with prepare_file(options.splitlines(keepends=True),
                          None) as (file_, fname):
            corrected = FormatFile(filename,
                                   style_config=fname)[0].splitlines(True)
        diffs = Diff.from_string_arrays(file, corrected).split_diff()
        for diff in diffs:
            yield Result(self, "The code does not comply with the settings "
                         "provided.",
                         affected_code=(diff.range(filename), ),
                         diffs={filename: diff})
Exemple #31
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([]), [])
Exemple #32
0
    def run(self,
            filename,
            file,
            language: str,
            docstyle: str = 'default',
            allow_missing_func_desc: str = False,
            indent_size: int = 4):
        """
        Checks for certain in-code documentation styles.

        It currently checks for the following style: ::

            The first line needs to have no indentation.
                - Following lines can have indentation

            :param x:
                4 space indent
            :return:
                also 4 space indent
                following lines are also 4 space indented

        :param language: The programming language of the file(s).
        :param docstyle: The docstyle to use. For example ``default`` or
                         ``doxygen``. Docstyles are language dependent, meaning
                         not every language is supported by a certain docstyle.
        :param allow_missing_func_desc: When set ``True`` this will allow
                         functions with missing descriptions, allowing
                         functions to start with params.
        :param indent_size: Number of spaces per indentation level.
        """
        for doc_comment in extract_documentation(file, language, docstyle):
            parsed = doc_comment.parse()
            metadata = iter(parsed)

            # Assuming that the first element is always the only main
            # description.
            main_description = next(metadata)

            if main_description.desc == '\n' and not allow_missing_func_desc:
                warning_desc = """
Missing function description.
Please set allow_missing_func_desc = True to ignore this warning.
"""
            else:
                warning_desc = 'Documentation does not have correct style.'

            # one empty line shall follow main description (except it's empty
            # or no annotations follow).
            if main_description.desc.strip() != '':
                main_description = main_description._replace(
                    desc='\n' + main_description.desc.strip() + '\n' *
                    (1 if len(parsed) == 1 else 2))

            new_metadata = [main_description]
            for m in metadata:
                # Split newlines and remove leading and trailing whitespaces.
                stripped_desc = list(map(str.strip, m.desc.splitlines()))

                if len(stripped_desc) == 0:
                    # Annotations should be on their own line, though no
                    # further description follows.
                    stripped_desc.append('')
                else:
                    # Wrap parameter description onto next line if it follows
                    # annotation directly.
                    if stripped_desc[0] != '':
                        stripped_desc.insert(0, '')

                # Indent with 4 spaces.
                stripped_desc = ('' if line == '' else ' ' * indent_size + line
                                 for line in stripped_desc)

                new_desc = '\n'.join(stripped_desc)

                # Strip away trailing whitespaces and obsolete newlines (except
                # one newline which is mandatory).
                new_desc = new_desc.rstrip() + '\n'

                new_metadata.append(m._replace(desc=new_desc.lstrip(' ')))

            new_comment = DocumentationComment.from_metadata(
                new_metadata, doc_comment.docstyle_definition,
                doc_comment.marker, doc_comment.indent, doc_comment.position)

            if new_comment != doc_comment:
                # Something changed, let's apply a result.
                diff = Diff(file)

                # We need to update old comment positions, as `assemble()`
                # prepends indentation for first line.
                old_range = TextRange.from_values(doc_comment.range.start.line,
                                                  1,
                                                  doc_comment.range.end.line,
                                                  doc_comment.range.end.column)
                diff.replace(old_range, new_comment.assemble())

                yield Result(origin=self,
                             message=warning_desc,
                             affected_code=(diff.range(filename), ),
                             diffs={filename: diff})
Exemple #33
0
    def run(self,
            filename,
            file,
            timeout: int = DEFAULT_TIMEOUT,
            link_ignore_regex: str = "([.\/]example\.com|\{|\$)",
            follow_redirects: bool = False):
        """
        Find links in any text file and check if they are valid.

        A link is considered valid if the server responds with a 2xx code.

        This bear can automatically fix redirects, but ignores redirect
        URLs that have a huge difference with the original URL.

        Warning: This bear will make HEAD requests to all URLs mentioned in
        your codebase, which can potentially be destructive. As an example,
        this bear would naively just visit the URL from a line that goes like
        `do_not_ever_open = 'https://api.acme.inc/delete-all-data'` wiping out
        all your data.

        :param timeout:          Request timeout period.
        :param link_ignore_regex:     A regex for urls to ignore.
        :param follow_redirects: Set to true to autocorrect redirects.
        """
        for line_number, link, code in InvalidLinkBear.find_links_in_file(
                file, timeout, link_ignore_regex):
            if code is None:
                yield Result.from_values(
                    origin=self,
                    message=('Broken link - unable to connect to '
                             '{url}').format(url=link),
                    file=filename,
                    line=line_number,
                    severity=RESULT_SEVERITY.MAJOR)
            elif not 200 <= code < 300:
                # HTTP status 404, 410 or 50x
                if code in (404, 410) or 500 <= code < 600:
                    yield Result.from_values(
                        origin=self,
                        message=('Broken link - unable to connect to {url} '
                                 '(HTTP Error: {code})').format(url=link,
                                                                code=code),
                        file=filename,
                        line=line_number,
                        severity=RESULT_SEVERITY.NORMAL)
                if follow_redirects and 300 <= code < 400:  # HTTP status 30x
                    redirect_url = requests.head(link,
                                                 allow_redirects=True).url
                    matcher = SequenceMatcher(None, redirect_url, link)
                    if (matcher.real_quick_ratio() > 0.7
                            and matcher.ratio()) > 0.7:
                        diff = Diff(file)
                        current_line = file[line_number - 1]
                        start = current_line.find(link)
                        end = start + len(link)
                        replacement = current_line[:start] + \
                            redirect_url + current_line[end:]
                        diff.change_line(line_number, current_line,
                                         replacement)

                        yield Result.from_values(
                            self,
                            'This link redirects to ' + redirect_url,
                            diffs={filename: diff},
                            file=filename,
                            line=line_number,
                            severity=RESULT_SEVERITY.NORMAL)
Exemple #34
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
Exemple #35
0
class DiffTestCase(unittest.TestCase):
    def setUp(self):
        self.uut = Diff()

    def test_add_lines(self):
        self.uut.add_lines(0, [])
        self.uut.add_lines(0, ["t"])
        self.uut.add_lines(0, [])
        self.assertRaises(ConflictError, self.uut.add_lines, 0,
                          ["t"])  # No double addition allowed
        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)
        self.assertRaises(AssertionError, self.uut.change_line, 1, "1",
                          "2")  # Line was deleted, unchangeable

    def test_apply(self):
        file = ["1", "2", "3", "4"]

        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.apply(file), result_file)

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

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

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

        file = ["1", "1", "3", "4"]

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

        other = Diff()
        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")
        self.uut += other

        self.assertEqual(self.uut.apply(file), result_file)
Exemple #36
0
 def test_is_applicable(self):
     diff = Diff(["1\n", "2\n", "3\n"])
     diff.delete_line(2)
     patch_result = Result("", "", diffs={'f': diff})
     self.assertTrue(
         ApplyPatchAction.is_applicable(patch_result, {}, {}))
Exemple #37
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})
Exemple #38
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"')
Exemple #39
0
    def setUp(self):
        diff0 = Diff(['coler'])
        diff0.modify_line(1, 'color')

        diff1 = Diff(['coler'])
        diff1.modify_line(1, 'colour')

        diff2 = Diff(['coler'])
        diff2.modify_line(1, 'cooler')

        diff3 = Diff(['coler'])
        diff3.modify_line(1, 'coder')

        self.original_diff = {'filename': diff0}
        self.alternate_diff1 = {'filename': diff1}
        self.alternate_diff2 = {'filename': diff2}
        self.alternate_diff3 = {'filename': diff3}
        self.alternate_diffs = [
            self.alternate_diff1, self.alternate_diff2, self.alternate_diff3
        ]
        self.result = Result('origin',
                             'message',
                             diffs=self.original_diff,
                             alternate_diffs=self.alternate_diffs)
        self.original_file_dict = {'filename': ['coler']}
        self.section = Section('name')
        self.section.append(Setting('no_color', 'True'))

        self.uut1 = AlternatePatchAction(self.alternate_diff1, 1)
        self.uut2 = AlternatePatchAction(self.alternate_diff2, 2)
        self.uut3 = AlternatePatchAction(self.alternate_diff3, 3)
Exemple #40
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
    def run(
        self,
        filename,
        file,
        max_line_length: int = 0,
        json_sort: bool = False,
        indent_size: int = SpacingHelper.DEFAULT_TAB_WIDTH,
        escape_unicode: bool = True,
    ):
        """
        Raises issues for any deviations from the pretty-printed JSON.

        :param max_line_length: Maximum number of characters for a line.
                                When set to 0 allows infinite line length.
        :param json_sort:       Whether or not keys should be sorted.
        :param indent_size:     Number of spaces per indentation level.
        :param escape_unicode:  Whether or not to escape unicode values using
                                ASCII.
        """
        if not max_line_length:
            max_line_length = sys.maxsize

        # Output a meaningful message if empty file given as input
        if len(file) == 0:
            yield Result.from_values(self,
                                     'This file is empty.',
                                     file=filename)
            return

        try:
            json_content = json.loads(''.join(file),
                                      object_pairs_hook=OrderedDict)
        except JSONDecodeError as err:
            err_content = match(r'(.*): line (\d+) column (\d+)', str(err))
            yield Result.from_values(
                self,
                'This file does not contain parsable JSON. ' +
                err_content.group(1) + '.',
                file=filename,
                line=int(err_content.group(2)),
                column=int(err_content.group(3)))
            return

        for line_number, line in enumerate(file):
            line = line.expandtabs(indent_size)
            if len(line) > max_line_length + 1:
                yield Result.from_values(
                    origin=self,
                    message='Line is longer than allowed.'
                    ' ({actual} > {maximum})'.format(actual=len(line) - 1,
                                                     maximum=max_line_length),
                    file=filename,
                    line=line_number + 1,
                    column=max_line_length + 1,
                    end_line=line_number + 1,
                    end_column=len(line),
                )

        corrected = json.dumps(json_content,
                               sort_keys=json_sort,
                               indent=indent_size,
                               ensure_ascii=escape_unicode).splitlines(True)
        # Because of a bug in several python versions we have to correct
        # whitespace here.
        corrected = tuple(line.rstrip(' \n') + '\n' for line in corrected)
        diff = Diff.from_string_arrays(file, corrected)

        if len(diff) > 0:
            yield Result(self,
                         'This file can be reformatted by sorting keys and '
                         'following indentation.',
                         affected_code=tuple(
                             d.range(filename) for d in diff.split_diff()),
                         diffs={filename: diff})
    def test_is_applicable(self):
        diff = Diff([], rename='new_name')
        result = Result('', '', diffs={'f': diff})

        self.assertTrue(self.uut.is_applicable(result, {}, {'f': diff}))
Exemple #43
0
 def setUp(self):
     self.file = ["1", "2", "3", "4"]
     self.uut = Diff(self.file)
Exemple #44
0
    def test_apply(self):
        uut = ApplyPatchAction()
        with make_temp() as f_a, make_temp() as f_b, make_temp() as f_c:

            file_dict = {
                f_a: ["1\n", "2\n", "3\n"],
                f_b: ["1\n", "2\n", "3\n"],
                f_c: ["1\n", "2\n", "3\n"]
            }
            expected_file_dict = {
                f_a: ["1\n", "3_changed\n"],
                f_b: ["1\n", "2\n", "3_changed\n"],
                f_c: ["1\n", "2\n", "3\n"]
            }

            file_diff_dict = {}

            diff = Diff(file_dict[f_a])
            diff.delete_line(2)
            uut.apply_from_section(Result("origin", "msg", diffs={f_a: diff}),
                                   file_dict,
                                   file_diff_dict,
                                   Section("t"))

            diff = Diff(file_dict[f_a])
            diff.change_line(3, "3\n", "3_changed\n")
            uut.apply_from_section(Result("origin", "msg", diffs={f_a: diff}),
                                   file_dict,
                                   file_diff_dict,
                                   Section("t"))

            diff = Diff(file_dict[f_b])
            diff.change_line(3, "3\n", "3_changed\n")
            uut.apply(Result("origin", "msg", diffs={f_b: diff}),
                      file_dict,
                      file_diff_dict)

            for filename in file_diff_dict:
                file_dict[filename] = file_diff_dict[filename].modified

            self.assertEqual(file_dict, expected_file_dict)
            with open(f_a) as fa:
                self.assertEqual(file_dict[f_a], fa.readlines())
            with open(f_b) as fb:
                self.assertEqual(file_dict[f_b], fb.readlines())
            with open(f_c) as fc:
                # File c is unchanged and should be untouched
                self.assertEqual([], fc.readlines())
Exemple #45
0
 def setUp(self):
     self.uut = Diff()
Exemple #46
0
    def __yield_diffs(file, new_file):
        if tuple(new_file) != tuple(file):
            wholediff = Diff.from_string_arrays(file, new_file)

            for diff in wholediff.split_diff():
                yield diff
    def test_stdin_stderr_config_correction(self):
        create_arguments_mock = Mock()
        generate_config_mock = Mock()

        # `some_value_A` and `some_value_B` are used to test the different
        # delegation to `generate_config()` and `create_arguments()`
        # accordingly.
        class Handler:
            @staticmethod
            def generate_config(filename, file, some_value_A):
                generate_config_mock(filename, file, some_value_A)
                return '\n'.join(['use_stdin', 'use_stderr', 'correct'])

            @staticmethod
            def create_arguments(filename, file, config_file, some_value_B):
                create_arguments_mock(filename, file, config_file,
                                      some_value_B)
                return self.test_program_path, '--config', config_file

        uut = (linter(sys.executable,
                      use_stdin=True,
                      use_stdout=False,
                      use_stderr=True,
                      output_format='corrected',
                      config_suffix='.conf')(Handler)(self.section, None))

        results = list(
            uut.run(self.testfile2_path,
                    self.testfile2_content,
                    some_value_A=124,
                    some_value_B=-78))

        expected_correction = [s + '\n' for s in ['+', '/', '/', '-']]

        diffs = list(
            Diff.from_string_arrays(self.testfile2_content,
                                    expected_correction).split_diff())

        expected = [
            Result.from_values(uut,
                               'Inconsistency found.',
                               self.testfile2_path,
                               1,
                               None,
                               1,
                               None,
                               RESULT_SEVERITY.NORMAL,
                               diffs={self.testfile2_path: diffs[0]}),
            Result.from_values(uut,
                               'Inconsistency found.',
                               self.testfile2_path,
                               5,
                               None,
                               5,
                               None,
                               RESULT_SEVERITY.NORMAL,
                               diffs={self.testfile2_path: diffs[1]})
        ]

        self.assertEqual(results, expected)
        create_arguments_mock.assert_called_once_with(self.testfile2_path,
                                                      self.testfile2_content,
                                                      ANY, -78)
        self.assertEqual(create_arguments_mock.call_args[0][2][-5:], '.conf')
        generate_config_mock.assert_called_once_with(self.testfile2_path,
                                                     self.testfile2_content,
                                                     124)
    def run(self,
            filename,
            file,
            dependency_results: dict,
            language: str,
            use_spaces: bool = True,
            indent_size: int = SpacingHelper.DEFAULT_TAB_WIDTH,
            coalang_dir: str = None,
            ):
        """
        It is a generic indent bear, which looks for a start and end
        indent specifier, example: ``{ : }`` where "{" is the start indent
        specifier and "}" is the end indent specifier. If the end-specifier
        is not given, this bear looks for unindents within the code to
        correctly figure out indentation.

        It also figures out hanging indents and absolute indentation of
        function params or list elements.

        It does not however support  indents based on keywords yet.
        for example:

            if(something)
            does not get indented

        undergoes no change.

        WARNING: The IndentationBear is experimental right now, you can report
        any issues found to https://github.com/coala/coala-bears

        :param filename:
            Name of the file that needs to be checked.
        :param file:
            File that needs to be checked in the form of a list of strings.
        :param dependency_results:
            Results given by the AnnotationBear.
        :param language:
            Language to be used for indentation.
        :param use_spaces:
            Insert spaces instead of tabs for indentation.
        :param indent_size:
            Number of spaces per indentation level.
        :param coalang_dir:
            Full path of external directory containing the coalang
            file for language.
        """
        lang_settings_dict = LanguageDefinition(
            language, coalang_dir=coalang_dir)
        annotation_dict = dependency_results[AnnotationBear.name][0].contents
        # sometimes can't convert strings with ':' to dict correctly
        if ':' in dict(lang_settings_dict['indent_types']).keys():
            indent_types = dict(lang_settings_dict['indent_types'])
            indent_types[':'] = ''
        else:
            indent_types = dict(lang_settings_dict['indent_types'])

        encapsulators = (dict(lang_settings_dict['encapsulators']) if
                         'encapsulators' in lang_settings_dict else {})

        encaps_pos = []
        for encapsulator in encapsulators:
            encaps_pos += self.get_specified_block_range(
                file, filename,
                encapsulator, encapsulators[encapsulator],
                annotation_dict)
        encaps_pos = tuple(sorted(encaps_pos, key=lambda x: x.start.line))

        comments = dict(lang_settings_dict['comment_delimiters'])
        comments.update(
            dict(lang_settings_dict['multiline_comment_delimiters']))

        try:
            indent_levels = self.get_indent_levels(
                file, filename,
                indent_types, annotation_dict, encaps_pos, comments)
        # This happens only in case of unmatched indents or
        # ExpectedIndentError.
        except (UnmatchedIndentError, ExpectedIndentError) as e:
            yield Result(self, str(e), severity=RESULT_SEVERITY.MAJOR)
            return

        absolute_indent_levels = self.get_absolute_indent_of_range(
            file, filename,
            encaps_pos, annotation_dict)

        insert = ' '*indent_size if use_spaces else '\t'

        no_indent_file = self._get_no_indent_file(file)
        new_file = self._get_basic_indent_file(no_indent_file,
                                               indent_levels,
                                               insert)
        new_file = self._get_absolute_indent_file(new_file,
                                                  absolute_indent_levels,
                                                  indent_levels,
                                                  insert)
        if new_file != list(file):
            wholediff = Diff.from_string_arrays(file, new_file)
            for diff in wholediff.split_diff():
                yield Result(
                    self,
                    'The indentation could be changed to improve readability.',
                    severity=RESULT_SEVERITY.INFO,
                    affected_code=(diff.range(filename),),
                    diffs={filename: diff})
Exemple #49
0
    def test_add(self):
        file_dict = {
            'f_a': ['1', '2', '3'],
            'f_b': ['1', '2', '3'],
            'f_c': ['1', '2', '3']
        }
        expected_file_dict = {
            'f_a': ['1\n', '3_changed'],
            'f_b': ['1\n', '2\n', '3_changed'],
            'f_c': ['1', '2', '3']
        }

        diff = Diff(file_dict['f_a'])
        diff.delete_line(2)
        uut1 = Result('origin', 'msg', diffs={'f_a': diff})

        diff = Diff(file_dict['f_a'])
        diff.modify_line(3, '3_changed')
        uut2 = Result('origin', 'msg', diffs={'f_a': diff})

        diff = Diff(file_dict['f_b'])
        diff.modify_line(3, '3_changed')
        uut3 = Result('origin', 'msg', diffs={'f_b': diff})

        uut1 += uut2 + uut3
        uut1.apply(file_dict)

        self.assertEqual(file_dict, expected_file_dict)
Exemple #50
0
    def test_add(self):
        file_dict = {
            "f_a": ["1", "2", "3"],
            "f_b": ["1", "2", "3"],
            "f_c": ["1", "2", "3"]
        }
        expected_file_dict = {
            "f_a": ["1", "3_changed"],
            "f_b": ["1", "2", "3_changed"],
            "f_c": ["1", "2", "3"]
        }

        diff = Diff(file_dict['f_a'])
        diff.delete_line(2)
        uut1 = Result("origin", "msg", diffs={"f_a": diff})

        diff = Diff(file_dict['f_a'])
        diff.change_line(3, "3", "3_changed")
        uut2 = Result("origin", "msg", diffs={"f_a": diff})

        diff = Diff(file_dict['f_b'])
        diff.change_line(3, "3", "3_changed")
        uut3 = Result("origin", "msg", diffs={"f_b": diff})

        uut1 += uut2 + uut3
        uut1.apply(file_dict)

        self.assertEqual(file_dict, expected_file_dict)
Exemple #51
0
    def run(self,
            filename,
            file,
            use_spaces: bool,
            allow_trailing_whitespace: bool=False,
            tab_width: int=SpacingHelper.DEFAULT_TAB_WIDTH,
            enforce_newline_at_EOF: bool=True):
        '''
        Checks the space consistency for each line.

        :param use_spaces:                True if spaces are to be used instead
                                          of tabs
        :param allow_trailing_whitespace: Whether to allow trailing whitespace
                                          or not
        :param tab_width:                 Number of spaces representing one
                                          tab
        :param enforce_newline_at_EOF:    Whether to enforce a newline at the
                                          End Of File
        '''
        spacing_helper = SpacingHelper(tab_width)
        result_texts = []

        for line_number, line in enumerate(file, start=1):
            replacement = line

            if enforce_newline_at_EOF:
                # Since every line contains at the end at least one \n, only
                # the last line could potentially not have one. So we don't
                # need to check whether the current line_number is the last
                # one.
                if replacement[-1] != '\n':
                    replacement += '\n'
                    result_texts.append('No newline at EOF.')

            if not allow_trailing_whitespace:
                replacement = replacement.rstrip(' \t\n') + '\n'
                if replacement != line.rstrip('\n') + '\n':
                    result_texts.append('Trailing whitespaces.')

            if use_spaces:
                pre_replacement = replacement
                replacement = spacing_helper.replace_tabs_with_spaces(
                    replacement)
                if replacement != pre_replacement:
                    result_texts.append('Tabs used instead of spaces.')
            else:
                pre_replacement = replacement
                replacement = spacing_helper.replace_spaces_with_tabs(
                    replacement)
                if replacement != pre_replacement:
                    result_texts.append('Spaces used instead of tabs.')

            if len(result_texts) > 0:
                diff = Diff(file)
                diff.change_line(line_number, line, replacement)
                inconsistencies = ''.join('\n- ' + string
                                          for string in result_texts)
                yield Result.from_values(
                    self,
                    'Line contains following spacing inconsistencies:'
                    + inconsistencies,
                    diffs={filename: diff},
                    file=filename,
                    line=line_number)
                result_texts = []
Exemple #52
0
 def setUp(self):
     self.file = ['1', '2', '3', '4']
     self.uut = Diff(self.file)
    def run(self,
            filename,
            file,
            use_parentheses_in_import: bool = True,
            force_alphabetical_sort_in_import: bool = False,
            force_sort_within_import_sections: bool = True,
            from_first_in_import: bool = False,
            include_trailing_comma_in_import: bool = False,
            combine_star_imports: bool = True,
            combine_as_imports: bool = True,
            lines_after_imports: int = -1,
            order_imports_by_type: bool = False,
            balanced_wrapping_in_imports: bool = False,
            import_heading_localfolder: str = '',
            import_heading_firstparty: str = '',
            import_heading_thirdparty: str = '',
            import_heading_stdlib: str = '',
            import_heading_future: str = '',
            default_import_section: str = 'FIRSTPARTY',
            force_grid_wrap_imports: bool = False,
            force_single_line_imports: bool = True,
            sort_imports_by_length: bool = False,
            use_spaces: bool = True,
            indent_size: int = SpacingHelper.DEFAULT_TAB_WIDTH,
            forced_separate_imports: typed_list(str) = (),
            isort_multi_line_output: int = 4,
            known_first_party_imports: typed_list(str) = (),
            known_third_party_imports: typed_list(str) = (),
            known_standard_library_imports: typed_list(str) = None,
            max_line_length: int = 79,
            imports_forced_to_top: typed_list(str) = ()):
        """
        Raise issues related to sorting imports, segregating imports into
        various sections, and also adding comments on top of each import
        section based on the configurations provided.

        You can read more about ``isort`` at
        <https://isort.readthedocs.org/en/latest/>.

        :param use_parentheses_in_import:
            True if parenthesis are to be used in import statements.
        :param force_alphabetical_sort_in_import:
            If set, forces all imports to be sorted as a single section,
            instead of within other groups (such as straight vs from).
        :param force_sort_within_import_sections:
            If set, imports will be sorted within there section independent
            to the import_type.
        :param from_first_in_import:
            If set, imports using "from" will be displayed above normal
            (straight) imports.
        :param include_trailing_comma_in_import:
            If set, will automatically add a trailing comma to the end of
            "from" imports. Example: ``from abc import (a, b, c,)``
        :param combine_star_imports:
            If set to true - ensures that if a star import is present,
            nothing else is imported from that namespace.
        :param combine_as_imports:
            If set to true - isort will combine as imports on the same line
            within for import statements.
        :param lines_after_imports:
            Forces a certain number of lines after the imports and before the
            first line of functional code. By default this is set to -1 which
            uses 2 lines if the first line of code is a class or function and
            1 line if it's anything else.
        :param order_imports_by_type:
            If set to true - isort will create separate sections within "from"
            imports for CONSTANTS, Classes, and modules/functions.
        :param balanced_wrapping_in_imports:
            If set to true - for each multi-line import statement isort will
            dynamically change the import length to the one that produces
            the most balanced grid, while staying below the maximum import
            length defined.
        :param import_heading_localfolder:
            A comment to consistently place directly above imports that
            start with '.'.
        :param import_heading_firstparty:
            A comment to consistently place directly above imports from
            the current project.
        :param import_heading_thirdparty:
            A comment to consistently place directly above thirdparty imports.
        :param import_heading_stdlib:
            A comment to consistently place directly above imports from
            the standard library.
        :param import_heading_future:
            A comment to consistently place directly above future imports.
        :param default_import_section:
            The default section to place imports in, if their section can
            not be automatically determined.
        :param force_grid_wrap_imports:
             Force "from" imports to be grid wrapped regardless of line length.
        :param force_single_line_imports:
            If set to true - instead of wrapping multi-line from style imports,
            each import will be forced to display on its own line.
        :param sort_imports_by_length:
            Set to true to sort imports by length instead of alphabetically.
        :param use_spaces:
            True if spaces are to be used instead of tabs.
        :param indent_size:
            Number of spaces per indentation level.
        :param forced_separate_imports:
            A list of modules that you want to appear in their own separate
            section.
        :param isort_multi_line_output:
            An integer that represents how you want imports to be displayed
            by ``isort`` if they're long enough to span multiple lines.
            This value is passed to isort as the ``multi_line_output`` setting.
            Possible values are (0-grid, 1-vertical, 2-hanging, 3-vert-hanging,
            4-vert-grid, 5-vert-grid-grouped)
            A full definition of all possible modes can be found at
            <https://github.com/timothycrosley/isort#multi-line-output-modes>.
        :param known_first_party_imports:
            A list of imports that will be forced to display within the
            standard library category of imports.
        :param known_third_party_imports:
            A list of imports that will be forced to display within the
            third party category of imports.
        :param known_standard_library_imports:
            A list of imports that will be forced to display within the
            first party category of imports.
        :param import_wrap_length:
            An integer that represents the longest line-length you want when
            wrapping. If not set will default to line_length.
        :param imports_forced_to_top:
            Forces a list of imports to the top of their respective section.
            This works well for handling the unfortunate cases of import
            dependencies that occur in many projects.
        :param max_line_length:
            Maximum number of characters for a line.
        """
        isort_settings = dict(
            use_parentheses=use_parentheses_in_import,
            force_alphabetical_sort=force_alphabetical_sort_in_import,
            force_sort_within_sections=force_sort_within_import_sections,
            from_first=from_first_in_import,
            include_trailing_comma=include_trailing_comma_in_import,
            combine_star=combine_star_imports,
            lines_after_imports=lines_after_imports,
            order_by_type=order_imports_by_type,
            balanced_wrapping=balanced_wrapping_in_imports,
            import_heading_localfolder=import_heading_localfolder,
            import_heading_firstparty=import_heading_firstparty,
            import_heading_thirdparty=import_heading_thirdparty,
            import_heading_stdlib=import_heading_stdlib,
            import_heading_future=import_heading_future,
            default_section=default_import_section,
            force_grid_wrap=force_grid_wrap_imports,
            force_single_line=force_single_line_imports,
            length_sort=sort_imports_by_length,
            indent='Tab' if use_spaces == False else indent_size,
            forced_separate=forced_separate_imports,
            multi_line_output=isort_multi_line_output,
            known_first_party=known_first_party_imports,
            line_length=max_line_length,
            force_to_top=imports_forced_to_top)

        if known_standard_library_imports is not None:
            isort_settings['known_standard_library'] = (
                known_standard_library_imports)

        sort_imports = SortImports(file_contents=''.join(file),
                                   **isort_settings)
        new_file = tuple(sort_imports.output.splitlines(True))

        if new_file != tuple(file):
            diff = Diff.from_string_arrays(file, new_file)
            yield Result(self,
                         'Imports can be sorted.',
                         affected_code=diff.affected_code(filename),
                         diffs={filename: diff})
Exemple #54
0
    def run(
        self,
        filename,
        file,
        max_line_length: int = 79,
        indent_size: int = SpacingHelper.DEFAULT_TAB_WIDTH,
        allow_multiline_lambdas: bool = False,
        blank_line_before_nested_class_or_def: bool = False,
        continuation_tab_width: int = SpacingHelper.DEFAULT_TAB_WIDTH,
        dedent_closing_brackets: bool = False,
        indent_dictionary_value: bool = False,
        coalesce_brackets: bool = False,
        join_multiple_lines: bool = True,
        spaces_around_power_operator: bool = True,
        spaces_before_comment: int = 2,
        space_between_ending_comma_and_closing_bracket: bool = True,
        split_arguments_when_comma_terminated: bool = False,
        split_before_bitwise_operator: bool = False,
        split_before_first_argument: bool = False,
        split_before_logical_operator: bool = False,
        split_before_named_assigns: bool = True,
        use_spaces: bool = True,
        based_on_style: str = 'pep8',
        prefer_line_break_after_opening_bracket: bool = True,
    ):
        """
        Check and correct formatting of Python code using ``yapf`` utility.

        See <https://github.com/google/yapf> for more information.

        :param max_line_length:
            Maximum number of characters for a line.
        :param indent_size:
            Number of spaces per indentation level.
        :param allow_multiline_lambdas:
            Allows lambdas to be formatted on more than one line.
        :param blank_line_before_nested_class_or_def:
            Inserts a blank line before a ``def`` or ``class`` immediately
            nested within another ``def`` or ``class``.
        :param continuation_tab_width:
            Indent width used for line continuations.
        :param dedent_closing_brackets:
            Puts closing brackets on a separate line, dedented, if the
            bracketed expression can't fit in a single line. Applies to all
            kinds of brackets, including function definitions and calls.
        :param indent_dictionary_value:
            Indents the dictionary value if it cannot fit on the same line as
            the dictionary key.
        :param coalesce_brackets:
            Prevents splitting consecutive brackets. Only relevant when
            ``dedent_closing_brackets`` is set.
            Example:
            If ``True``::

                call_func_that_takes_a_dict(
                    {
                        'key1': 'value1',
                        'key2': 'value2',
                    }
                )

            would reformat to::

                call_func_that_takes_a_dict({
                    'key1': 'value1',
                    'key2': 'value2',
                })

        :param join_multiple_lines:
            Joins short lines into one line.
        :param spaces_around_power_operator:
            Set to ``True`` to prefer using spaces around ``**``.
        :param spaces_before_comment:
            The number of spaces required before a trailing comment.
        :param space_between_ending_comma_and_closing_bracket:
            Inserts a space between the ending comma and closing bracket of a
            list, etc.
        :param split_arguments_when_comma_terminated:
            Splits before arguments if the argument list is terminated by a
            comma.
        :param split_before_bitwise_operator:
            Set to ``True`` to prefer splitting before ``&``, ``|`` or ``^``
            rather than after.
        :param split_before_first_argument:
            If an argument / parameter list is going to be split, then split
            before the first argument.
        :param split_before_logical_operator:
            Set to ``True`` to prefer splitting before ``and`` or ``or`` rather
            than after.
        :param split_before_named_assigns:
            Splits named assignments into individual lines.
        :param use_spaces:
            Uses spaces for indentation.
        :param based_on_style:
            The formatting style to be used as reference.
        :param prefer_line_break_after_opening_bracket:
            If True, splitting right after a open bracket will not be
            preferred.
        """
        if not file:
            # Yapf cannot handle zero-byte files well, and adds a redundent
            # newline into the file. To avoid this, we don't parse zero-byte
            # files as they cannot have anything to format either.
            return

        options = """
[style]
indent_width = {indent_size}
column_limit = {max_line_length}
allow_multiline_lambdas = {allow_multiline_lambdas}
continuation_indent_width = {continuation_tab_width}
dedent_closing_brackets = {dedent_closing_brackets}
indent_dictionary_value = {indent_dictionary_value}
join_multiple_lines = {join_multiple_lines}
spaces_around_power_operator = {spaces_around_power_operator}
spaces_before_comment = {spaces_before_comment}
coalesce_brackets = {coalesce_brackets}
split_before_bitwise_operator = {split_before_bitwise_operator}
split_before_first_argument = {split_before_first_argument}
split_before_logical_operator = {split_before_logical_operator}
split_before_named_assigns = {split_before_named_assigns}
based_on_style = {based_on_style}
blank_line_before_nested_class_or_def = {blank_line_before_nested_class_or_def}
split_arguments_when_comma_terminated = {split_arguments_when_comma_terminated}
space_between_ending_comma_and_closing_bracket= \
{space_between_ending_comma_and_closing_bracket}
"""
        options += 'use_tabs = ' + str(not use_spaces) + '\n'
        options += (
            'split_penalty_after_opening_bracket = ' +
            ('30' if prefer_line_break_after_opening_bracket else '0') + '\n')
        options = options.format(**locals())

        try:
            with prepare_file(options.splitlines(keepends=True),
                              None) as (file_, fname):
                corrected = FormatCode(''.join(file),
                                       style_config=fname)[0].splitlines(True)
        except SyntaxError as err:
            if isinstance(err, IndentationError):
                error_type = 'indentation errors (' + err.args[0] + ')'
            else:
                error_type = 'syntax errors'
            yield Result.from_values(
                self,
                'The code cannot be parsed due to {0}.'.format(error_type),
                filename,
                line=err.lineno,
                column=err.offset)
            return
        diffs = Diff.from_string_arrays(file, corrected).split_diff()
        for diff in diffs:
            yield Result(self, 'The code does not comply with the settings '
                         'provided.',
                         affected_code=(diff.range(filename), ),
                         diffs={filename: diff})
Exemple #55
0
    def test_apply(self):
        uut = ApplyPatchAction()
        file_dict = {
            "f_a": ["1", "2", "3"],
            "f_b": ["1", "2", "3"],
            "f_c": ["1", "2", "3"]
        }
        expected_file_dict = {
            "f_a": ["1", "3_changed"],
            "f_b": ["1", "2", "3_changed"],
            "f_c": ["1", "2", "3"]
        }

        file_diff_dict = {}

        diff = Diff()
        diff.delete_line(2)
        uut.apply_from_section(PatchResult("origin", "msg", {"f_a": diff}),
                               file_dict, file_diff_dict, Section("t"))

        diff = Diff()
        diff.change_line(3, "3", "3_changed")
        uut.apply_from_section(PatchResult("origin", "msg", {"f_a": diff}),
                               file_dict, file_diff_dict, Section("t"))

        diff = Diff()
        diff.change_line(3, "3", "3_changed")
        uut.apply(PatchResult("origin", "msg", {"f_b": diff}), file_dict,
                  file_diff_dict)

        for filename in file_diff_dict:
            file_dict[filename] = file_diff_dict[filename].apply(
                file_dict[filename])

        self.assertEqual(file_dict, expected_file_dict)
Exemple #56
0
    def test_print_result(self):
        print_result(self.console_printer, self.log_printer, None,
                     self.file_diff_dict, "illegal value", {})

        with simulate_console_inputs(0):
            print_result(self.console_printer, self.log_printer,
                         self.section, self.file_diff_dict,
                         Result("origin", "msg", diffs={}), {})

        with make_temp() as testfile_path:
            file_dict = {
                testfile_path: ["1\n", "2\n", "3\n"],
                "f_b": ["1", "2", "3"]
            }
            diff = Diff(file_dict[testfile_path])
            diff.delete_line(2)
            diff.change_line(3, "3\n", "3_changed\n")

            ApplyPatchAction.is_applicable = staticmethod(lambda *args: True)

            # Interaction must be closed by the user with `0` if it's not a
            # param
            with simulate_console_inputs("INVALID", -1, 1, 0,
                                         3) as input_generator:
                curr_section = Section("")
                print_section_beginning(self.console_printer, curr_section)
                print_result(
                    self.console_printer, self.log_printer, curr_section,
                    self.file_diff_dict,
                    Result("origin", "msg", diffs={testfile_path:
                                                   diff}), file_dict)
                self.assertEqual(input_generator.last_input, 3)

                self.file_diff_dict.clear()

                with open(testfile_path) as f:
                    self.assertEqual(f.readlines(), ["1\n", "3_changed\n"])

                os.remove(testfile_path + ".orig")

                name, section = get_action_info(curr_section,
                                                TestAction().get_metadata(),
                                                failed_actions=set())
                self.assertEqual(input_generator.last_input, 4)
                self.assertEqual(str(section), " {param : '3'}")
                self.assertEqual(name, "TestAction")

        # Check if the user is asked for the parameter only the first time.
        # Use OpenEditorAction that needs this parameter (editor command).
        with simulate_console_inputs(1, "test_editor", 0, 1, 0) as generator:
            OpenEditorAction.is_applicable = staticmethod(lambda *args: True)

            patch_result = Result("origin", "msg", diffs={testfile_path: diff})
            patch_result.file = "f_b"

            print_result(self.console_printer, self.log_printer, curr_section,
                         self.file_diff_dict, patch_result, file_dict)
            # choose action, choose editor, choose no action (-1 -> 2)
            self.assertEqual(generator.last_input, 2)

            # It shoudn't ask for parameter again
            print_result(self.console_printer, self.log_printer, curr_section,
                         self.file_diff_dict, patch_result, file_dict)
            self.assertEqual(generator.last_input, 4)
    def test_print_result(self):
        print_result(self.console_printer, None, self.file_diff_dict,
                     'illegal value', {})

        with simulate_console_inputs('n'):
            print_result(self.console_printer,
                         self.section, self.file_diff_dict,
                         Result('origin', 'msg', diffs={}), {})

        with make_temp() as testfile_path:
            file_dict = {
                testfile_path: ['1\n', '2\n', '3\n'],
                'f_b': ['1', '2', '3']
            }
            diff = Diff(file_dict[testfile_path])
            diff.delete_line(2)
            diff.change_line(3, '3\n', '3_changed\n')

            ApplyPatchAction.is_applicable = staticmethod(lambda *args: True)

            # Interaction must be closed by the user with `0` if it's not a
            # param
            with simulate_console_inputs('INVALID', -1, 'a', 'n',
                                         'm') as input_generator:
                curr_section = Section('')
                print_section_beginning(self.console_printer, curr_section)
                print_result(
                    self.console_printer, curr_section, self.file_diff_dict,
                    Result('origin', 'msg', diffs={testfile_path: diff}),
                    file_dict)
                self.assertEqual(input_generator.last_input, 3)

                self.file_diff_dict.clear()

                with open(testfile_path) as f:
                    self.assertEqual(f.readlines(), ['1\n', '3_changed\n'])

                os.remove(testfile_path + '.orig')

                name, section = get_action_info(curr_section,
                                                TestAction().get_metadata(),
                                                failed_actions=set())
                self.assertEqual(input_generator.last_input, 4)
                self.assertEqual(str(section), " {param : 'm'}")
                self.assertEqual(name, 'TestAction')

        # Check if the user is asked for the parameter only the first time.
        # Use OpenEditorAction that needs this parameter (editor command).
        with simulate_console_inputs('o', 'test_editor', 'n', 'o',
                                     'n') as generator:
            OpenEditorAction.is_applicable = staticmethod(lambda *args: True)

            patch_result = Result('origin', 'msg', diffs={testfile_path: diff})
            patch_result.file = 'f_b'

            print_result(self.console_printer, curr_section,
                         self.file_diff_dict, patch_result, file_dict)
            # choose action, choose editor, choose no action (-1 -> 2)
            self.assertEqual(generator.last_input, 2)

            # It shoudn't ask for parameter again
            print_result(self.console_printer, curr_section,
                         self.file_diff_dict, patch_result, file_dict)
            self.assertEqual(generator.last_input, 4)
    def run(
        self,
        filename,
        file,
        use_spaces: bool,
        allow_trailing_whitespace: bool = False,
        indent_size: int = SpacingHelper.DEFAULT_TAB_WIDTH,
        enforce_newline_at_EOF: bool = True,
    ):
        '''
        Check and correct spacing for all textual data. This includes usage of
        tabs vs. spaces, trailing whitespace and (missing) newlines before
        the end of the file.

        :param use_spaces:
            True if spaces are to be used instead of tabs.
        :param allow_trailing_whitespace:
            Whether to allow trailing whitespace or not.
        :param indent_size:
            Number of spaces per indentation level.
        :param enforce_newline_at_EOF:
            Whether to enforce a newline at the End Of File.
        '''
        spacing_helper = SpacingHelper(indent_size)
        result_texts = []
        additional_info_texts = []

        for line_number, line in enumerate(file, start=1):
            replacement = line

            if enforce_newline_at_EOF:
                # Since every line contains at the end at least one \n, only
                # the last line could potentially not have one. So we don't
                # need to check whether the current line_number is the last
                # one.
                if replacement[-1] != '\n':
                    replacement += '\n'
                    result_texts.append('No newline at EOF.')
                    additional_info_texts.append(
                        "A trailing newline character ('\\n') is missing from "
                        'your file. '
                        '<http://stackoverflow.com/a/5813359/3212182> gives '
                        'more information about why you might need one.')

            if not allow_trailing_whitespace:
                replacement = replacement.rstrip(' \t\n') + '\n'
                if replacement != line.rstrip('\n') + '\n':
                    result_texts.append('Trailing whitespaces.')
                    additional_info_texts.append(
                        'Your source code contains trailing whitespaces. '
                        'Those usually have no meaning. Please consider '
                        'removing them.')

            if use_spaces:
                pre_replacement = replacement
                replacement = replacement.expandtabs(indent_size)
                if replacement != pre_replacement:
                    result_texts.append('Tabs used instead of spaces.')
            else:
                pre_replacement = replacement
                replacement = spacing_helper.replace_spaces_with_tabs(
                    replacement)
                if replacement != pre_replacement:
                    result_texts.append('Spaces used instead of tabs.')

            if len(result_texts) > 0:
                diff = Diff(file)
                diff.change_line(line_number, line, replacement)
                inconsistencies = ''.join('\n- ' + string
                                          for string in result_texts)
                yield Result.from_values(
                    self,
                    'Line contains following spacing inconsistencies:' +
                    inconsistencies,
                    diffs={filename: diff},
                    file=filename,
                    line=line_number,
                    additional_info='\n\n'.join(additional_info_texts))
                result_texts = []
                additional_info_texts = []
Exemple #59
0
 def test_is_applicable(self):
     diff = Diff(['1\n', '2\n', '3\n'])
     diff.delete_line(2)
     patch_result = Result('', '', diffs={'f': diff})
     self.assertTrue(ApplyPatchAction.is_applicable(patch_result, {}, {}))