| #!/usr/bin/env python3 |
| # |
| # Copyright (c) 2009 Google Inc. All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are |
| # met: |
| # |
| # * Redistributions of source code must retain the above copyright |
| # notice, this list of conditions and the following disclaimer. |
| # * Redistributions in binary form must reproduce the above |
| # copyright notice, this list of conditions and the following disclaimer |
| # in the documentation and/or other materials provided with the |
| # distribution. |
| # * Neither the name of Google Inc. nor the names of its |
| # contributors may be used to endorse or promote products derived from |
| # this software without specific prior written permission. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| """Command Line interface integration test for cpplint.py.""" |
| |
| import contextlib |
| import glob |
| import os |
| import shutil |
| import subprocess |
| import sys |
| import tempfile |
| |
| import pytest |
| from testfixtures import compare # type: ignore[import-untyped] |
| |
| import cpplint # noqa: F401 |
| |
| BASE_CMD = sys.executable + " " + os.path.abspath("./cpplint.py ") |
| |
| |
| def run_shell_command(cmd: str, args: str, cwd: str = ".") -> tuple[int, bytes, bytes]: |
| """Executes a command |
| |
| Args: |
| cmd: A string to execute. |
| args: A string with arguments to the command. |
| cwd: from which folder to run. |
| """ |
| cmd, args = cmd.split(), args.replace('"', "").split() # type: ignore[assignment] |
| proc = subprocess.run(cmd + args, cwd=cwd, capture_output=True, check=False) |
| out, err = proc.stdout, proc.stderr |
| |
| # Make output system-agnostic, aka support Windows |
| if os.sep == "\\": |
| # TODO(aaronliu0130): Support scenario with multiple input names |
| # We currently only support the last arguments as the input name |
| # to prevent accidentally replacing sed tests. |
| # Fixing would likely need coding an internal "replace slashes" option for cpplint itself. |
| win_path = (os.path.dirname(args[-1]) + "\\").encode() |
| good_path = win_path.replace(b"\\", b"/") |
| out, err = out.replace(win_path, good_path), err.replace(win_path, good_path) |
| if os.linesep == "\r\n": |
| out, err = out.replace(b"\r\n", b"\n"), err.replace(b"\r\n", b"\n") |
| |
| # print(err) # to get the output at time of test |
| return proc.returncode, out, err |
| |
| |
| def test_help(): |
| (status, out, err) = run_shell_command(BASE_CMD, "--help") |
| assert status == 0 |
| assert out == b"" |
| assert err.startswith(b"\nSyntax: cpplint") |
| |
| |
| class TemporaryFolderClassSetup: |
| """ |
| Regression tests: The test starts a filetreewalker scanning for files name *.def |
| Such files are expected to have as first line the argument |
| to a cpplint invocation from within the same directory, as second line the |
| expected status code, then the line count of stdout lines, |
| then the stdout lines, and all other lines the expected |
| systemerr output (two blank lines at end). |
| """ |
| |
| @pytest.fixture(autouse=True, name="set_up()", scope="class") |
| @classmethod |
| def set_up(cls): |
| """setup tmp folder for testing with samples and custom additions by subclasses""" |
| try: |
| cls._root = os.path.realpath(tempfile.mkdtemp()) |
| shutil.copytree("samples", os.path.join(cls._root, "samples")) |
| cls.prepare_directory(cls._root) |
| except Exception: |
| with contextlib.suppress(Exception): |
| cls.tear_down() |
| raise |
| # yield |
| # cls.tear_down() |
| |
| @classmethod |
| def tear_down(cls): |
| if cls._root: |
| # pass |
| shutil.rmtree(cls._root) |
| |
| @classmethod |
| def prepare_directory(cls, root): |
| """Override in subclass to manipulate temporary samples root folder before tests""" |
| pass |
| |
| def get_extra_command_args(self, cwd): |
| """Override in subclass to add arguments to command""" |
| return "" |
| |
| def check_all_in_folder(self, folder_name, expected_defs): |
| # uncomment to show complete diff |
| # self.maxDiff = None |
| count = 0 |
| for dirpath, _, fnames in os.walk(folder_name): |
| for f in fnames: |
| if f.endswith(".def"): |
| count += 1 |
| self.check_def(os.path.join(dirpath, f)) |
| assert count == expected_defs |
| |
| def check_def(self, path): |
| """runs command and compares to expected output from def file""" |
| # self.maxDiff = None # to see full diff |
| with open(path, "rb") as file_handle: |
| data = file_handle.readlines() |
| stdout_lines = int(data[2]) |
| filenames = data[0].decode("utf8").strip() |
| args, _, filenames = filenames.rpartition(" ") |
| if "*" in filenames: |
| rel_cwd = os.path.dirname(path) |
| filenames = " ".join( |
| filename[len(rel_cwd) + 1 :] |
| for filename in glob.glob(rel_cwd + "/" + filenames) |
| ) |
| args += " " + filenames |
| self._run_and_compare( |
| path, |
| args, |
| int(data[1]), |
| [line.decode("utf8").strip() for line in data[3 : 3 + stdout_lines]], |
| [line.decode("utf8").strip() for line in data[3 + stdout_lines :]], |
| ) |
| |
| def _run_and_compare(self, definition_file, args, expected_status, expected_out, expected_err): |
| rel_cwd = os.path.dirname(definition_file) |
| cmd = BASE_CMD + self.get_extra_command_args(rel_cwd) |
| cwd = os.path.join(self._root, rel_cwd) |
| # command to reproduce, do not forget first two lines have special meaning |
| print("\ncd " + cwd + " && " + cmd + " " + args + " 2> <filename>") |
| (status, out, err) = run_shell_command(cmd, args, cwd) |
| assert expected_status == status, f"bad command status {status}" |
| prefix = f"Failed check in {cwd} comparing to {definition_file} for command: {cmd}" |
| compare("\n".join(expected_err), err.decode("utf8"), prefix=prefix, show_whitespace=True) |
| compare("\n".join(expected_out), out.decode("utf8"), prefix=prefix, show_whitespace=True) |
| |
| |
| class TestNoRepoSignature(TemporaryFolderClassSetup): |
| """runs in a temporary folder (under /tmp in linux) without any .git/.hg/.svn file""" |
| |
| def get_extra_command_args(self, cwd): |
| return f" --repository {self._root} " |
| |
| @pytest.mark.parametrize( |
| ("folder", "case"), |
| [ |
| (folder, case[:-4]) |
| for folder in ["chromium", "vlc", "silly", "boost", "protobuf", "codelite", "v8"] |
| for case in os.listdir(f"./samples/{folder}-sample") |
| if case.endswith(".def") |
| ], |
| ) |
| @pytest.mark.timeout(180) |
| def test_samples(self, folder, case): |
| self.check_def(os.path.join(f"./samples/{folder}-sample", case + ".def")) |
| |
| |
| class TestGitRepoSignature(TemporaryFolderClassSetup): |
| """runs in a temporary folder with .git file""" |
| |
| @classmethod |
| def prepare_directory(cls, root): |
| with open(os.path.join(root, ".git"), "a"): |
| pass |
| |
| def test_codelite_sample(self): |
| self.check_all_in_folder("./samples/codelite-sample", 1) |
| |
| |
| class TestMercurialRepoSignature(TemporaryFolderClassSetup): |
| """runs in a temporary folder with .hg file""" |
| |
| @classmethod |
| def prepare_directory(cls, root): |
| with open(os.path.join(root, ".hg"), "a"): |
| pass |
| |
| def test_codelite_sample(self): |
| self.check_all_in_folder("./samples/codelite-sample", 1) |
| |
| |
| class TestSvnRepoSignature(TemporaryFolderClassSetup): |
| """runs in a temporary folder with .svn file""" |
| |
| @classmethod |
| def prepare_directory(cls, root): |
| with open(os.path.join(root, ".svn"), "a"): |
| pass |
| |
| def test_codelite_sample(self): |
| self.check_all_in_folder("./samples/codelite-sample", 1) |
| |
| |
| if __name__ == "__main__": |
| pytest.main([__file__]) |