Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
strategy:
matrix:
# Python 3.8 is the last non-EOL version. Also adapt tox.ini
python-version: ['3.8', 'pypy3.10', '3.12']
python-version: ['3.8', 'pypy3.10', '3.x']
os: [ubuntu-latest, windows-latest]
fail-fast: false

Expand Down
6 changes: 1 addition & 5 deletions setup.cfg → .pytest.ini
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
[aliases]
test = pytest

[tool:pytest]
[pytest]
python_files = *test.py
testpaths = .
required_plugins = pytest-cov pytest-timeout
timeout = 60
# fail if coverage is under 90%
addopts = --color=yes --cov-fail-under=90 --cov=cpplint
6 changes: 3 additions & 3 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Thanks for your interest in contributing to cpplint.
Any kinds of contributions are welcome: Bug reports, Documentation, Patches. However, here are some contributions you probably shouldn't make:

* Drastic reorganization
* Making the code conform to Google's Python style guidelines
* Making the code conform to Google's Python style guidelines
* Features that could be regarded as a security vulnerability

If you need some ideas, you may check out some of the tasks in our `issue tracker <https://github.com/cpplint/cpplint/issues>`_.
Expand All @@ -22,9 +22,9 @@ For many tasks, it is okay to just develop using a single installed python versi
1. (Optional) Install `pyenv <https://github.com/pyenv/pyenv-installer>`_ to manage python versions
2. (Optional) Using pyenv, install the python versions used in testing::

pyenv install 3.12.6
pyenv install 3.<version>
# ...
pyenv local 3.12.6 ...
pyenv local 3.<version> ...

It may be okay to run and test python against locally installed libraries, but if you need to have a consistent build, it is recommended to manage your environment using virtualenv: `virtualenv <https://virtualenv.pypa.io/en/latest/>`_, `virtualenvwrapper <https://pypi.org/project/virtualenvwrapper/>`_::

Expand Down
129 changes: 66 additions & 63 deletions cpplint_clitest.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,50 +38,57 @@
import unittest
import shutil
import tempfile

from pytest import mark
from testfixtures import compare
from parameterized import parameterized

BASE_CMD = sys.executable + ' ' + os.path.abspath('./cpplint.py ')

def RunShellCommand(cmd: str, args: str, cwd='.'):
"""
executes a command
:param cmd: A string to execute.
:param cwd: from which folder to run.

def run_shell_command(cmd: str, args: str, cwd='.'):
"""Executes a command

Args:
cmd: A string to execute.
args: A string with arguments to the command.
cwd: from which folder to run.
"""

stdout_target = subprocess.PIPE
stderr_target = subprocess.PIPE

proc = subprocess.Popen(cmd + ' ' + args,
with subprocess.Popen(cmd + ' ' + args,
shell=True,
cwd=cwd,
stdout=stdout_target,
stderr=stderr_target)
out, err = proc.communicate()
stderr=stderr_target) as proc:
out, err = proc.communicate()

# Make output system-agnostic, aka support Windows
if os.sep == '\\':
# TODO: Support scenario with multiple folder inputs
win_path = (os.path.dirname(args.split(' ')[-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')
# Make output system-agnostic, aka support Windows
if os.sep == '\\':
args_paths = args.split(' ')
for path in args_paths:
win_path = (os.path.dirname(path) + '\\').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)
# print(err) # to get the output at time of test
return proc.returncode, out, err


class UsageTest(unittest.TestCase):

def testHelp(self):
(status, out, err) = RunShellCommand(BASE_CMD, '--help')
(status, out, err) = run_shell_command(BASE_CMD, '--help')
self.assertEqual(0, status)
self.assertEqual(b'', out)
self.assertTrue(err.startswith(b'\nSyntax: cpplint'))

class TemporaryFolderClassSetup(object):

class TemporaryFolderClassSetup(unittest.TestCase):
"""
Regression tests: The test starts a filetreewalker scanning for files name *.def
Such files are expected to have as first line the argument
Expand All @@ -107,7 +114,7 @@ def setUpClass(cls):

@classmethod
def tearDownClass(cls):
if (cls._root):
if cls._root:
# pass
shutil.rmtree(cls._root)

Expand All @@ -120,39 +127,39 @@ def get_extra_command_args(self, cwd):
"""Override in subclass to add arguments to command"""
return ''

def checkAllInFolder(self, foldername, expectedDefs):
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(foldername):
for dirpath, _, fnames in os.walk(folder_name):
for f in fnames:
if f.endswith('.def'):
count += 1
self._checkDef(os.path.join(dirpath, f))
self.assertEqual(count, expectedDefs)
self.check_def(os.path.join(dirpath, f))
self.assertEqual(count, expected_defs)

def _checkDef(self, path):
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 filehandle:
datalines = filehandle.readlines()
stdoutLines = int(datalines[2])
filenames = datalines[0].decode('utf8').strip()
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:]
filename[len(rel_cwd) + 1:]
for filename in glob.glob(rel_cwd + '/' + filenames)
)
args += ' ' + filenames
self._runAndCheck(path,
args,
int(datalines[1]),
[line.decode('utf8').strip() for line in datalines[3:3 + stdoutLines]],
[line.decode('utf8').strip() for line in datalines[3 + stdoutLines:]])
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 _runAndCheck(
def _run_and_compare(
self,
definition_file,
args,
Expand All @@ -164,8 +171,8 @@ def _runAndCheck(
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) = RunShellCommand(cmd, args, cwd)
print("\ncd " + cwd + " && " + cmd + ' ' + args + " 2> <filename>")
(status, out, err) = run_shell_command(cmd, args, cwd)
self.assertEqual(expected_status, status, 'bad command status %s' % status)
prefix = 'Failed check in %s comparing to %s for command: %s' % (cwd, definition_file, cmd)
compare('\n'.join(expected_err), err.decode('utf8'), prefix=prefix, show_whitespace=True)
Expand All @@ -176,29 +183,22 @@ class NoRepoSignatureTests(TemporaryFolderClassSetup, unittest.TestCase):
"""runs in a temporary folder (under /tmp in linux) without any .git/.hg/.svn file"""

def get_extra_command_args(self, cwd):
return (' --repository %s ' % self._root)

def testChromiumSample(self):
self.checkAllInFolder('./samples/chromium-sample', 1)

def testVlcSample(self):
self.checkAllInFolder('./samples/vlc-sample', 1)

def testSillySample(self):
self.checkAllInFolder('./samples/silly-sample', 5)

def testBoostSample(self):
self.checkAllInFolder('./samples/boost-sample', 4)

return f' --repository {self._root} '

def _test_name_func(fun, _, x):
del fun
return f'test{x.args[0].capitalize()}Sample-{x.args[1]}'

@parameterized.expand([(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')],
name_func=_test_name_func)
@mark.timeout(180)
def testProtobufSample(self):
self.checkAllInFolder('./samples/protobuf-sample', 1)

def testCodeliteSample(self):
self.checkAllInFolder('./samples/codelite-sample', 1)
def testSamples(self, folder, case):
self.check_def(os.path.join(f'./samples/{folder}-sample', case + '.def'))

def testV8Sample(self):
self.checkAllInFolder('./samples/v8-sample', 1)

class GitRepoSignatureTests(TemporaryFolderClassSetup, unittest.TestCase):
"""runs in a temporary folder with .git file"""
Expand All @@ -209,7 +209,8 @@ def prepare_directory(cls, root):
pass

def testCodeliteSample(self):
self.checkAllInFolder('./samples/codelite-sample', 1)
self.check_all_in_folder('./samples/codelite-sample', 1)


class MercurialRepoSignatureTests(TemporaryFolderClassSetup, unittest.TestCase):
"""runs in a temporary folder with .hg file"""
Expand All @@ -220,7 +221,8 @@ def prepare_directory(cls, root):
pass

def testCodeliteSample(self):
self.checkAllInFolder('./samples/codelite-sample', 1)
self.check_all_in_folder('./samples/codelite-sample', 1)


class SvnRepoSignatureTests(TemporaryFolderClassSetup, unittest.TestCase):
"""runs in a temporary folder with .svn file"""
Expand All @@ -231,7 +233,8 @@ def prepare_directory(cls, root):
pass

def testCodeliteSample(self):
self.checkAllInFolder('./samples/codelite-sample', 1)
self.check_all_in_folder('./samples/codelite-sample', 1)


if __name__ == '__main__':
unittest.main()
45 changes: 44 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,49 @@
[build-system]
build-backend = "setuptools.build_meta:__legacy__"
build-backend = "setuptools.build_meta"
requires = [
"setuptools",
"wheel",
]

[project]
name = "cpplint"
dynamic = ["version", "optional-dependencies"]
maintainers = [
{ name = "Aaron Liu", email = "aaronliu0130@gmail.com" },
{ name = "John Vandenberg", email = "jayvdb@gmail.com" }
]
authors = [
{ name = "Google Inc."},
{ name = "Thibault Kruse"},
{ name = "Andrew Davis", email = "theandrewdavis@gmail.com" }
]
description="Check C++ files configurably against Google's style guide"
readme = "README.rst"
requires-python = ">=3.8"
keywords = ["lint", "cpp", "c++", "google style"]
license = { text = "BSD-3-Clause" }
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"Natural Language :: English",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Software Development :: Quality Assurance",
]

[tool.setuptools.dynamic]
version = {attr = "cpplint.__VERSION__"}

[tool.setuptools.dynamic.optional-dependencies]
dev = { file = "dev-requirements" }
test = { file = ["test-requirements", "dev-requirements"] }

[project.scripts]
cpplint = "cpplint:main"
Loading
Loading