diff --git a/.ci-ignore b/.ci-ignore deleted file mode 100644 index b9610532..00000000 --- a/.ci-ignore +++ /dev/null @@ -1,2 +0,0 @@ -cpp_linter -mkdocs.yml diff --git a/.gitattributes b/.gitattributes index e1a14d22..f7c5d6a8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,3 +6,5 @@ *.py text eol=lf *.rst text eol=lf *.sh text eol=lf +*.cpp text eol=lf +*.hpp text eol=lf diff --git a/.github/workflows/mkdocs-deploy.yml b/.github/workflows/mkdocs-deploy.yml index eebd301c..7ca30a6f 100644 --- a/.github/workflows/mkdocs-deploy.yml +++ b/.github/workflows/mkdocs-deploy.yml @@ -8,8 +8,8 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 with: python-version: 3.x - name: Install python action for doc extraction diff --git a/.github/workflows/pre-commit-hooks.yml b/.github/workflows/pre-commit-hooks.yml new file mode 100644 index 00000000..e9426fc7 --- /dev/null +++ b/.github/workflows/pre-commit-hooks.yml @@ -0,0 +1,17 @@ +name: Pre-commit + +on: + push: + pull_request: + types: opened + +jobs: + check-source-files: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.x' + - run: python3 -m pip install pre-commit + - run: pre-commit run --all-files diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml new file mode 100644 index 00000000..00ad109a --- /dev/null +++ b/.github/workflows/publish-pypi.yml @@ -0,0 +1,51 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Upload Python Package + +on: + release: + branches: [master] + types: [published] + workflow_dispatch: + +permissions: + contents: read + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + # use fetch --all for setuptools_scm to work + with: + fetch-depth: 0 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + - name: Install dependencies + run: python -m pip install --upgrade pip twine + - name: Build wheel + run: python -m pip wheel -w dist --no-deps . + - name: Check distribution + run: twine check dist/* + - name: Publish package (to TestPyPI) + if: github.event_name == 'workflow_dispatch' && github.repository == 'cpp-linter/cpp-linter-action' + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.TEST_PYPI_TOKEN }} + run: twine upload --repository testpypi dist/* + - name: Publish package (to PyPI) + if: github.event_name != 'workflow_dispatch' && github.repository == 'cpp-linter/cpp-linter-action' + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: twine upload dist/* diff --git a/.github/workflows/run-dev-tests.yml b/.github/workflows/run-dev-tests.yml new file mode 100644 index 00000000..ae17ce5d --- /dev/null +++ b/.github/workflows/run-dev-tests.yml @@ -0,0 +1,113 @@ +name: "Check python code" + +on: + push: + paths: + - "**.py" + - pyproject.toml + - pre-commit-config.yaml + - ".github/workflows/run-dev-tests.yml" + pull_request: + types: opened + paths: + - "**.py" + - "**requirements*.txt" + - pyproject.toml + - pre-commit-config.yaml + - ".github/workflows/run-dev-tests.yml" + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: '3.x' + - name: Build wheel + run: python3 -m pip wheel --no-deps -w dist . + - name: Upload wheel as artifact + uses: actions/upload-artifact@v3 + with: + name: cpp-linter-action_wheel + path: ${{ github.workspace }}/dist/*.whl + + test: + needs: [build] + strategy: + fail-fast: false + matrix: + py: ['3.7', '3.8', '3.9', '3.10'] + os: ['windows-latest', ubuntu-latest] + version: ['13', '12', '11', '10', '9', '8', '7'] + include: + - tools_dir: 'N/A' + - os: 'windows-latest' + version: '10' + tools_dir: temp + - os: 'windows-latest' + version: '10' + tools_dir: temp + - os: 'windows-latest' + version: '11' + tools_dir: temp + - os: 'windows-latest' + version: '12' + tools_dir: temp + - os: 'ubuntu-latest' + version: '13' + tools_dir: temp + # - version: '14' + # tools_dir: temp + - version: '7' + tools_dir: temp + - version: '8' + tools_dir: temp + - version: '9' + tools_dir: temp + + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.py }} + + - name: download wheel artifact + uses: actions/download-artifact@v3 + with: + name: cpp-linter-action_wheel + path: dist + + - name: Install workflow deps + # using a wildcard as filename on Windows requires a bash shell + shell: bash + run: python3 -m pip install pytest coverage[toml] dist/*.whl + + - name: Install clang-tools + if: matrix.tools_dir == 'temp' + run: | + python -m pip install clang-tools + clang-tools --install ${{ matrix.version }} --directory ${{ runner.temp }}/clang-tools + + - name: Collect Coverage (native clang install) + if: matrix.tools_dir == 'N/A' + env: + CLANG_VERSION: ${{ matrix.version }} + run: coverage run -m pytest + + - name: Collect Coverage (non-native clang install) + if: matrix.tools_dir == 'temp' + env: + CLANG_VERSION: ${{ runner.temp }}/clang-tools + run: coverage run -m pytest + + - run: coverage report && coverage xml + + - uses: codecov/codecov-action@v3 + if: matrix.os == 'ubuntu-latest' && matrix.version == '12' && matrix.py == '3.10' + with: + files: ./coverage.xml + fail_ci_if_error: true # optional (default = false) + verbose: true # optional (default = false) diff --git a/.github/workflows/run-pylint.yml b/.github/workflows/run-pylint.yml deleted file mode 100644 index 0d103b95..00000000 --- a/.github/workflows/run-pylint.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: "Check python code" - -on: - push: - paths: - - "**.py" - - .pylintrc - pull_request: - types: opened - paths: - - "**.py" - - .pylintrc - -jobs: - using-pylint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v1 - with: - python-version: 3.7 - - name: Install pylint and action deps - run: | - python3 -m pip install --upgrade pylint - python3 -m pip install -r requirements.txt - - name: run pylint - run: | - pylint cpp_linter/** - pylint setup.py diff --git a/.github/workflows/run-test.yml b/.github/workflows/run-test.yml index f8fd8ad3..eaf19106 100644 --- a/.github/workflows/run-test.yml +++ b/.github/workflows/run-test.yml @@ -23,12 +23,13 @@ jobs: git push origin latest test-action: + needs: [add-tag] runs-on: ubuntu-latest steps: - uses: convictional/trigger-workflow-and-wait@v1.6.3 with: owner: cpp-linter - repo: test-cpp-linter-action + repo: test-cpp-linter-action github_token: ${{ secrets.PAT_TOKEN }} workflow_file_name: cpp-lint-action.yml ref: master @@ -38,12 +39,13 @@ jobs: trigger_workflow: true wait_workflow: true test-package: + needs: [add-tag] runs-on: ubuntu-latest steps: - uses: convictional/trigger-workflow-and-wait@v1.6.3 with: owner: cpp-linter - repo: test-cpp-linter-action + repo: test-cpp-linter-action github_token: ${{ secrets.PAT_TOKEN }} workflow_file_name: cpp-lint-package.yml ref: master diff --git a/.gitignore b/.gitignore index 7806c15a..e4f88937 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,8 @@ coverage.xml *.py,cover .hypothesis/ .pytest_cache/ +tests/*/test*.json +tests/**/*.c # Translations *.mo diff --git a/.gitpod.yml b/.gitpod.yml index 7fd69193..c21ea2f0 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -1,2 +1,2 @@ tasks: - - init: pip install -r requirements.txt \ No newline at end of file + - init: pip install -r requirements.txt diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..e5ea8a37 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,36 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-docstring-first + - id: check-added-large-files + - id: check-yaml + - id: check-toml + - id: requirements-txt-fixer + - repo: https://github.com/python/black + rev: '22.6.0' + hooks: + - id: black + args: ["--diff"] + - repo: https://github.com/pycqa/pylint + rev: v2.14.5 + hooks: + - id: pylint + name: pylint (action code) + types: [python] + exclude: "^(docs/|tests/|setup.py$)" + additional_dependencies: [pyyaml, requests] + - repo: local + # this is a "local" hook to run mypy (see https://pre-commit.com/#repository-local-hooks) + # because the mypy project doesn't seem to be compatible with pre-commit hooks + hooks: + - id: mypy + name: mypy + description: type checking with mypy tool + language: python + types: [python] + entry: mypy + exclude: "^(docs/|setup.py$)" + additional_dependencies: [mypy, types-pyyaml, types-requests, rich, requests, pytest, pyyaml, '.'] diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index e6a2eed7..00000000 --- a/.pylintrc +++ /dev/null @@ -1,396 +0,0 @@ -[MASTER] - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist= - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS - -# Add files or directories matching the regex patterns to the blacklist. The -# regex matches against base names, not paths. -ignore-patterns= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Use multiple processes to speed up Pylint. -# jobs=1 -jobs=2 - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Pickle collected data for later comparisons. -persistent=yes - -# Specify a configuration file. -#rcfile= - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -# disable=print-statement,parameter-unpacking,unpacking-in-except,backtick,long-suffix,import-star-module-level,raw-checker-failed,bad-inline-option,locally-disabled,locally-enabled,file-ignored,suppressed-message,useless-suppression,apply-builtin,basestring-builtin,buffer-builtin,cmp-builtin,coerce-builtin,execfile-builtin,file-builtin,long-builtin,raw_input-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,no-absolute-import,dict-iter-method,dict-view-method,next-method-called,metaclass-assignment,indexing-exception,raising-string,reload-builtin,oct-method,hex-method,nonzero-method,cmp-method,input-builtin,round-builtin,intern-builtin,unichr-builtin,map-builtin-not-iterating,zip-builtin-not-iterating,range-builtin-not-iterating,filter-builtin-not-iterating,using-cmp-argument,eq-without-hash,div-method,idiv-method,rdiv-method,exception-message-attribute,invalid-str-codec,sys-max-int,bad-python3-import,bad-whitespace -disable=invalid-sequence-index,anomalous-backslash-in-string,too-few-public-methods,consider-using-f-string,subprocess-run-check - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable= - - -[REPORTS] - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio).You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages -reports=no - -# Activate the evaluation score. -score=yes - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -# notes=FIXME,XXX,TODO -notes=FIXME,XXX - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb,_callback - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,future.builtins - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -# expected-line-ending-format= -expected-line-ending-format=LF - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=88 - -# Maximum number of lines in a module -max-module-lines=1000 - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - -[SIMILARITIES] - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - -# Minimum lines number of a similarity. -min-similarity-lines=4 - - -[BASIC] - -# Regular expression matching correct argument names -argument-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct attribute names -attr-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata - -# Regular expression matching correct class attribute names -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Regular expression matching correct class names -# class-rgx=[A-Z_][a-zA-Z0-9]+$ -class-rgx=[A-Z_][a-zA-Z0-9_]+$ - -# Regular expression matching correct constant names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Regular expression matching correct function names -function-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Good variable names which should always be accepted, separated by a comma -# good-names=i,j,k,ex,Run,_ -good-names=r,g,b,w,i,j,k,n,x,y,z,ex,ok,Run,_ - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no - -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Regular expression matching correct method names -method-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - -# Regular expression matching correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -property-classes=abc.abstractproperty - -# Regular expression matching correct variable names -variable-rgx=(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$ - - -[IMPORTS] - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=optparse,tkinter.tix - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=8 - -# Maximum number of attributes for a class (see R0902). -# max-attributes=7 -max-attributes=11 - -# Maximum number of boolean expressions in a if statement -max-bool-expr=5 - -# Maximum number of branch for function / method body -max-branches=12 - -# Maximum number of locals for function / method body -max-locals=15 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of statements in function / method body -max-statements=50 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=1 - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=Exception diff --git a/Dockerfile b/Dockerfile index 2ac40d28..fbb6128b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ LABEL maintainer="shenxianpeng <20297606+shenxianpeng@users.noreply.github.com>" RUN apt-get update && apt-get -y install python3-pip COPY cpp_linter/ pkg/cpp_linter/ -COPY setup.py pkg/setup.py +COPY pyproject.toml pkg/pyproject.toml RUN python3 -m pip install pkg/ # github action args use the CMD option diff --git a/README.md b/README.md index faa23d1c..b854411e 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/cpp-linter/cpp-linter-action/cpp-linter?label=cpp-linter&logo=Github&style=flat-square)](https://github.com/cpp-linter/cpp-linter-action/actions/workflows/cpp-linter.yml) [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/cpp-linter/cpp-linter-action/MkDocs%20Deploy?label=docs&logo=Github&style=flat-square)](https://github.com/cpp-linter/cpp-linter-action/actions/workflows/mkdocs-deploy.yml) ![GitHub](https://img.shields.io/github/license/cpp-linter/cpp-linter-action?label=license&logo=github&style=flat-square) +[![codecov](https://codecov.io/gh/cpp-linter/cpp-linter-action/branch/master/graph/badge.svg?token=4SF7UEDEZ2)](https://codecov.io/gh/cpp-linter/cpp-linter-action) A Github Action for linting C/C++ code integrating clang-tidy and clang-format to collect feedback provided in the form of thread comments and/or annotations. @@ -40,8 +41,7 @@ jobs: - name: Fail fast?! if: steps.linter.outputs.checks-failed > 0 - run: | - echo "Some files failed the linting checks!" + run: echo "Some files failed the linting checks!" # for actual deployment # run: exit 1 ``` @@ -50,7 +50,9 @@ jobs: #### `style` -- **Description**: The style rules to use. Set this to 'file' to have clang-format use the closest relative .clang-format file. +- **Description**: The style rules to use. + - Set this to 'file' to have clang-format use the closest relative .clang-format file. + - Set this to a blank string (`''`) to disable the use of clang-format entirely. - Default: 'llvm' #### `extensions` @@ -60,9 +62,9 @@ jobs: #### `tidy-checks` -- **Description**: Comma-separated list of globs with optional '-' prefix. Globs are processed in order of appearance in the list. Globs without '-' prefix add checks with matching names to the set, globs with the '-' prefix remove checks with matching names from the set of enabled checks. This option's value is appended to the value of the 'Checks' option in a .clang-tidy file (if any). - - It is possible to disable clang-tidy entirely by setting this option to '-\*'. This allows using only clang-format to lint your source files. - - It is also possible to rely solely on a .clang-tidy config file by specifying this option as a blank string (''). +- **Description**: Comma-separated list of globs with optional `-` prefix. Globs are processed in order of appearance in the list. Globs without `-` prefix add checks with matching names to the set, globs with the `-` prefix remove checks with matching names from the set of enabled checks. This option's value is appended to the value of the 'Checks' option in a .clang-tidy file (if any). + - It is possible to disable clang-tidy entirely by setting this option to `'-*'`. + - It is also possible to rely solely on a .clang-tidy config file by specifying this option as a blank string (`''`). - Default: 'boost-\*,bugprone-\*,performance-\*,readability-\*,portability-\*,modernize-\*,clang-analyzer-\*,cppcoreguidelines-\*' #### `repo-root` @@ -73,39 +75,43 @@ jobs: #### `version` - **Description**: The desired version of the [clang-tools](https://hub.docker.com/r/xianpengshen/clang-tools) to use. Accepted options are strings which can be 14, 13, 12, 11, 10, 9, or 8. - - Set this option to a blank string ('') to use the platform's default installed version. + - Set this option to a blank string (`''`) to use the platform's default installed version. + - This value can also be a path to where the clang tools are installed (if using a custom install location). Because all paths specified here are converted to absolute, using a relative path as a value may not be compatible when using the docker environment (see [Running without the docker container](#running-without-the-docker-container)). - Default: '12' #### `verbosity` -- **Description**: This controls the action's verbosity in the workflow's logs. Supported options are defined by the python logging library's log levels. This option does not affect the verbosity of resulting comments or annotations. +- **Description**: This controls the action's verbosity in the workflow's logs. Supported options are defined by the [python logging library's log levels](https://docs.python.org/3/library/logging.html#logging-levels). This option does not affect the verbosity of resulting thread comments or file annotations. - Default: '10' #### `lines-changed-only` -- **Description**: Set this option to true to only analyze changes in the event's diff. -- Default: false +- **Description**: This controls what part of the files are analyzed. The following values are accepted: + - false: All lines in a file are analyzed. + - true: Only lines in the diff that contain additions are analyzed. + - diff: All lines in the diff are analyzed (including unchanged lines but not subtractions). +- Default: false. #### `files-changed-only` -- **Description**: Set this option to false to analyze any source files in the repo. +- **Description**: Set this option to false to analyze any source files in the repo. This is automatically enabled if lines-changed-only is enabled. - Default: true - NOTE: The `GITHUB_TOKEN` should be supplied when running on a private repository with this option enabled, otherwise the runner does not not have the privilege to list changed files for an event. See [Authenticating with the GITHUB_TOKEN](https://docs.github.com/en/actions/reference/authentication-in-a-workflow) #### `ignore` - **Description**: Set this option with string of path(s) to ignore. - - In the case of multiple paths, you can use a pipe character ('|') + - In the case of multiple paths, you can use a pipe character (`|`) to separate the multiple paths. Multiple lines are forbidden as an input to this option; it must be a single string. - This can also have files, but the file's relative path has to be specified as well. - - There is no need to use './' for each entry; a blank string ('') represents + - There is no need to use `./` for each entry; a blank string (`''`) represents the repo-root path (specified by the `repo-root` input option). - - Submodules are automatically ignored. Hidden directories (beginning with a '.') are also ignored automatically. - - Prefix a path with a bang ('!') to make it explicitly _not_ ignored - order of - multiple paths does _not_ take precedence. The '!' prefix can be applied to + - Submodules are automatically ignored. Hidden directories (beginning with a `.`) are also ignored automatically. + - Prefix a path with a bang (`!`) to make it explicitly _not_ ignored. The order of + multiple paths does _not_ take precedence. The `!` prefix can be applied to a submodule's path (if desired) but not hidden directories. - - Glob patterns are not supported here. All asterisk characters ('\*') are literal. + - Glob patterns are not supported here. All asterisk characters (`*`) are literal. - Default: '.github' #### `thread-comments` @@ -124,6 +130,7 @@ jobs: #### `database` - **Description**: The directory containing compilation database (like compile_commands.json) file. + - This option doesn't seems to work properly from the docker environment. Instead we recommend using this option when see [running without the docker container](#running-without-the-docker-container). - Default: '' ### Outputs @@ -166,14 +173,14 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: actions/setup-python@v3 + - uses: actions/setup-python@v4 # this step can be skipped if the desired # version already comes with the runner's OS - name: Install clang-tools uses: KyleMayes/install-llvm-action@v1 with: - # v12 is the recommended minimum for the Visual Studio compiler (on Windows) + # v13 is the recommended minimum for the Visual Studio compiler (on Windows) version: 14 # specifying an install path is required (on Windows) because installing # multiple versions on Windows runners needs non-default install paths. @@ -184,11 +191,11 @@ jobs: - name: run linter as a python package id: linter - # pass the installed path to the '--version' argument (Windows only). - # Any other OS-based runners only take the version number. + # Pass the installed path to the '--version' argument. + # Alternatively, pass the version number. # Example. run: cpp-linter --version=14 # Omit the version option if using the default version available in the OS. - run: cpp-linter --version=${{ runner.temp }}/llvm + run: cpp-linter --version=${{ runner.temp }}/llvm - name: Fail fast?! if: steps.linter.outputs.checks-failed > 0 @@ -229,13 +236,13 @@ is equivalent to ### Annotations -![clang-format annotations](./docs/images/annotations-clang-format.png) +![clang-format annotations](https://raw.githubusercontent.com/cpp-linter/cpp-linter-action/master/docs/images/annotations-clang-format.png) -![clang-tidy annotations](./docs/images/annotations-clang-tidy.png) +![clang-tidy annotations](https://raw.githubusercontent.com/cpp-linter/cpp-linter-action/master/docs/images/annotations-clang-tidy.png) ### Thread Comment -![sample comment](./docs/images/comment.png) +![sample comment](https://raw.githubusercontent.com/cpp-linter/cpp-linter-action/master/docs/images/comment.png) diff --git a/action.yml b/action.yml index cdc7dc2a..62673c57 100644 --- a/action.yml +++ b/action.yml @@ -80,6 +80,8 @@ outputs: runs: using: "docker" image: "Dockerfile" + env: + USING_CLANG_TOOLS_DOCKER: 'true' args: - --style=${{ inputs.style }} - --extensions=${{ inputs.extensions }} diff --git a/cpp_linter/__init__.py b/cpp_linter/__init__.py index db14aa92..84394cdf 100644 --- a/cpp_linter/__init__.py +++ b/cpp_linter/__init__.py @@ -1,10 +1,17 @@ """The Base module of the `cpp_linter` package. This holds the objects shared by multiple modules.""" -import io import os +from pathlib import Path +import platform import logging +from typing import TYPE_CHECKING, List, Dict, Union, Any from requests import Response +if TYPE_CHECKING: # Used to avoid circular imports + from cpp_linter.clang_format_xml import XMLFixit + from cpp_linter.clang_tidy_yml import YMLFixit + from cpp_linter.clang_tidy import TidyNotification + FOUND_RICH_LIB = False try: from rich.logging import RichHandler @@ -16,10 +23,10 @@ handlers=[RichHandler(show_time=False)], ) -except ImportError: +except ImportError: # pragma: no cover logging.basicConfig() -#: The logging.Logger object used for outputing data. +#: The logging.Logger object used for outputting data. logger = logging.getLogger("CPP Linter") if not FOUND_RICH_LIB: logger.debug("rich module not found") @@ -28,20 +35,24 @@ IS_ON_RUNNER = bool(os.getenv("CI")) GITHUB_SHA = os.getenv("GITHUB_SHA", "") GITHUB_TOKEN = os.getenv("GITHUB_TOKEN", os.getenv("GIT_REST_API", "")) -API_HEADERS = {"Accept": "application/vnd.github.v3.text+json",} +API_HEADERS = { + "Accept": "application/vnd.github.v3.text+json", +} if GITHUB_TOKEN: API_HEADERS["Authorization"] = f"token {GITHUB_TOKEN}" +IS_ON_WINDOWS = platform.system().lower() == "windows" + class Globals: """Global variables for re-use (non-constant).""" - PAYLOAD_TIDY = "" + PAYLOAD_TIDY: str = "" """The accumulated output of clang-tidy (gets appended to OUTPUT)""" - OUTPUT = "" + OUTPUT: str = "" """The accumulated body of the resulting comment that gets posted.""" - FILES = [] + FILES: List[Dict[str, Any]] = [] """The responding payload containing info about changed files.""" - EVENT_PAYLOAD = {} + EVENT_PAYLOAD: Dict[str, Any] = {} """The parsed JSON of the event payload.""" response_buffer = Response() """A shared response object for `requests` module.""" @@ -52,13 +63,13 @@ class GlobalParser: following attributes represents a clang-tool's output for 1 source file. """ - tidy_notes = [] + tidy_notes = [] # type: List[TidyNotification] """This can only be a `list` of type [`TidyNotification`][cpp_linter.clang_tidy.TidyNotification]""" - tidy_advice = [] + tidy_advice = [] # type: List[YMLFixit] """This can only be a `list` of type [`YMLFixit`][cpp_linter.clang_tidy_yml.YMLFixit]""" - format_advice = [] + format_advice = [] # type: List[XMLFixit] """This can only be a `list` of type [`XMLFixit`][cpp_linter.clang_format_xml.XMLFixit]""" @@ -76,28 +87,40 @@ def get_line_cnt_from_cols(file_path: str, offset: int) -> tuple: - Index 0 is the line number for the given offset. - Index 1 is the column number for the given offset on the line. """ - line_cnt = 1 - last_lf_pos = 0 - cols = 1 - file_path = file_path.replace("/", os.sep) # logger.debug("Getting line count from %s at offset %d", file_path, offset) - with io.open(file_path, "rb") as src_file: - max_len = src_file.seek(0, io.SEEK_END) - src_file.seek(0, io.SEEK_SET) - while src_file.tell() != offset and src_file.tell() < max_len: - char = src_file.read(1) - if char == b"\n": - line_cnt += 1 - last_lf_pos = src_file.tell() - 1 # -1 because LF is part of offset - cols = src_file.tell() - last_lf_pos - return (line_cnt, cols) + contents = Path(file_path).read_bytes()[:offset] + return (contents.count(b"\n") + 1, offset - contents.rfind(b"\n")) + + +def range_of_changed_lines( + file_obj: Dict[str, Any], lines_changed_only: int +) -> List[int]: + """Assemble a list of lines changed. + + Args: + file_obj: The file's JSON object. + lines_changed_only: A flag to indicate the focus of certain lines. + + - 0: focuses on all lines in file. + - 1: focuses on any lines shown in the event's diff + (may include unchanged lines). + - 2: focuses strictly on lines in the diff that contain additions. + Returns: + A list of line numbers for which to give attention. + """ + if lines_changed_only: + ranges = file_obj["line_filter"][ + "diff_chunks" if lines_changed_only == 1 else "lines_added" + ] + return [l for r in ranges for l in range(r[0], r[1])] + return [] def log_response_msg() -> bool: """Output the response buffer's message on a failed request. Returns: - A bool decribing if response's status code was less than 400. + A bool describing if response's status code was less than 400. """ if Globals.response_buffer.status_code >= 400: logger.error( @@ -107,3 +130,31 @@ def log_response_msg() -> bool: ) return False return True + + +def assemble_version_exec(tool_name: str, specified_version: str) -> str: + """Assembles the command to the executable of the given clang tool based on given + version information. + + Args: + tool_name: The name of the clang tool to be executed. + specified_version: The version number or the installed path to a version of + the tool's executable. + """ + suffix = ".exe" if IS_ON_WINDOWS else "" + if specified_version.isdigit(): # version info is not a path + # let's assume the exe is in the PATH env var + if IS_ON_WINDOWS: + # installs don't usually append version number to exe name on Windows + return f"{tool_name}{suffix}" # omit version number + return f"{tool_name}-{specified_version}{suffix}" + version_path = Path(specified_version).resolve() # make absolute + for path in [ + # if installed via KyleMayes/install-llvm-action using the `directory` option + version_path / "bin" / (tool_name + suffix), + # if installed via clang-tools-pip pkg using the `-d` option + version_path / (tool_name + suffix), + ]: + if path.exists(): + return str(path) + return tool_name + suffix diff --git a/cpp_linter/clang_format_xml.py b/cpp_linter/clang_format_xml.py index 9df31f8d..7ba4c18b 100644 --- a/cpp_linter/clang_format_xml.py +++ b/cpp_linter/clang_format_xml.py @@ -1,5 +1,6 @@ """Parse output from clang-format's XML suggestions.""" -import os +from pathlib import PurePath +from typing import List, Optional import xml.etree.ElementTree as ET from . import GlobalParser, get_line_cnt_from_cols @@ -47,7 +48,7 @@ def __init__(self, line_numb: int): line_numb: The line number of about the replacements """ self.line = line_numb - self.replacements = [] + self.replacements: List[FormatReplacement] = [] def __repr__(self): return ( @@ -73,8 +74,8 @@ def __init__(self, filename: str): filename: The source file's name for which the contents of the xml file exported by clang-tidy. """ - self.filename = filename.replace(os.sep, "/") - self.replaced_lines = [] + self.filename = PurePath(filename).as_posix() + self.replaced_lines: List[FormatReplacementLine] = [] def __repr__(self) -> str: return ( @@ -82,7 +83,7 @@ def __repr__(self) -> str: f"replacements for {self.filename}>" ) - def log_command(self, style: str) -> str: + def log_command(self, style: str, line_filter: List[int]) -> Optional[str]: """Output a notification as a github log command. !!! info See Also @@ -104,13 +105,18 @@ def log_command(self, style: str) -> str: style = style.upper() else: style = style.title() - + line_list = [] + for fix in self.replaced_lines: + if not line_filter or (line_filter and fix.line in line_filter): + line_list.append(str(fix.line)) + if not line_list: + return None return ( "::notice file={name},title=Run clang-format on {name}::" "File {name} (lines {lines}): Code does not conform to {style_guide} " "style guidelines.".format( name=self.filename, - lines=", ".join(str(f.line) for f in self.replaced_lines), + lines=", ".join(line_list), style_guide=style, ) ) @@ -142,21 +148,3 @@ def parse_format_replacements_xml(src_filename: str): elif fixit.replaced_lines and line == fixit.replaced_lines[-1].line: fixit.replaced_lines[-1].replacements.append(fix) GlobalParser.format_advice.append(fixit) - - -def print_fixits(): - """Print all [`XMLFixit`][cpp_linter.clang_format_xml.XMLFixit] objects in - [`format_advice`][cpp_linter.GlobalParser.format_advice].""" - for fixit in GlobalParser.format_advice: - print(repr(fixit)) - for line_fix in fixit.replaced_lines: - print(" " + repr(line_fix)) - for fix in line_fix.replacements: - print("\t" + repr(fix)) - - -if __name__ == "__main__": - import sys - - parse_format_replacements_xml(sys.argv[1]) - print_fixits() diff --git a/cpp_linter/clang_tidy.py b/cpp_linter/clang_tidy.py index 673ba3c0..3eb0167c 100644 --- a/cpp_linter/clang_tidy.py +++ b/cpp_linter/clang_tidy.py @@ -1,9 +1,11 @@ """Parse output from clang-tidy's stdout""" -import os +from pathlib import Path, PurePath import re -from . import GlobalParser, IS_ON_RUNNER # , logger +from typing import Tuple, Union, List, cast +from . import GlobalParser + +NOTE_HEADER = re.compile(r"^(.*):(\d+):(\d+):\s(\w+):(.*)\[(.*)\]$") -NOTE_HEADER = re.compile("^(.*):(\d+):(\d+):\s(\w+):(.*)\[(.*)\]$") class TidyNotification: """Create a object that decodes info from the clang-tidy output's initial line that @@ -20,7 +22,10 @@ class TidyNotification: notification. """ - def __init__(self, notification_line: tuple): + def __init__( + self, + notification_line: Tuple[str, Union[int, str], Union[int, str], str, str, str], + ): """ Args: notification_line: The first line in the notification parsed into a tuple of @@ -37,25 +42,35 @@ def __init__(self, notification_line: tuple): self.diagnostic, ) = notification_line - self.note_info: str = self.note_info.strip() - self.note_type: str = self.note_type.strip() + self.note_info = self.note_info.strip() + self.note_type = self.note_type.strip() self.line = int(self.line) self.cols = int(self.cols) - self.filename: str = self.filename.replace(os.getcwd() + os.sep, "") - self.fixit_lines = [] + self.filename = ( + PurePath(self.filename).as_posix().replace(Path.cwd().as_posix(), "") + ) + self.fixit_lines: List[str] = [] def __repr__(self) -> str: + concerned_code = "" + if self.fixit_lines: + if not self.fixit_lines[-1].endswith("\n"): + # some notifications' code-blocks don't end in a LF + self.fixit_lines[-1] += "\n" # and they should for us + concerned_code = "```{}\n{}```\n".format( + PurePath(self.filename).suffix.lstrip("."), + "\n".join(self.fixit_lines), + ) return ( "
\n{}:{}:{}: {}: [{}]" - "\n\n> {}\n

\n\n```{}\n{}```\n

\n
\n\n".format( + "\n\n> {}\n

\n\n{}

\n\n\n".format( self.filename, self.line, self.cols, self.note_type, self.diagnostic, self.note_info, - os.path.splitext(self.filename)[1], - "".join(self.fixit_lines), + concerned_code, ) ) @@ -70,9 +85,7 @@ def log_command(self) -> str: - [A notice message](https://docs.github.com/en/actions/learn-github- actions/workflow-commands-for-github-actions#setting-a-notice-message) """ - filename = self.filename - if IS_ON_RUNNER: - filename = self.filename.replace(os.sep, "/") + filename = self.filename.replace("\\", "/") return ( "::{} file={file},line={line},title={file}:{line}:{cols} [{diag}]::" "{info}".format( @@ -89,25 +102,18 @@ def log_command(self) -> str: def parse_tidy_output() -> None: """Parse clang-tidy output in a file created from stdout.""" notification = None - with open("clang_tidy_report.txt", "r", encoding="utf-8") as tidy_out: - for line in tidy_out.readlines(): - match = re.match(NOTE_HEADER, line) - if match is not None: - notification = TidyNotification(match.groups()) - GlobalParser.tidy_notes.append(notification) - elif notification is not None: - notification.fixit_lines.append(line) - - -def print_fixits(): - """Print out all clang-tidy notifications from stdout (which are saved to - clang_tidy_report.txt and allocated to - [`tidy_notes`][cpp_linter.GlobalParser.tidy_notes].""" - for notification in GlobalParser.tidy_notes: - print("found", len(GlobalParser.tidy_notes), "tidy_notes") - print(repr(notification)) - - -if __name__ == "__main__": - parse_tidy_output() - print_fixits() + tidy_out = Path("clang_tidy_report.txt").read_text(encoding="utf-8") + for line in tidy_out.splitlines(): + match = re.match(NOTE_HEADER, line) + if match is not None: + notification = TidyNotification( + cast( + Tuple[str, Union[int, str], Union[int, str], str, str, str], + match.groups(), + ) + ) + GlobalParser.tidy_notes.append(notification) + elif notification is not None: + # append lines of code that are part of + # the previous line's notification + notification.fixit_lines.append(line) diff --git a/cpp_linter/clang_tidy_yml.py b/cpp_linter/clang_tidy_yml.py index 5628b602..26de4e7c 100644 --- a/cpp_linter/clang_tidy_yml.py +++ b/cpp_linter/clang_tidy_yml.py @@ -1,11 +1,12 @@ """Parse output from clang-tidy's YML format""" -import os +from pathlib import Path, PurePath +from typing import List, cast, Dict, Any import yaml -from . import GlobalParser, get_line_cnt_from_cols +from . import GlobalParser, get_line_cnt_from_cols, logger CWD_HEADER_GUARD = bytes( - os.getcwd().upper().replace(os.sep, "_").replace("-", "_"), encoding="utf-8" + "_".join([p.upper().replace("-", "_") for p in Path.cwd().parts]), encoding="utf-8" ) #: The constant used to trim absolute paths from header guard suggestions. @@ -34,7 +35,7 @@ def __init__(self, diagnostic_name: str): self.line = 0 self.cols = 0 self.null_len = 0 - self.replacements = [] + self.replacements: List["TidyReplacement"] = [] def __repr__(self): """a str representation of all attributes.""" @@ -51,7 +52,7 @@ class TidyReplacement: line (int): The replacement content's starting line cols (int): The replacement content's starting columns null_len (int): The number of bytes discarded from `cols` - text (list): The replacement content's text (each `str` item is a line) + text (bytes): The replacement content's text. """ def __init__(self, line_cnt: int, cols: int, length: int): @@ -64,7 +65,7 @@ def __init__(self, line_cnt: int, cols: int, length: int): self.line = line_cnt self.cols = cols self.null_len = length - self.text = [] + self.text: bytes = b"" def __repr__(self) -> str: return ( @@ -87,8 +88,8 @@ def __init__(self, filename: str) -> None: Args: filename: The source file's name (with path) concerning the suggestion. """ - self.filename = filename.replace(os.getcwd() + os.sep, "").replace(os.sep, "/") - self.diagnostics = [] + self.filename = PurePath(filename).relative_to(Path.cwd()).as_posix() + self.diagnostics: List[TidyDiagnostic] = [] def __repr__(self) -> str: return ( @@ -101,24 +102,30 @@ def parse_tidy_suggestions_yml(): """Read a YAML file from clang-tidy and create a list of suggestions from it. Output is saved to [`tidy_advice`][cpp_linter.GlobalParser.tidy_advice]. """ - yml = {} - with open("clang_tidy_output.yml", "r", encoding="utf-8") as yml_file: - yml = yaml.safe_load(yml_file) + yml_file = Path("clang_tidy_output.yml").read_text(encoding="utf-8") + yml = yaml.safe_load(yml_file) fixit = YMLFixit(yml["MainSourceFile"]) + for diag_results in yml["Diagnostics"]: diag = TidyDiagnostic(diag_results["DiagnosticName"]) - diag.message = diag_results["DiagnosticMessage"]["Message"] - diag.line, diag.cols = get_line_cnt_from_cols( - yml["MainSourceFile"], diag_results["DiagnosticMessage"]["FileOffset"] - ) - for replacement in diag_results["DiagnosticMessage"]["Replacements"]: + if "DiagnosticMessage" in cast(Dict[str, Any], diag_results).keys(): + msg = diag_results["DiagnosticMessage"]["Message"] + offset = diag_results["DiagnosticMessage"]["FileOffset"] + replacements = diag_results["DiagnosticMessage"]["Replacements"] + else: # prior to clang-tidy v9, the YML output was structured differently + msg = diag_results["Message"] + offset = diag_results["FileOffset"] + replacements = diag_results["Replacements"] + diag.message = msg + diag.line, diag.cols = get_line_cnt_from_cols(yml["MainSourceFile"], offset) + for replacement in [] if replacements is None else replacements: line_cnt, cols = get_line_cnt_from_cols( yml["MainSourceFile"], replacement["Offset"] ) fix = TidyReplacement(line_cnt, cols, replacement["Length"]) fix.text = bytes(replacement["ReplacementText"], encoding="utf-8") if fix.text.startswith(b"header is missing header guard"): - print( + logger.debug( "filtering header guard suggestion (making relative to repo root)" ) fix.text = fix.text.replace(CWD_HEADER_GUARD, b"") @@ -126,18 +133,3 @@ def parse_tidy_suggestions_yml(): fixit.diagnostics.append(diag) # filter out absolute header guards GlobalParser.tidy_advice.append(fixit) - - -def print_fixits(): - """Print all [`YMLFixit`][cpp_linter.clang_tidy_yml.YMLFixit] objects in - [`tidy_advice`][cpp_linter.GlobalParser.tidy_advice].""" - for fix in GlobalParser.tidy_advice: - for diag in fix.diagnostics: - print(repr(diag)) - for replac in diag.replacements: - print(" " + repr(replac), f"\n\treplace text:\n{replac.text}") - - -if __name__ == "__main__": - parse_tidy_suggestions_yml() - print_fixits() diff --git a/cpp_linter/run.py b/cpp_linter/run.py index 9f91d1b6..4418bd34 100644 --- a/cpp_linter/run.py +++ b/cpp_linter/run.py @@ -11,11 +11,13 @@ https://docs.github.com/en/rest/reference/issues) """ import subprocess +from pathlib import Path, PurePath import os import sys import argparse import configparser import json +from typing import cast, List, Dict, Any, Tuple import requests from . import ( Globals, @@ -25,12 +27,14 @@ GITHUB_TOKEN, GITHUB_SHA, API_HEADERS, + IS_ON_RUNNER, log_response_msg, + range_of_changed_lines, + assemble_version_exec, ) - from .clang_tidy_yml import parse_tidy_suggestions_yml from .clang_format_xml import parse_format_replacements_xml -from .clang_tidy import parse_tidy_output +from .clang_tidy import parse_tidy_output, TidyNotification from .thread_comments import remove_bot_comments, list_diff_comments # , get_review_id @@ -39,9 +43,9 @@ GITHUB_API_URL = os.getenv("GITHUB_API_URL", "https://api.github.com") GITHUB_REPOSITORY = os.getenv("GITHUB_REPOSITORY", "") GITHUB_EVENT_NAME = os.getenv("GITHUB_EVENT_NAME", "unknown") -RUNNER_WORKSPACE = os.getenv("RUNNER_WORKSPACE", "") - -IS_ON_WINDOWS = sys.platform.startswith("win32") +GITHUB_WORKSPACE = os.getenv("GITHUB_WORKSPACE", "") +IS_USING_DOCKER = os.getenv("USING_CLANG_TOOLS_DOCKER", os.getenv("CLANG_VERSIONS")) +RUNNER_WORKSPACE = "/github/workspace" if IS_USING_DOCKER else GITHUB_WORKSPACE # setup CLI args cli_arg_parser = argparse.ArgumentParser( @@ -50,7 +54,8 @@ cli_arg_parser.add_argument( "-v", "--verbosity", - default="10", + type=int, + default=10, help="The logging level. Defaults to level 20 (aka 'logging.INFO').", ) cli_arg_parser.add_argument( @@ -86,13 +91,14 @@ "--version", default="", help="The desired version of the clang tools to use. Accepted options are strings " - "which can be 8, 9, 10, 11, 12, 13, 14. Defaults to %(default)s. On Windows, this " - "can also be a path to the install location of LLVM", + "which can be 8, 9, 10, 11, 12, 13, 14. Defaults to '%(default)s'. On Windows, " + "this can also be a path to the install location of LLVM", ) cli_arg_parser.add_argument( "-e", "--extensions", - default="c,h,C,H,cpp,hpp,cc,hh,c++,h++,cxx,hxx", + default=["c", "h", "C", "H", "cpp", "hpp", "cc", "hh", "c++", "h++", "cxx", "hxx"], + type=lambda i: [ext.strip().lstrip(".") for ext in i.split(",")], help="The file extensions to run the action against. This comma-separated string " "defaults to %(default)s.", ) @@ -106,7 +112,7 @@ cli_arg_parser.add_argument( "-i", "--ignore", - nargs="?", + default=".github", help="Set this option with paths to ignore. In the case of multiple " "paths, you can set this option (multiple times) for each path. This can " "also have files, but the file's relative path has to be specified as well " @@ -115,9 +121,10 @@ cli_arg_parser.add_argument( "-l", "--lines-changed-only", - default="false", - type=lambda input: input.lower() == "true", - help="Set this option to 'true' to only analyse changes in the event's diff. " + default=0, + type=lambda a: 1 if a.lower() == "true" else (2 if a.lower() == "strict" else 0), + help="Set this option to 'true' to only analyze changes in the event's diff. " + "Set this to 'strict' to only analyze additions in the event's diff. " "Defaults to %(default)s.", ) cli_arg_parser.add_argument( @@ -125,7 +132,7 @@ "--files-changed-only", default="false", type=lambda input: input.lower() == "true", - help="Set this option to 'false' to analyse any source files in the repo. " + help="Set this option to 'false' to analyze any source files in the repo. " "Defaults to %(default)s.", ) cli_arg_parser.add_argument( @@ -162,29 +169,29 @@ def set_exit_code(override: int = None) -> int: # setup a separate logger for using github log commands -log_commander = logger.getChild("LOG COMMANDER") # create a child of our logger obj +log_commander = logging.getLogger("LOG COMMANDER") # create a child of our logger obj log_commander.setLevel(logging.DEBUG) # be sure that log commands are output console_handler = logging.StreamHandler() # Create special stdout stream handler console_handler.setFormatter(logging.Formatter("%(message)s")) # no formatted log cmds log_commander.addHandler(console_handler) # Use special handler for log_commander -log_commander.propagate = False # prevent duplicate messages in the parent logger obj +log_commander.propagate = False def start_log_group(name: str) -> None: - """Begin a callapsable group of log statements. + """Begin a collapsable group of log statements. Args: - name: The name of the callapsable group + name: The name of the collapsable group """ log_commander.fatal("::group::%s", name) def end_log_group() -> None: - """End a callapsable group of log statements.""" + """End a collapsable group of log statements.""" log_commander.fatal("::endgroup::") -def is_file_in_list(paths: list, file_name: str, prompt: str) -> bool: +def is_file_in_list(paths: List[str], file_name: str, prompt: str) -> bool: """Determine if a file is specified in a list of paths and/or filenames. Args: @@ -198,14 +205,14 @@ def is_file_in_list(paths: list, file_name: str, prompt: str) -> bool: - False if `file_name` is not in the `paths` list. """ for path in paths: - result = os.path.commonpath([path, file_name]).replace(os.sep, "/") + result = os.path.commonpath( + [PurePath(path).as_posix(), PurePath(file_name).as_posix()] + ) if result == path: logger.debug( - '".%s%s" is %s as specified in the domain ".%s%s"', - os.sep, + '"./%s" is %s as specified in the domain "./%s"', file_name, prompt, - os.sep, path, ) return True @@ -219,17 +226,41 @@ def get_list_of_changed_files() -> None: files_link = f"{GITHUB_API_URL}/repos/{GITHUB_REPOSITORY}/" if GITHUB_EVENT_NAME == "pull_request": files_link += f"pulls/{Globals.EVENT_PAYLOAD['number']}/files" - elif GITHUB_EVENT_NAME == "push": - files_link += f"commits/{GITHUB_SHA}" else: - logger.warning("triggered on unsupported event.") - sys.exit(set_exit_code(0)) + if GITHUB_EVENT_NAME != "push": + logger.warning( + "Triggered on unsupported event '%s'. Behaving like a push event.", + GITHUB_EVENT_NAME, + ) + files_link += f"commits/{GITHUB_SHA}" logger.info("Fetching files list from url: %s", files_link) - Globals.FILES = requests.get(files_link, headers=API_HEADERS).json() + Globals.response_buffer = requests.get(files_link, headers=API_HEADERS) + log_response_msg() + if GITHUB_EVENT_NAME == "pull_request": + Globals.FILES = Globals.response_buffer.json() + else: + Globals.FILES = Globals.response_buffer.json()["files"] + + +def consolidate_list_to_ranges(just_numbers: List[int]) -> List[List[int]]: + """A helper function to [`filter_out_non_source_files()`] that is only used when + extracting the lines from a diff that contain additions.""" + result: List[List[int]] = [] + for i, n in enumerate(just_numbers): + if not i: + result.append([n]) + elif n - 1 != just_numbers[i - 1]: + result[-1].append(just_numbers[i - 1] + 1) + result.append([n]) + if i == len(just_numbers) - 1: + result[-1].append(n + 1) + return result def filter_out_non_source_files( - ext_list: list, ignored: list, not_ignored: list, lines_changed_only: bool + ext_list: List[str], + ignored: List[str], + not_ignored: List[str], ) -> bool: """Exclude undesired files (specified by user input 'extensions'). This filter applies to the event's [`FILES`][cpp_linter.Globals.FILES] attribute. @@ -238,52 +269,42 @@ def filter_out_non_source_files( ext_list: A list of file extensions that are to be examined. ignored: A list of paths to explicitly ignore. not_ignored: A list of paths to explicitly not ignore. - lines_changed_only: A flag that forces focus on only changes in the event's - diff info. Returns: True if there are files to check. False will invoke a early exit (in [`main()`][cpp_linter.run.main]) when no files to be checked. """ files = [] - for file in ( - Globals.FILES if GITHUB_EVENT_NAME == "pull_request" else Globals.FILES["files"] - ): + for file in Globals.FILES: if ( - os.path.splitext(file["filename"])[1][1:] in ext_list + PurePath(file["filename"]).suffix.lstrip(".") in ext_list and not file["status"].endswith("removed") and ( not is_file_in_list(ignored, file["filename"], "ignored") or is_file_in_list(not_ignored, file["filename"], "not ignored") ) ): - if lines_changed_only and "patch" in file.keys(): + if "patch" in file.keys(): # get diff details for the file's changes - line_filter = { - "name": file["filename"].replace("/", os.sep), - "lines": [], - } - file["diff_line_map"], line_numb_in_diff = ({}, 0) - # diff_line_map is a dict for which each - # - key is the line number in the file - # - value is the line's "position" in the diff - for i, line in enumerate(file["patch"].splitlines()): + # ranges is a list of start/end line numbers shown in the diff + ranges: List[List[int]] = [] + # additions is a list line numbers in the diff containing additions + additions: List[int] = [] + line_numb_in_diff: int = 0 + for line in cast(str, file["patch"]).splitlines(): + if line.startswith("+"): + additions.append(line_numb_in_diff) if line.startswith("@@ -"): - changed_hunk = line[line.find(" +") + 2 : line.find(" @@")] - changed_hunk = changed_hunk.split(",") - start_line = int(changed_hunk[0]) - hunk_length = int(changed_hunk[1]) - line_filter["lines"].append( - [start_line, hunk_length + start_line] - ) + hunk = line[line.find(" +") + 2 : line.find(" @@")].split(",") + start_line, hunk_length = [int(x) for x in hunk] + ranges.append([start_line, hunk_length + start_line]) line_numb_in_diff = start_line elif not line.startswith("-"): - file["diff_line_map"][line_numb_in_diff] = i - line_filter["lines"][-1][1] = line_numb_in_diff line_numb_in_diff += 1 - file["line_filter"] = line_filter - elif lines_changed_only: - continue + file["line_filter"] = dict( + diff_chunks=ranges, + lines_added=consolidate_list_to_ranges(additions), + ) files.append(file) if files: @@ -291,14 +312,13 @@ def filter_out_non_source_files( "Giving attention to the following files:\n\t%s", "\n\t".join([f["filename"] for f in files]), ) - if GITHUB_EVENT_NAME == "pull_request": - Globals.FILES = files - else: - Globals.FILES["files"] = files - if not os.getenv("CI"): # if not executed on a github runner - with open(".changed_files.json", "w", encoding="utf-8") as temp: - # dump altered json of changed files - json.dump(Globals.FILES, temp, indent=2) + Globals.FILES = files + if not IS_ON_RUNNER: # if not executed on a github runner + # dump altered json of changed files + Path(".changed_files.json").write_text( + json.dumps(Globals.FILES, indent=2), + encoding="utf-8", + ) else: logger.info("No source files need checking!") return False @@ -313,19 +333,20 @@ def verify_files_are_present() -> None: repository. If files are not found, then they are downloaded to the working directory. This is bad for files with the same name from different folders. """ - for file in ( - Globals.FILES if GITHUB_EVENT_NAME == "pull_request" else Globals.FILES["files"] - ): - file_name = file["filename"].replace("/", os.sep) - if not os.path.exists(file_name): + for file in Globals.FILES: + file_name = Path(file["filename"]) + if not file_name.exists(): logger.warning("Could not find %s! Did you checkout the repo?", file_name) logger.info("Downloading file from url: %s", file["raw_url"]) Globals.response_buffer = requests.get(file["raw_url"]) - with open(os.path.split(file_name)[1], "w", encoding="utf-8") as temp: - temp.write(Globals.response_buffer.text) + # retain the repo's original structure + Path.mkdir(file_name.parent, parents=True, exist_ok=True) + file_name.write_text(Globals.response_buffer.text, encoding="utf-8") -def list_source_files(ext_list: list, ignored_paths: list, not_ignored: list) -> bool: +def list_source_files( + ext_list: List[str], ignored_paths: List[str], not_ignored: List[str] +) -> bool: """Make a list of source files to be checked. The resulting list is stored in [`FILES`][cpp_linter.Globals.FILES]. @@ -339,36 +360,20 @@ def list_source_files(ext_list: list, ignored_paths: list, not_ignored: list) -> [`main()`][cpp_linter.run.main]) when no files to be checked. """ start_log_group("Get list of specified source files") - if os.path.exists(".gitmodules"): - submodules = configparser.ConfigParser() - submodules.read(".gitmodules") - for module in submodules.sections(): - logger.info( - "Appending submodule to ignored paths: %s", submodules[module]["path"] - ) - ignored_paths.append(submodules[module]["path"]) - - root_path = os.getcwd() - for dirpath, _, filenames in os.walk(root_path): - path = dirpath.replace(root_path, "").lstrip(os.sep) - path_parts = path.split(os.sep) - is_hidden = False - for part in path_parts: - if part.startswith("."): - # logger.debug("Skipping \".%s%s\"", os.sep, path) - is_hidden = True - break - if is_hidden: - continue # skip sources in hidden directories - logger.debug('Crawling ".%s%s"', os.sep, path) - for file in filenames: - if os.path.splitext(file)[1][1:] in ext_list: - file_path = os.path.join(path, file) - logger.debug('".%s%s" is a source code file', os.sep, file_path) + + root_path = Path(".") + for ext in ext_list: + for rel_path in root_path.rglob(f"*.{ext}"): + for parent in rel_path.parts[:-1]: + if parent.startswith("."): + break + else: + file_path = rel_path.as_posix() + logger.debug('"./%s" is a source code file', file_path) if not is_file_in_list( ignored_paths, file_path, "ignored" ) or is_file_in_list(not_ignored, file_path, "not ignored"): - Globals.FILES.append({"filename": file_path}) + Globals.FILES.append(dict(filename=file_path)) if Globals.FILES: logger.info( @@ -383,10 +388,10 @@ def list_source_files(ext_list: list, ignored_paths: list, not_ignored: list) -> def run_clang_tidy( filename: str, - file_obj: dict, + file_obj: Dict[str, Any], version: str, checks: str, - lines_changed_only: bool, + lines_changed_only: int, database: str, repo_root: str, ) -> None: @@ -403,51 +408,46 @@ def run_clang_tidy( """ if checks == "-*": # if all checks are disabled, then clang-tidy is skipped # clear the clang-tidy output file and exit function - with open("clang_tidy_report.txt", "wb") as f_out: - return + Path("clang_tidy_report.txt").write_bytes(b"") + return + filename = PurePath(filename).as_posix() cmds = [ - "clang-tidy" + ("" if not version else f"-{version}"), + assemble_version_exec("clang-tidy", version), "--export-fixes=clang_tidy_output.yml", ] - if IS_ON_WINDOWS: - cmds[0] = "clang-tidy" - if os.path.exists(version + "\\bin"): - cmds[0] = f"{version}\\bin\\clang-tidy.exe" - elif not version.isdigit(): - logger.warning("ignoring invalid version number %s.", version) - cmds[0] = "clang-tidy" if checks: cmds.append(f"-checks={checks}") if database: cmds.append("-p") - if RUNNER_WORKSPACE: - path_to_db = RUNNER_WORKSPACE - if repo_root and repo_root != ".": - path_to_db += os.sep + repo_root - cmds.append(os.path.join(path_to_db, database)) - else: - cmds.append(database) + if not PurePath(database).is_absolute(): + database = str(Path(RUNNER_WORKSPACE, repo_root, database).resolve()) + cmds.append(database) if lines_changed_only: - logger.info("line_filter = %s", json.dumps(file_obj["line_filter"]["lines"])) - cmds.append(f"--line-filter={json.dumps([file_obj['line_filter']])}") - cmds.append(filename.replace("/", os.sep)) - with open("clang_tidy_output.yml", "wb"): - pass # clear yml file's content before running clang-tidy + ranges = "diff_chunks" if lines_changed_only == 1 else "lines_added" + line_ranges = dict(name=filename, lines=file_obj["line_filter"][ranges]) + logger.info("line_filter = %s", json.dumps([line_ranges])) + cmds.append(f"--line-filter={json.dumps([line_ranges])}") + cmds.append(filename) + # clear yml file's content before running clang-tidy + Path("clang_tidy_output.yml").write_bytes(b"") logger.info('Running "%s"', " ".join(cmds)) results = subprocess.run(cmds, capture_output=True) - with open("clang_tidy_report.txt", "wb") as f_out: - f_out.write(results.stdout) + Path("clang_tidy_report.txt").write_bytes(results.stdout) logger.debug("Output from clang-tidy:\n%s", results.stdout.decode()) - if os.path.getsize("clang_tidy_output.yml"): + if Path("clang_tidy_output.yml").stat().st_size: parse_tidy_suggestions_yml() # get clang-tidy fixes from yml - if results.returncode: - logger.warning( - "%s raised the following error(s):\n%s", cmds[0], results.stderr.decode() + if results.stderr: + logger.debug( + "clang-tidy made the following summary:\n%s", results.stderr.decode() ) def run_clang_format( - filename: str, file_obj: dict, version: str, style: str, lines_changed_only: bool + filename: str, + file_obj: Dict[str, Any], + version: str, + style: str, + lines_changed_only: int, ) -> None: """Run clang-format on a certain file @@ -460,37 +460,85 @@ def run_clang_format( lines_changed_only: A flag that forces focus on only changes in the event's diff info. """ + if not style: # if `style` == "" + Path("clang_format_output.xml").write_bytes(b"") + return # clear any previous output and exit cmds = [ - "clang-format" + ("" if not version else f"-{version}"), + assemble_version_exec("clang-format", version), f"-style={style}", "--output-replacements-xml", ] - if IS_ON_WINDOWS: - cmds[0] = "clang-format" - if os.path.exists(version + "\\bin"): - cmds[0] = f"{version}\\bin\\clang-format.exe" - elif not version.isdigit(): - logger.warning("ignoring invalid version number %s.", version) - cmds[0] = "clang-format" if lines_changed_only: - for line_range in file_obj["line_filter"]["lines"]: + ranges = "diff_chunks" if lines_changed_only == 1 else "lines_added" + for line_range in file_obj["line_filter"][ranges]: cmds.append(f"--lines={line_range[0]}:{line_range[1]}") - cmds.append(filename.replace("/", os.sep)) + cmds.append(PurePath(filename).as_posix()) logger.info('Running "%s"', " ".join(cmds)) results = subprocess.run(cmds, capture_output=True) - with open("clang_format_output.xml", "wb") as f_out: - f_out.write(results.stdout) + Path("clang_format_output.xml").write_bytes(results.stdout) if results.returncode: - logger.warning( + logger.debug( "%s raised the following error(s):\n%s", cmds[0], results.stderr.decode() ) +def create_comment_body( + filename: str, + file_obj: Dict[str, Any], + lines_changed_only: int, + tidy_notes: List[TidyNotification], +): + """Create the content for a thread comment about a certain file. + This is a helper function to [`capture_clang_tools_output()`]. + + Args: + filename: The file's name (& path). + file_obj: The file's JSON `dict`. + lines_changed_only: A flag used to filter the comment based on line changes. + tidy_notes: A list of cached notifications from clang-tidy. This is used to + avoid duplicated content in comment, and it is later used again by + [`make_annotations()`] after [`capture_clang_tools_output()`] us finished. + """ + ranges = range_of_changed_lines(file_obj, lines_changed_only) + if Path("clang_tidy_report.txt").stat().st_size: + parse_tidy_output() # get clang-tidy fixes from stdout + comment_output = "" + if Globals.PAYLOAD_TIDY: + Globals.PAYLOAD_TIDY += "
" + for fix in GlobalParser.tidy_notes: + if lines_changed_only and fix.line not in ranges: + continue + comment_output += repr(fix) + tidy_notes.append(fix) + if comment_output: + Globals.PAYLOAD_TIDY += f"
{filename}
\n" + Globals.PAYLOAD_TIDY += comment_output + GlobalParser.tidy_notes.clear() # empty list to avoid duplicated output + + if Path("clang_format_output.xml").stat().st_size: + parse_format_replacements_xml(PurePath(filename).as_posix()) + if GlobalParser.format_advice and GlobalParser.format_advice[-1].replaced_lines: + should_comment = lines_changed_only == 0 + if not should_comment: + for line in [ + replacement.line + for replacement in GlobalParser.format_advice[-1].replaced_lines + ]: + if line in ranges: + should_comment = True + break + if should_comment: + if not Globals.OUTPUT: + Globals.OUTPUT = "\n## :scroll: " + Globals.OUTPUT += "Run `clang-format` on the following files\n" + Globals.OUTPUT += f"- [ ] {file_obj['filename']}\n" + + def capture_clang_tools_output( version: str, checks: str, style: str, - lines_changed_only: bool, + lines_changed_only: int, database: str, repo_root: str, ): @@ -506,42 +554,18 @@ def capture_clang_tools_output( lines_changed_only: A flag that forces focus on only changes in the event's diff info. """ - tidy_notes = [] # temporary cache of parsed notifications for use in log commands - for file in ( - Globals.FILES - if GITHUB_EVENT_NAME == "pull_request" or isinstance(Globals.FILES, list) - else Globals.FILES["files"] - ): - filename = file["filename"] - if not os.path.exists(file["filename"]): - filename = os.path.split(file["raw_url"])[1] + # temporary cache of parsed notifications for use in log commands + tidy_notes: List[TidyNotification] = [] + for file in Globals.FILES: + filename = cast(str, file["filename"]) start_log_group(f"Performing checkup on {filename}") run_clang_tidy( filename, file, version, checks, lines_changed_only, database, repo_root ) run_clang_format(filename, file, version, style, lines_changed_only) end_log_group() - if os.path.getsize("clang_tidy_report.txt"): - parse_tidy_output() # get clang-tidy fixes from stdout - if Globals.PAYLOAD_TIDY: - Globals.PAYLOAD_TIDY += "
" - Globals.PAYLOAD_TIDY += f"
{filename}
\n" - for fix in GlobalParser.tidy_notes: - Globals.PAYLOAD_TIDY += repr(fix) - for note in GlobalParser.tidy_notes: - tidy_notes.append(note) - GlobalParser.tidy_notes.clear() # empty list to avoid duplicated output - - if os.path.getsize("clang_format_output.xml"): - parse_format_replacements_xml(filename.replace("/", os.sep)) - if ( - GlobalParser.format_advice - and GlobalParser.format_advice[-1].replaced_lines - ): - if not Globals.OUTPUT: - Globals.OUTPUT = "\n## :scroll: " - Globals.OUTPUT += "Run `clang-format` on the following files\n" - Globals.OUTPUT += f"- [ ] {file['filename']}\n" + + create_comment_body(filename, file, lines_changed_only, tidy_notes) if Globals.PAYLOAD_TIDY: if not Globals.OUTPUT: @@ -592,7 +616,7 @@ def post_diff_comments(base_url: str, user_id: int) -> bool: output value (a soft exit code). """ comments_url = base_url + "pulls/comments/" # for use with comment_id - payload = list_diff_comments() + payload = list_diff_comments(2) # only focus on additions in diff logger.info("Posting %d comments", len(payload)) # uncomment the next 3 lines for debug output without posting a comment @@ -707,7 +731,9 @@ def post_results(use_diff_comments: bool, user_id: int = 41898282): set_exit_code(1 if checks_passed else 0) -def make_annotations(style: str, file_annotations: bool) -> bool: +def make_annotations( + style: str, file_annotations: bool, lines_changed_only: int +) -> bool: """Use github log commands to make annotations from clang-format and clang-tidy output. @@ -715,48 +741,74 @@ def make_annotations(style: str, file_annotations: bool) -> bool: style: The chosen code style guidelines. The value 'file' is replaced with 'custom style'. """ - # log_commander obj's verbosity is hard-coded to show debug statements - ret_val = False count = 0 - for note in GlobalParser.format_advice: - if note.replaced_lines: - ret_val = True + files = ( + Globals.FILES + if GITHUB_EVENT_NAME == "pull_request" or isinstance(Globals.FILES, list) + else cast(Dict[str, Any], Globals.FILES)["files"] + ) + for advice, file in zip(GlobalParser.format_advice, files): + line_filter = range_of_changed_lines(file, lines_changed_only) + if advice.replaced_lines: if file_annotations: - log_commander.info(note.log_command(style)) - count += 1 + output = advice.log_command(style, line_filter) + if output is not None: + log_commander.info(output) + count += 1 for note in GlobalParser.tidy_notes: - ret_val = True - if file_annotations: + if lines_changed_only: + filename = note.filename.replace("\\", "/") + line_filter = [] + for file in files: + if filename == file["filename"]: + line_filter = range_of_changed_lines(file, lines_changed_only) + break + else: + continue + if note.line in line_filter: + count += 1 + log_commander.info(note.log_command()) + else: + count += 1 log_commander.info(note.log_command()) - count += 1 logger.info("Created %d annotations", count) - return ret_val + return bool(count) -def parse_ignore_option(paths: str) -> tuple: +def parse_ignore_option(paths: str) -> Tuple[List[str], List[str]]: """Parse a given string of paths (separated by a '|') into `ignored` and `not_ignored` lists of strings. Args: paths: This argument conforms to the CLI arg `--ignore` (or `-i`). - Returns: - A tuple of lists in which each list is a set of strings. - - index 0 is the `ignored` list - - index 1 is the `not_ignored` list + Returns: Returns a tuple of lists in which each list is a set of strings. + index 0 is the `ignored` list + index 1 is the `not_ignored` list """ ignored, not_ignored = ([], []) - paths = paths.split("|") - for path in paths: + + for path in paths.split("|"): is_included = path.startswith("!") if path.startswith("!./" if is_included else "./"): path = path.replace("./", "", 1) # relative dir is assumed path = path.strip() # strip leading/trailing spaces if is_included: - not_ignored.append(path[1:]) + not_ignored.append(path[1:]) # strip leading `!` else: ignored.append(path) + # auto detect submodules + gitmodules = Path(".gitmodules") + if gitmodules.exists(): + submodules = configparser.ConfigParser() + submodules.read(gitmodules.resolve().as_posix()) + for module in submodules.sections(): + path = submodules[module]["path"] + if path not in not_ignored: + logger.info("Appending submodule to ignored paths: %s", path) + ignored.append(path) + if ignored: logger.info( "Ignoring the following paths/files:\n\t./%s", @@ -776,23 +828,26 @@ def main(): # The parsed CLI args args = cli_arg_parser.parse_args() + # force files-changed-only to reflect value of lines-changed-only + if args.lines_changed_only: + args.files_changed_only = True + # set logging verbosity logger.setLevel(int(args.verbosity)) # prepare ignored paths list - ignored, not_ignored = parse_ignore_option("" if not args.ignore else args.ignore) - - # prepare extensions list - args.extensions = args.extensions.split(",") + ignored, not_ignored = parse_ignore_option(args.ignore) logger.info("processing %s event", GITHUB_EVENT_NAME) # change working directory os.chdir(args.repo_root) - # load event's json info about the workflow run - with open(GITHUB_EVENT_PATH, "r", encoding="utf-8") as payload: - Globals.EVENT_PAYLOAD = json.load(payload) + if GITHUB_EVENT_PATH: + # load event's json info about the workflow run + Globals.EVENT_PAYLOAD = json.loads( + Path(GITHUB_EVENT_PATH).read_text(encoding="utf-8") + ) if logger.getEffectiveLevel() <= logging.DEBUG: start_log_group("Event json from the runner") logger.debug(json.dumps(Globals.EVENT_PAYLOAD)) @@ -805,7 +860,6 @@ def main(): args.extensions, ignored, not_ignored, - args.lines_changed_only if args.files_changed_only else False, ) if not exit_early: verify_files_are_present() @@ -826,13 +880,17 @@ def main(): start_log_group("Posting comment(s)") thread_comments_allowed = True - if "private" in Globals.EVENT_PAYLOAD["repository"]: + if GITHUB_EVENT_PATH and "private" in Globals.EVENT_PAYLOAD["repository"]: thread_comments_allowed = ( Globals.EVENT_PAYLOAD["repository"]["private"] is not True ) if args.thread_comments and thread_comments_allowed: post_results(False) # False is hard-coded to disable diff comments. - set_exit_code(int(make_annotations(args.style, args.file_annotations))) + set_exit_code( + int( + make_annotations(args.style, args.file_annotations, args.lines_changed_only) + ) + ) end_log_group() diff --git a/cpp_linter/thread_comments.py b/cpp_linter/thread_comments.py index 4313d60a..915eb776 100644 --- a/cpp_linter/thread_comments.py +++ b/cpp_linter/thread_comments.py @@ -1,9 +1,17 @@ """A module to house the various functions for traversing/adjusting comments""" -import os -from typing import Union +from typing import Union, cast, List, Optional, Dict, Any import json +from pathlib import Path import requests -from . import Globals, GlobalParser, logger, API_HEADERS, GITHUB_SHA, log_response_msg +from . import ( + Globals, + GlobalParser, + logger, + API_HEADERS, + GITHUB_SHA, + log_response_msg, + range_of_changed_lines, +) def remove_bot_comments(comments_url: str, user_id: int): @@ -48,30 +56,29 @@ def remove_bot_comments(comments_url: str, user_id: int): json.dump(comments, json_comments, indent=4) -def aggregate_tidy_advice() -> list: +def aggregate_tidy_advice(lines_changed_only: int) -> List[Dict[str, Any]]: """Aggregate a list of json contents representing advice from clang-tidy suggestions.""" results = [] - for index, fixit in enumerate(GlobalParser.tidy_advice): + for fixit, file in zip(GlobalParser.tidy_advice, Globals.FILES): for diag in fixit.diagnostics: + ranges = range_of_changed_lines(file, lines_changed_only) + if lines_changed_only and diag.line not in ranges: + continue + # base body of comment body = "\n## :speech_balloon: Clang-tidy\n**" body += diag.name + "**\n>" + diag.message # get original code - filename = Globals.FILES[index]["filename"].replace("/", os.sep) - if not os.path.exists(filename): - # the file had to be downloaded (no git checkout). - # thus use only the filename (without the path to the file) - filename = os.path.split(filename)[1] - lines = [] # the list of lines in a file - with open(filename, encoding="utf-8") as temp: - lines = temp.readlines() + filename = Path(cast(str, file["filename"])) + # the list of lines in a file + lines = filename.read_text(encoding="utf-8").splitlines() # aggregate clang-tidy advice suggestion = "\n```suggestion\n" is_multiline_fix = False - fix_lines = [] # a list of line numbers for the suggested fixes + fix_lines: List[int] = [] # a list of line numbers for the suggested fixes line = "" # the line that concerns the fix/comment for i, tidy_fix in enumerate(diag.replacements): line = lines[tidy_fix.line - 1] @@ -95,43 +102,34 @@ def aggregate_tidy_advice() -> list: body += suggestion results.append( - { - "body": body, - "commit_id": GITHUB_SHA, - "line": diag.line, - "path": fixit.filename, - "side": "RIGHT", - } + dict( + body=body, + commit_id=GITHUB_SHA, + line=diag.line, + path=fixit.filename, + side="RIGHT", + ) ) return results -def aggregate_format_advice() -> list: +def aggregate_format_advice(lines_changed_only: int) -> List[Dict[str, Any]]: """Aggregate a list of json contents representing advice from clang-format suggestions.""" results = [] - for index, fmt_advice in enumerate(GlobalParser.format_advice): + for fmt_advice, file in zip(GlobalParser.format_advice, Globals.FILES): # get original code - filename = Globals.FILES[index]["filename"].replace("/", os.sep) - if not os.path.exists(filename): - # the file had to be downloaded (no git checkout). - # thus use only the filename (without the path to the file) - filename = os.path.split(filename)[1] - lines = [] # the list of lines from the src file - with open(filename, encoding="utf-8") as temp: - lines = temp.readlines() + filename = Path(file["filename"]) + # the list of lines from the src file + lines = filename.read_text(encoding="utf-8").splitlines() # aggregate clang-format suggestion line = "" # the line that concerns the fix for fixed_line in fmt_advice.replaced_lines: # clang-format can include advice that starts/ends outside the diff's domain - in_range = False - ranges = Globals.FILES[index]["line_filter"]["lines"] - for scope in ranges: - if fixed_line.line in range(scope[0], scope[1] + 1): - in_range = True - if not in_range: + ranges = range_of_changed_lines(file, lines_changed_only) + if lines_changed_only and fixed_line.line not in ranges: continue # line is out of scope for diff, so skip this fix # assemble the suggestion @@ -157,18 +155,20 @@ def aggregate_format_advice() -> list: # create a suggestion from clang-format advice results.append( - { - "body": body, - "commit_id": GITHUB_SHA, - "line": fixed_line.line, - "path": fmt_advice.filename, - "side": "RIGHT", - } + dict( + body=body, + commit_id=GITHUB_SHA, + line=fixed_line.line, + path=fmt_advice.filename, + side="RIGHT", + ) ) return results -def concatenate_comments(tidy_advice: list, format_advice: list) -> list: +def concatenate_comments( + tidy_advice: list, format_advice: list +) -> List[Dict[str, Union[str, int]]]: """Concatenate comments made to the same line of the same file.""" # traverse comments from clang-format for index, comment_body in enumerate(format_advice): @@ -188,20 +188,20 @@ def concatenate_comments(tidy_advice: list, format_advice: list) -> list: return tidy_advice + format_advice -def list_diff_comments() -> list: +def list_diff_comments(lines_changed_only: int) -> List[Dict[str, Union[str, int]]]: """Aggregate list of comments for use in the event's diff. This function assumes - that the CLI option `--diff-only` is set to True. + that the CLI option `--lines_changed_only` is set to True. Returns: A list of comments (each element as json content). """ - tidy_advice = aggregate_tidy_advice() - format_advice = aggregate_format_advice() - results = concatenate_comments(tidy_advice, format_advice) - return results + return concatenate_comments( + aggregate_tidy_advice(lines_changed_only), + aggregate_format_advice(lines_changed_only), + ) -def get_review_id(reviews_url: str, user_id: int) -> int: +def get_review_id(reviews_url: str, user_id: int) -> Optional[int]: """Dismiss all stale reviews (only the ones made by our bot). Args: @@ -238,7 +238,7 @@ def get_review_id(reviews_url: str, user_id: int) -> int: return review_id -def find_review(reviews: dict, user_id: int) -> Union[int, None]: +def find_review(reviews: dict, user_id: int) -> Optional[int]: """Find a review created by a certain user ID. Args: diff --git a/demo/demo.hpp b/demo/demo.hpp index 40db149d..f93d0122 100644 --- a/demo/demo.hpp +++ b/demo/demo.hpp @@ -8,7 +8,7 @@ class Dummy { Dummy() :numb(0), useless("\0"){} public: - void *not_usefull(char *str){useless = str;} + void *not_useful(char *str){useless = str;} }; diff --git a/docs/requirements.txt b/docs/requirements.txt index 2c86af4b..36951be8 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ mkdocs -mkdocstrings[python] -mkdocs-material mkdocs-autorefs mkdocs-include-markdown-plugin +mkdocs-material +mkdocstrings[python] diff --git a/mkdocs.yml b/mkdocs.yml index 7d3246c1..2c970a5c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -48,6 +48,7 @@ plugins: rendering: # show_if_no_docstring: true show_source: true + show_signature_annotations: true heading_level: 2 watch: - cpp_linter diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..c06ca881 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,616 @@ +[build-system] +requires = ["setuptools>=45", "setuptools-scm"] +build-backend = "setuptools.build_meta" + +[project] +name = "cpp-linter" +description = "Run clang-format and clang-tidy on a batch of files." +readme = "README.md" +keywords = ["clang", "clang-tools", "linter", "clang-tidy", "clang-format"] +license = {text = "MIT License"} +authors = [ + { name = "Brendan Doherty", email = "2bndy5@gmail.com" }, + { name = "Peter Shen", email = "xianpeng.shen@gmail.com" }, +] +dependencies = [ + "requests", + "pyyaml", +] +classifiers = [ + # https://pypi.org/pypi?%3Aaction=list_classifiers + "Development Status :: 5 - Production/Stable", + "License :: OSI Approved :: MIT License", + "Intended Audience :: Developers", + "Intended Audience :: System Administrators", + "Intended Audience :: Information Technology", + "Natural Language :: English", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX :: Linux", + "Operating System :: MacOS", + "Programming Language :: Python :: 3", + "Topic :: Software Development :: Build Tools", +] +dynamic = ["version"] + +[project.scripts] +cpp-linter = "cpp_linter.run:main" + +[project.urls] +source = "https://github.com/cpp-linter/cpp-linter-action" +tracker = "https://github.com/cpp-linter/cpp-linter-action/issues" + +# ... other project metadata fields as specified in: +# https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ + +[tool.setuptools] +zip-safe = false +packages = ["cpp_linter"] + +[tool.setuptools_scm] +# It would be nice to include the commit hash in the version, but that +# can't be done in a PEP 440-compatible way. +version_scheme= "no-guess-dev" +# Test PyPI does not support local versions. +local_scheme = "no-local-version" +fallback_version = "0.0.0" + +[tool.mypy] +show_error_codes = true +show_column_numbers = true + +[tool.pytest.ini_options] +minversion = "6.0" +addopts = "-vv" +testpaths = ["tests"] + +[tool.coverage] +[tool.coverage.run] +dynamic_context = "test_function" +omit = [ + # don't include tests in coverage + "tests/*", +] + +[tool.coverage.json] +pretty_print = true + +[tool.coverage.html] +show_contexts = true + +[tool.coverage.report] +# Regexes for lines to exclude from consideration +exclude_lines = [ + # Have to re-enable the standard pragma + "pragma: no cover", + # Don\'t complain about missing debug-only code: + "def __repr__", + # the point of unit tests is to test parts of main() + "def main", + # ignore any branch that makes the module executable + 'if __name__ == "__main__"', + # ignore branches specific to type checking + "if TYPE_CHECKING", + # ignore the local secific debug statement related to not having rich installed + "if not FOUND_RICH_LIB", +] + +[tool.pylint.main] +# Analyse import fallback blocks. This can be used to support both Python 2 and 3 +# compatible code, which means that the block might have code that exists only in +# one or another interpreter, leading to false positives when analysed. +# analyse-fallback-blocks = + +# Always return a 0 (non-error) status code, even if lint errors are found. This +# is primarily useful in continuous integration scripts. +# exit-zero = + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +# extension-pkg-allow-list = + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +# extension-pkg-whitelist = + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +# fail-on = + +# Specify a score threshold to be exceeded before program exits with error. +fail-under = 10 + +# Interpret the stdin as a python script, whose filename needs to be passed as +# the module_or_package argument. +# from-stdin = + +# Files or directories to be skipped. They should be base names, not paths. +ignore = ["CVS"] + +# Add files or directories matching the regex patterns to the ignore-list. The +# regex matches against paths and can be in Posix or Windows format. +# ignore-paths = + +# Files or directories matching the regex patterns are skipped. The regex matches +# against base names, not paths. The default value ignores Emacs file locks +# ignore-patterns = + +# List of module names for which member attributes should not be checked (useful +# for modules/projects where namespaces are manipulated during runtime and thus +# existing member attributes cannot be deduced by static analysis). It supports +# qualified module names, as well as Unix pattern matching. +# ignored-modules = + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +# init-hook = + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs = 2 + +# Control the amount of potential inferred values when inferring a single object. +# This can help the performance when dealing with large functions or complex, +# nested conditions. +limit-inference-results = 100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +# load-plugins = + +# Pickle collected data for later comparisons. +persistent = true + +# Minimum Python version to use for version dependent checks. Will default to the +# version used to run pylint. +py-version = "3.10" + +# Discover python modules and packages in the file system subtree. +# recursive = + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode = true + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +# unsafe-load-any-extension = + +[tool.pylint.basic] +# Naming style matching correct argument names. +argument-naming-style = "snake_case" + +# Regular expression matching correct argument names. Overrides argument-naming- +# style. If left empty, argument names will be checked with the set naming style. +argument-rgx = "(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$" + +# Naming style matching correct attribute names. +attr-naming-style = "snake_case" + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. If left empty, attribute names will be checked with the set naming +# style. +attr-rgx = "(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$" + +# Bad variable names which should always be refused, separated by a comma. +bad-names = ["foo", "bar", "baz", "toto", "tutu", "tata"] + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +# bad-names-rgxs = + +# Naming style matching correct class attribute names. +class-attribute-naming-style = "any" + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. If left empty, class attribute names will be checked +# with the set naming style. +class-attribute-rgx = "([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$" + +# Naming style matching correct class constant names. +class-const-naming-style = "UPPER_CASE" + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. If left empty, class constant names will be checked with +# the set naming style. +# class-const-rgx = + +# Naming style matching correct class names. +class-naming-style = "PascalCase" + +# Regular expression matching correct class names. Overrides class-naming-style. +# If left empty, class names will be checked with the set naming style. +class-rgx = "[A-Z_][a-zA-Z0-9_]+$" + +# Naming style matching correct constant names. +const-naming-style = "UPPER_CASE" + +# Regular expression matching correct constant names. Overrides const-naming- +# style. If left empty, constant names will be checked with the set naming style. +const-rgx = "(([A-Z_][A-Z0-9_]*)|(__.*__))$" + +# Minimum line length for functions/classes that require docstrings, shorter ones +# are exempt. +docstring-min-length = -1 + +# Naming style matching correct function names. +function-naming-style = "snake_case" + +# Regular expression matching correct function names. Overrides function-naming- +# style. If left empty, function names will be checked with the set naming style. +function-rgx = "(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$" + +# Good variable names which should always be accepted, separated by a comma. +good-names = ["r", "g", "b", "w", "i", "j", "k", "n", "x", "y", "z", "ex", "ok", "Run", "_"] + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +# good-names-rgxs = + +# Include a hint for the correct naming format with invalid-name. +# include-naming-hint = + +# Naming style matching correct inline iteration names. +inlinevar-naming-style = "any" + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. If left empty, inline iteration names will be checked +# with the set naming style. +inlinevar-rgx = "[A-Za-z_][A-Za-z0-9_]*$" + +# Naming style matching correct method names. +method-naming-style = "snake_case" + +# Regular expression matching correct method names. Overrides method-naming- +# style. If left empty, method names will be checked with the set naming style. +method-rgx = "(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$" + +# Naming style matching correct module names. +module-naming-style = "snake_case" + +# Regular expression matching correct module names. Overrides module-naming- +# style. If left empty, module names will be checked with the set naming style. +module-rgx = "(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$" + +# Colon-delimited sets of names that determine each other's naming style when the +# name regexes allow several styles. +# name-group = + +# Regular expression which should only match function or class names that do not +# require a docstring. +no-docstring-rgx = "^_" + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. These +# decorators are taken in consideration only for invalid-name. +property-classes = ["abc.abstractproperty"] + +# Regular expression matching correct type variable names. If left empty, type +# variable names will be checked with the set naming style. +# typevar-rgx = + +# Naming style matching correct variable names. +variable-naming-style = "snake_case" + +# Regular expression matching correct variable names. Overrides variable-naming- +# style. If left empty, variable names will be checked with the set naming style. +variable-rgx = "(([a-z][a-z0-9_]{2,30})|(_[a-z0-9_]*))$" + +[tool.pylint.classes] +# Warn about protected attribute access inside special methods +# check-protected-access-in-special-methods = + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods = ["__init__", "__new__", "setUp"] + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected = ["_asdict", "_fields", "_replace", "_source", "_make"] + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg = ["cls"] + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg = ["mcs"] + +[tool.pylint.design] +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +# exclude-too-few-public-methods = + +# List of qualified class names to ignore when counting class parents (see R0901) +# ignored-parents = + +# Maximum number of arguments for function / method. +max-args = 8 + +# Maximum number of attributes for a class (see R0902). +max-attributes = 11 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr = 5 + +# Maximum number of branch for function / method body. +max-branches = 12 + +# Maximum number of locals for function / method body. +max-locals = 18 + +# Maximum number of parents for a class (see R0901). +max-parents = 7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods = 20 + +# Maximum number of return / yield for function / method body. +max-returns = 6 + +# Maximum number of statements in function / method body. +max-statements = 50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods = 1 + +[tool.pylint.exceptions] +# Exceptions that will emit a warning when caught. +overgeneral-exceptions = ["Exception"] + +[tool.pylint.format] +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format = "LF" + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines = "^\\s*(# )??$" + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren = 4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string = " " + +# Maximum number of characters on a single line. +max-line-length = 88 + +# Maximum number of lines in a module. +max-module-lines = 1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +# single-line-class-stmt = + +# Allow the body of an if to be on the same line as the test if there is no else. +# single-line-if-stmt = + +[tool.pylint.imports] +# List of modules that can be imported at any level, not just the top level one. +# allow-any-import-level = + +# Allow wildcard imports from modules that define __all__. +# allow-wildcard-with-all = + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules = ["optparse", "tkinter.tix"] + +# Output a graph (.gv or any supported image format) of external dependencies to +# the given file (report RP0402 must not be disabled). +# ext-import-graph = + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be disabled). +# import-graph = + +# Output a graph (.gv or any supported image format) of internal dependencies to +# the given file (report RP0402 must not be disabled). +# int-import-graph = + +# Force import order to recognize a module as part of the standard compatibility +# libraries. +# known-standard-library = + +# Force import order to recognize a module as part of a third party library. +known-third-party = ["enchant"] + +# Couples of modules and preferred modules, separated by a comma. +# preferred-modules = + +[tool.pylint.logging] +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style = "old" + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules = ["logging"] + +[tool.pylint."messages control"] +# Only show warnings with the listed confidence levels. Leave empty to show all. +# Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence = ["HIGH", "CONTROL_FLOW", "INFERENCE", "INFERENCE_FAILURE", "UNDEFINED"] + +# Disable the message, report, category or checker with the given id(s). You can +# either give multiple identifiers separated by comma (,) or put this option +# multiple times (only on the command line, not in the configuration file where +# it should appear only once). You can also use "--disable=all" to disable +# everything first and then re-enable specific checks. For example, if you want +# to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable = ["raw-checker-failed", "bad-inline-option", "locally-disabled", "file-ignored", "suppressed-message", "useless-suppression", "deprecated-pragma", "use-symbolic-message-instead", "invalid-sequence-index", "anomalous-backslash-in-string", "too-few-public-methods", "consider-using-f-string", "subprocess-run-check"] + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where it +# should appear only once). See also the "--disable" option for examples. +enable = ["c-extension-no-member"] + +[tool.pylint.miscellaneous] +# List of note tags to take in consideration, separated by a comma. +notes = ["FIXME", "XXX"] + +# Regular expression of note tags to take in consideration. +# notes-rgx = + +[tool.pylint.refactoring] +# Maximum number of nested blocks for function / method body +max-nested-blocks = 5 + +# Complete name of functions that never returns. When checking for inconsistent- +# return-statements if a never returning function is called then it will be +# considered as an explicit return statement and no message will be printed. +never-returning-functions = ["sys.exit", "argparse.parse_error"] + +[tool.pylint.reports] +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'fatal', 'error', 'warning', 'refactor', +# 'convention', and 'info' which contain the number of messages in each category, +# as well as 'statement' which is the total number of statements analyzed. This +# score is used by the global evaluation report (RP0004). +evaluation = "10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)" + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +# msg-template = + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +# output-format = + +# Tells whether to display a full report or only the messages. +# reports = + +# Activate the evaluation score. +score = true + +[tool.pylint.similarities] +# Comments are removed from the similarity computation +ignore-comments = true + +# Docstrings are removed from the similarity computation +ignore-docstrings = true + +# Imports are removed from the similarity computation +# ignore-imports = + +# Signatures are removed from the similarity computation +ignore-signatures = true + +# Minimum lines number of a similarity. +min-similarity-lines = 4 + +[tool.pylint.spelling] +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions = 4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the 'python-enchant' package. +# spelling-dict = + +# List of comma separated words that should be considered directives if they +# appear at the beginning of a comment and should not be checked. +spelling-ignore-comment-directives = "fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:" + +# List of comma separated words that should not be checked. +# spelling-ignore-words = + +# A path to a file that contains the private dictionary; one word per line. +# spelling-private-dict-file = + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +# spelling-store-unknown-words = + +[tool.pylint.string] +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +# check-quote-consistency = + +# This flag controls whether the implicit-str-concat should generate a warning on +# implicit string concatenation in sequences defined over several lines. +# check-str-concat-over-line-jumps = + +[tool.pylint.typecheck] +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators = ["contextlib.contextmanager"] + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +# generated-members = + +# Tells whether missing members accessed in mixin class should be ignored. A +# class is considered mixin if its name matches the mixin-class-rgx option. +# Tells whether to warn about missing members when the owner of the attribute is +# inferred to be None. +ignore-none = true + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference can +# return multiple potential results while evaluating a Python object, but some +# branches might not be evaluated, which results in partial inference. In that +# case, it might be useful to still emit no-member and other checks for the rest +# of the inferred objects. +ignore-on-opaque-inference = true + +# List of symbolic message names to ignore for Mixin members. +ignored-checks-for-mixins = ["no-member", "not-async-context-manager", "not-context-manager", "attribute-defined-outside-init"] + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes = ["optparse.Values", "thread._local", "_thread._local"] + +# Show a hint with possible names when a member name was not found. The aspect of +# finding the hint is based on edit distance. +missing-member-hint = true + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance = 1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices = 1 + +# Regex pattern to define which classes are considered mixins. +mixin-class-rgx = ".*[Mm]ixin" + +# List of decorators that change the signature of a decorated function. +# signature-mutators = + +[tool.pylint.variables] +# List of additional names supposed to be defined in builtins. Remember that you +# should avoid defining new builtins when possible. +# additional-builtins = + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables = true + +# List of names allowed to shadow builtins +# allowed-redefined-builtins = + +# List of strings which can identify a callback function by name. A callback name +# must start or end with one of those strings. +callbacks = ["cb_", "_cb", "_callback"] + +# A regular expression matching the name of dummy variables (i.e. expected to not +# be used). +dummy-variables-rgx = "_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_" + +# Argument names that match this expression will be ignored. Default to name with +# leading underscore. +ignored-argument-names = "_.*|^ignored_|^unused_" + +# Tells whether we should check for unused import in __init__ files. +# init-import = + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules = ["six.moves", "future.builtins"] diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 00000000..fe21d76f --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,7 @@ +coverage[toml] +mypy +pylint +pytest +rich +types-PyYAML +types-requests diff --git a/requirements.txt b/requirements.txt index ae1f79e0..1c6d8b40 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -requests pyyaml +requests diff --git a/setup.py b/setup.py index fcc4d600..1698e52c 100644 --- a/setup.py +++ b/setup.py @@ -1,39 +1,11 @@ -"""Bootstrapper for docker's ENTRYPOINT executable.""" -import os -from setuptools import setup +#!/usr/bin/env python +"""Bootstrapper for docker's ENTRYPOINT executable. +Since using setup.py is no longer std convention, +all install information is located in pyproject.toml +""" -ROOT_DIR = os.path.abspath(os.path.dirname(__file__)) -REPO = "https://github.com/" -repo = os.getenv("GITHUB_REPOSITORY", None) # in case this is published from a fork -REPO += "cpp-linter/cpp-linter-action" if repo is None else repo +import setuptools -setup( - name="cpp_linter", - # use_scm_version=True, - # setup_requires=["setuptools_scm"], - version="1.4.2", - description=__doc__, - long_description=( - "A python package that powers the github action named cpp-linter-action. " - + f"See `the github repository README <{REPO}#readme>`_ for full details." - ), - author="Brendan Doherty", - author_email="2bndy5@gmail.com", - install_requires=["requests", "pyyaml"], # pyyaml is installed with clang-tidy - license="MIT", - # See https://pypi.python.org/pypi?%3Aaction=list_classifiers - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 3", - ], - keywords="clang clang-tidy clang-format", - packages=["cpp_linter"], - entry_points={"console_scripts": ["cpp-linter=cpp_linter.run:main"]}, - # Specifiy your homepage URL for your project here - url=REPO, - download_url=f"{REPO}/releases", -) +setuptools.setup() diff --git a/tests/capture_tools_output/.clang-format b/tests/capture_tools_output/.clang-format new file mode 100644 index 00000000..69971928 --- /dev/null +++ b/tests/capture_tools_output/.clang-format @@ -0,0 +1,148 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: true +AlignConsecutiveAssignments: false +AlignConsecutiveBitFields: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortEnumsOnASingleLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: true + AfterControlStatement: Always + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: false + AfterStruct: true + AfterUnion: true + AfterExternBlock: false + BeforeCatch: false + BeforeElse: true + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + - Regex: '.*' + Priority: 1 + SortPriority: 0 +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentCaseLabels: false +IndentCaseBlocks: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentExternBlock: AfterExternBlock +IndentWidth: 4 +IndentWrappedFunctionNames: false +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: false +SortIncludes: false +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +Standard: Latest +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE +... diff --git a/tests/capture_tools_output/.clang-tidy b/tests/capture_tools_output/.clang-tidy new file mode 100644 index 00000000..19dc3ea0 --- /dev/null +++ b/tests/capture_tools_output/.clang-tidy @@ -0,0 +1,349 @@ +--- +Checks: 'clang-diagnostic-*,clang-analyzer-*,-boost-*,bugprone-*,performance-*,readability-*,portability-*,modernize-*,clang-analyzer-*,cppcoreguidelines-*,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers' +WarningsAsErrors: '' +HeaderFilterRegex: '' +AnalyzeTemporaryDtors: false +FormatStyle: none +User: ytreh +CheckOptions: + - key: modernize-replace-auto-ptr.IncludeStyle + value: llvm + - key: cppcoreguidelines-no-malloc.Reallocations + value: '::realloc' + - key: cppcoreguidelines-owning-memory.LegacyResourceConsumers + value: '::free;::realloc;::freopen;::fclose' + - key: readability-static-accessed-through-instance.NameSpecifierNestingThreshold + value: '3' + - key: readability-function-size.VariableThreshold + value: '4294967295' + - key: modernize-use-auto.MinTypeNameLength + value: '5' + - key: bugprone-reserved-identifier.Invert + value: 'false' + - key: performance-move-const-arg.CheckTriviallyCopyableMove + value: 'true' + - key: cert-dcl16-c.NewSuffixes + value: 'L;LL;LU;LLU' + - key: bugprone-narrowing-conversions.WarnOnFloatingPointNarrowingConversion + value: 'true' + - key: readability-identifier-naming.GetConfigPerFile + value: 'true' + - key: bugprone-narrowing-conversions.PedanticMode + value: 'false' + - key: readability-inconsistent-declaration-parameter-name.Strict + value: 'false' + - key: cppcoreguidelines-macro-usage.CheckCapsOnly + value: 'false' + - key: bugprone-unused-return-value.CheckedFunctions + value: '::std::async;::std::launder;::std::remove;::std::remove_if;::std::unique;::std::unique_ptr::release;::std::basic_string::empty;::std::vector::empty;::std::back_inserter;::std::distance;::std::find;::std::find_if;::std::inserter;::std::lower_bound;::std::make_pair;::std::map::count;::std::map::find;::std::map::lower_bound;::std::multimap::equal_range;::std::multimap::upper_bound;::std::set::count;::std::set::find;::std::setfill;::std::setprecision;::std::setw;::std::upper_bound;::std::vector::at;::bsearch;::ferror;::feof;::isalnum;::isalpha;::isblank;::iscntrl;::isdigit;::isgraph;::islower;::isprint;::ispunct;::isspace;::isupper;::iswalnum;::iswprint;::iswspace;::isxdigit;::memchr;::memcmp;::strcmp;::strcoll;::strncmp;::strpbrk;::strrchr;::strspn;::strstr;::wcscmp;::access;::bind;::connect;::difftime;::dlsym;::fnmatch;::getaddrinfo;::getopt;::htonl;::htons;::iconv_open;::inet_addr;::isascii;::isatty;::mmap;::newlocale;::openat;::pathconf;::pthread_equal;::pthread_getspecific;::pthread_mutex_trylock;::readdir;::readlink;::recvmsg;::regexec;::scandir;::semget;::setjmp;::shm_open;::shmget;::sigismember;::strcasecmp;::strsignal;::ttyname' + - key: modernize-use-default-member-init.UseAssignment + value: 'false' + - key: readability-function-size.NestingThreshold + value: '4294967295' + - key: modernize-use-override.AllowOverrideAndFinal + value: 'false' + - key: readability-function-size.ParameterThreshold + value: '4294967295' + - key: modernize-pass-by-value.ValuesOnly + value: 'false' + - key: modernize-loop-convert.IncludeStyle + value: llvm + - key: cert-str34-c.DiagnoseSignedUnsignedCharComparisons + value: '0' + - key: bugprone-suspicious-string-compare.WarnOnLogicalNotComparison + value: 'false' + - key: cppcoreguidelines-explicit-virtual-functions.AllowOverrideAndFinal + value: 'false' + - key: readability-redundant-smartptr-get.IgnoreMacros + value: 'true' + - key: readability-identifier-naming.AggressiveDependentMemberLookup + value: 'false' + - key: bugprone-suspicious-string-compare.WarnOnImplicitComparison + value: 'true' + - key: modernize-use-emplace.TupleTypes + value: '::std::pair;::std::tuple' + - key: modernize-use-emplace.TupleMakeFunctions + value: '::std::make_pair;::std::make_tuple' + - key: cppcoreguidelines-owning-memory.LegacyResourceProducers + value: '::malloc;::aligned_alloc;::realloc;::calloc;::fopen;::freopen;::tmpfile' + - key: bugprone-argument-comment.CommentNullPtrs + value: '0' + - key: bugprone-argument-comment.StrictMode + value: '0' + - key: cppcoreguidelines-init-variables.IncludeStyle + value: llvm + - key: modernize-use-nodiscard.ReplacementString + value: '[[nodiscard]]' + - key: modernize-loop-convert.MakeReverseRangeHeader + value: '' + - key: modernize-replace-random-shuffle.IncludeStyle + value: llvm + - key: cppcoreguidelines-narrowing-conversions.WarnOnFloatingPointNarrowingConversion + value: 'true' + - key: modernize-use-bool-literals.IgnoreMacros + value: 'true' + - key: bugprone-unhandled-self-assignment.WarnOnlyIfThisHasSuspiciousField + value: 'true' + - key: google-readability-namespace-comments.ShortNamespaceLines + value: '10' + - key: bugprone-suspicious-string-compare.StringCompareLikeFunctions + value: '' + - key: modernize-avoid-bind.PermissiveParameterList + value: 'false' + - key: modernize-use-override.FinalSpelling + value: final + - key: performance-move-constructor-init.IncludeStyle + value: llvm + - key: modernize-loop-convert.UseCxx20ReverseRanges + value: 'true' + - key: modernize-use-noexcept.ReplacementString + value: '' + - key: modernize-use-using.IgnoreMacros + value: 'true' + - key: performance-type-promotion-in-math-fn.IncludeStyle + value: llvm + - key: cppcoreguidelines-explicit-virtual-functions.FinalSpelling + value: final + - key: modernize-loop-convert.NamingStyle + value: CamelCase + - key: bugprone-suspicious-include.ImplementationFileExtensions + value: 'c;cc;cpp;cxx' + - key: cppcoreguidelines-pro-type-member-init.UseAssignment + value: 'false' + - key: modernize-loop-convert.MakeReverseRangeFunction + value: '' + - key: bugprone-suspicious-include.HeaderFileExtensions + value: ';h;hh;hpp;hxx' + - key: performance-no-automatic-move.AllowedTypes + value: '' + - key: performance-for-range-copy.WarnOnAllAutoCopies + value: 'false' + - key: bugprone-argument-comment.CommentIntegerLiterals + value: '0' + - key: bugprone-suspicious-missing-comma.SizeThreshold + value: '5' + - key: readability-inconsistent-declaration-parameter-name.IgnoreMacros + value: 'true' + - key: readability-identifier-naming.IgnoreFailedSplit + value: 'false' + - key: modernize-pass-by-value.IncludeStyle + value: llvm + - key: bugprone-sizeof-expression.WarnOnSizeOfThis + value: 'true' + - key: readability-qualified-auto.AddConstToQualified + value: 'true' + - key: bugprone-string-constructor.WarnOnLargeLength + value: 'true' + - key: bugprone-too-small-loop-variable.MagnitudeBitsUpperLimit + value: '16' + - key: readability-simplify-boolean-expr.ChainedConditionalReturn + value: 'false' + - key: cppcoreguidelines-explicit-virtual-functions.OverrideSpelling + value: override + - key: readability-else-after-return.WarnOnConditionVariables + value: 'true' + - key: readability-uppercase-literal-suffix.IgnoreMacros + value: 'true' + - key: modernize-use-nullptr.NullMacros + value: 'NULL' + - key: modernize-make-shared.IgnoreMacros + value: 'true' + - key: bugprone-dynamic-static-initializers.HeaderFileExtensions + value: ';h;hh;hpp;hxx' + - key: bugprone-suspicious-enum-usage.StrictMode + value: 'false' + - key: performance-unnecessary-copy-initialization.AllowedTypes + value: '' + - key: bugprone-suspicious-missing-comma.MaxConcatenatedTokens + value: '5' + - key: modernize-use-transparent-functors.SafeMode + value: 'false' + - key: cppcoreguidelines-macro-usage.AllowedRegexp + value: '^DEBUG_*' + - key: modernize-make-shared.IgnoreDefaultInitialization + value: 'true' + - key: bugprone-argument-comment.CommentCharacterLiterals + value: '0' + - key: cppcoreguidelines-narrowing-conversions.PedanticMode + value: 'false' + - key: bugprone-not-null-terminated-result.WantToUseSafeFunctions + value: 'true' + - key: modernize-make-shared.IncludeStyle + value: llvm + - key: bugprone-string-constructor.LargeLengthThreshold + value: '8388608' + - key: readability-simplify-boolean-expr.ChainedConditionalAssignment + value: 'false' + - key: cppcoreguidelines-special-member-functions.AllowMissingMoveFunctions + value: 'false' + - key: cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField + value: '0' + - key: bugprone-exception-escape.FunctionsThatShouldNotThrow + value: '' + - key: bugprone-signed-char-misuse.CharTypdefsToIgnore + value: '' + - key: performance-inefficient-vector-operation.EnableProto + value: 'false' + - key: modernize-loop-convert.MaxCopySize + value: '16' + - key: bugprone-argument-comment.CommentFloatLiterals + value: '0' + - key: readability-function-size.LineThreshold + value: '4294967295' + - key: portability-simd-intrinsics.Suggest + value: 'false' + - key: modernize-make-shared.MakeSmartPtrFunction + value: 'std::make_shared' + - key: cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors + value: 'true' + - key: cppcoreguidelines-pro-bounds-constant-array-index.GslHeader + value: '' + - key: modernize-make-unique.IgnoreMacros + value: 'true' + - key: modernize-make-shared.MakeSmartPtrFunctionHeader + value: '' + - key: performance-for-range-copy.AllowedTypes + value: '' + - key: modernize-use-override.IgnoreDestructors + value: 'false' + - key: bugprone-sizeof-expression.WarnOnSizeOfConstant + value: 'true' + - key: readability-redundant-string-init.StringNames + value: '::std::basic_string_view;::std::basic_string' + - key: modernize-make-unique.IgnoreDefaultInitialization + value: 'true' + - key: modernize-use-emplace.ContainersWithPushBack + value: '::std::vector;::std::list;::std::deque' + - key: modernize-make-unique.IncludeStyle + value: llvm + - key: readability-braces-around-statements.ShortStatementLines + value: '0' + - key: bugprone-argument-comment.CommentUserDefinedLiterals + value: '0' + - key: bugprone-argument-comment.CommentBoolLiterals + value: '0' + - key: modernize-use-override.OverrideSpelling + value: override + - key: performance-inefficient-string-concatenation.StrictMode + value: 'false' + - key: readability-implicit-bool-conversion.AllowPointerConditions + value: 'false' + - key: readability-redundant-declaration.IgnoreMacros + value: 'true' + - key: google-readability-braces-around-statements.ShortStatementLines + value: '1' + - key: modernize-make-unique.MakeSmartPtrFunction + value: 'std::make_unique' + - key: cppcoreguidelines-pro-type-member-init.IgnoreArrays + value: 'false' + - key: readability-else-after-return.WarnOnUnfixable + value: 'true' + - key: bugprone-reserved-identifier.AllowedIdentifiers + value: '' + - key: modernize-use-emplace.IgnoreImplicitConstructors + value: 'false' + - key: modernize-make-unique.MakeSmartPtrFunctionHeader + value: '' + - key: portability-restrict-system-includes.Includes + value: '*' + - key: modernize-use-equals-delete.IgnoreMacros + value: 'true' + - key: cppcoreguidelines-pro-bounds-constant-array-index.IncludeStyle + value: llvm + - key: cppcoreguidelines-macro-usage.IgnoreCommandLineMacros + value: 'true' + - key: bugprone-misplaced-widening-cast.CheckImplicitCasts + value: 'false' + - key: cppcoreguidelines-non-private-member-variables-in-classes.IgnorePublicMemberVariables + value: 'false' + - key: modernize-loop-convert.MinConfidence + value: reasonable + - key: performance-unnecessary-value-param.AllowedTypes + value: '' + - key: bugprone-suspicious-missing-comma.RatioThreshold + value: '0.200000' + - key: cppcoreguidelines-special-member-functions.AllowMissingMoveFunctionsWhenCopyIsDeleted + value: 'false' + - key: readability-uppercase-literal-suffix.NewSuffixes + value: '' + - key: google-readability-namespace-comments.SpacesBeforeComments + value: '2' + - key: readability-function-cognitive-complexity.Threshold + value: '25' + - key: cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic + value: 'true' + - key: bugprone-argument-comment.IgnoreSingleArgument + value: '0' + - key: cppcoreguidelines-no-malloc.Allocations + value: '::malloc;::calloc' + - key: modernize-use-noexcept.UseNoexceptFalse + value: 'true' + - key: bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression + value: 'false' + - key: performance-faster-string-find.StringLikeClasses + value: '::std::basic_string;::std::basic_string_view' + - key: bugprone-assert-side-effect.CheckFunctionCalls + value: 'false' + - key: readability-function-size.BranchThreshold + value: '4294967295' + - key: bugprone-string-constructor.StringNames + value: '::std::basic_string;::std::basic_string_view' + - key: bugprone-assert-side-effect.AssertMacros + value: assert + - key: bugprone-exception-escape.IgnoredExceptions + value: '' + - key: readability-function-size.StatementThreshold + value: '800' + - key: modernize-use-default-member-init.IgnoreMacros + value: 'true' + - key: llvm-qualified-auto.AddConstToQualified + value: '0' + - key: bugprone-argument-comment.CommentStringLiterals + value: '0' + - key: readability-identifier-naming.IgnoreMainLikeFunctions + value: 'false' + - key: bugprone-signed-char-misuse.DiagnoseSignedUnsignedCharComparisons + value: 'true' + - key: readability-implicit-bool-conversion.AllowIntegerConditions + value: 'false' + - key: cppcoreguidelines-init-variables.MathHeader + value: '' + - key: google-readability-function-size.StatementThreshold + value: '800' + - key: llvm-else-after-return.WarnOnConditionVariables + value: '0' + - key: bugprone-sizeof-expression.WarnOnSizeOfCompareToConstant + value: 'true' + - key: bugprone-reserved-identifier.AggressiveDependentMemberLookup + value: 'false' + - key: modernize-raw-string-literal.DelimiterStem + value: lit + - key: modernize-use-equals-default.IgnoreMacros + value: 'true' + - key: cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor + value: 'false' + - key: modernize-raw-string-literal.ReplaceShorterLiterals + value: 'false' + - key: modernize-use-emplace.SmartPointers + value: '::std::shared_ptr;::std::unique_ptr;::std::auto_ptr;::std::weak_ptr' + - key: cppcoreguidelines-no-malloc.Deallocations + value: '::free' + - key: modernize-use-auto.RemoveStars + value: 'false' + - key: bugprone-dangling-handle.HandleClasses + value: 'std::basic_string_view;std::experimental::basic_string_view' + - key: performance-inefficient-vector-operation.VectorLikeClasses + value: '::std::vector' + - key: portability-simd-intrinsics.Std + value: '' + - key: performance-unnecessary-value-param.IncludeStyle + value: llvm + - key: readability-redundant-member-init.IgnoreBaseInCopyConstructors + value: 'false' + - key: modernize-replace-disallow-copy-and-assign-macro.MacroName + value: DISALLOW_COPY_AND_ASSIGN + - key: llvm-else-after-return.WarnOnUnfixable + value: '0' + - key: readability-simplify-subscript-expr.Types + value: '::std::basic_string;::std::basic_string_view;::std::vector;::std::array' +... diff --git a/tests/capture_tools_output/event_files.json b/tests/capture_tools_output/event_files.json new file mode 100644 index 00000000..ba8947ac --- /dev/null +++ b/tests/capture_tools_output/event_files.json @@ -0,0 +1,230 @@ +[ + { + "sha": "7f1eed09c6c07682738de7b5141fd151d16cf368", + "filename": "CMakeLists.txt", + "status": "modified", + "additions": 14, + "deletions": 2, + "changes": 16, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/CMakeLists.txt", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/CMakeLists.txt", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/CMakeLists.txt?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -28,9 +28,21 @@ else()\n \"-Wredundant-decls\")\n endif()\n \n+option(ENABLE_SDL2_NET \"Enable SDL2_net\" On)\n+option(ENABLE_SDL2_MIXER \"Enable SDL2_mixer\" On)\n+\n find_package(SDL2 2.0.7)\n-find_package(SDL2_mixer 2.0.2)\n-find_package(SDL2_net 2.0.0)\n+if(ENABLE_SDL2_MIXER)\n+ find_package(SDL2_mixer 2.0.2)\n+else()\n+ add_compile_definitions(DISABLE_SDL2MIXER=1)\n+endif()\n+\n+if(ENABLE_SDL2_NET)\n+ find_package(SDL2_net 2.0.0)\n+else()\n+ add_compile_definitions(DISABLE_SDL2NET=1)\n+endif()\n \n # Check for libsamplerate.\n find_package(samplerate)" + }, + { + "sha": "fd5d8bcecd8a6edb6d77c8686ae32a995b571066", + "filename": "configure.ac", + "status": "modified", + "additions": 17, + "deletions": 2, + "changes": 19, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/configure.ac", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/configure.ac", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/configure.ac?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -32,8 +32,23 @@ then\n fi\n \n PKG_CHECK_MODULES(SDL, [sdl2 >= 2.0.7])\n-PKG_CHECK_MODULES(SDLMIXER, [SDL2_mixer >= 2.0.2])\n-PKG_CHECK_MODULES(SDLNET, [SDL2_net >= 2.0.0])\n+# Check for SDL2_mixer\n+AC_ARG_ENABLE([sdl2mixer],\n+AS_HELP_STRING([--disable-sdl2mixer], [Disable SDL2_mixer support])\n+)\n+AS_IF([test \"x$enable_sdl2mixer\" != xno], [\n+ PKG_CHECK_MODULES(SDLMIXER, [SDL2_mixer >= 2.0.2])], [\n+ AC_DEFINE([DISABLE_SDL2MIXER], [1], [SDL2_mixer disabled])\n+])\n+\n+# Check for networking\n+AC_ARG_ENABLE([sdl2net],\n+AS_HELP_STRING([--disable-sdl2net], [Disable SDL2_net support])\n+)\n+AS_IF([test \"x$enable_sdl2net\" != xno], [\n+ PKG_CHECK_MODULES(SDLNET, [SDL2_net >= 2.0.0])], [\n+ AC_DEFINE([DISABLE_SDL2NET], [1], [SDL2_net disabled])\n+])\n \n # Check for bash-completion.\n AC_ARG_ENABLE([bash-completion]," + }, + { + "sha": "151f7617dde7d1216f7212b45bb5aeb7661bc86b", + "filename": "opl/CMakeLists.txt", + "status": "modified", + "additions": 4, + "deletions": 1, + "changes": 5, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/opl%2FCMakeLists.txt", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/opl%2FCMakeLists.txt", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/opl%2FCMakeLists.txt?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -12,4 +12,7 @@ add_library(opl STATIC\n target_include_directories(opl\n INTERFACE \".\"\n PRIVATE \"${CMAKE_CURRENT_BINARY_DIR}/../\")\n-target_link_libraries(opl SDL2::mixer)\n+target_link_libraries(opl SDL2::SDL2)\n+if(ENABLE_SDL2_mixer)\n+ target_link_libraries(opl SDL2::mixer)\n+endif()" + }, + { + "sha": "46e082cf77c5e60affc7689f7357c926fc00cda3", + "filename": "opl/opl.c", + "status": "modified", + "additions": 2, + "deletions": 0, + "changes": 2, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/opl%2Fopl.c", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/opl%2Fopl.c", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/opl%2Fopl.c?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -50,7 +50,9 @@ static opl_driver_t *drivers[] =\n #ifdef _WIN32\n &opl_win32_driver,\n #endif\n+#ifndef DISABLE_SDL2MIXER\n &opl_sdl_driver,\n+#endif // DISABLE_SDL2MIXER\n NULL\n };\n " + }, + { + "sha": "6bd4e7e1f3374e289d3dc19972dcb1d5379d6b03", + "filename": "opl/opl_sdl.c", + "status": "modified", + "additions": 6, + "deletions": 0, + "changes": 6, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/opl%2Fopl_sdl.c", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/opl%2Fopl_sdl.c", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/opl%2Fopl_sdl.c?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -33,6 +33,10 @@\n \n #include \"opl_queue.h\"\n \n+\n+#ifndef DISABLE_SDL2MIXER\n+\n+\n #define MAX_SOUND_SLICE_TIME 100 /* ms */\n \n typedef struct\n@@ -511,3 +515,5 @@ opl_driver_t opl_sdl_driver =\n OPL_SDL_AdjustCallbacks,\n };\n \n+\n+#endif // DISABLE_SDL2MIXER" + }, + { + "sha": "9924263aea24261091948e455dfd2521787a04c4", + "filename": "pcsound/CMakeLists.txt", + "status": "modified", + "additions": 4, + "deletions": 1, + "changes": 5, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/pcsound%2FCMakeLists.txt", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/pcsound%2FCMakeLists.txt", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/pcsound%2FCMakeLists.txt?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -8,4 +8,7 @@ add_library(pcsound STATIC\n target_include_directories(pcsound\n INTERFACE \".\"\n PRIVATE \"${CMAKE_CURRENT_BINARY_DIR}/../\")\n-target_link_libraries(pcsound SDL2::mixer)\n+target_link_libraries(pcsound SDL2::SDL2)\n+if(ENABLE_SDL2_mixer)\n+ target_link_libraries(pcsound SDL2::mixer)\n+endif()" + }, + { + "sha": "58f9b75c2affd5f1dd990aaedab79d834575bf83", + "filename": "pcsound/pcsound.c", + "status": "modified", + "additions": 2, + "deletions": 0, + "changes": 2, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/pcsound%2Fpcsound.c", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/pcsound%2Fpcsound.c", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/pcsound%2Fpcsound.c?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -56,7 +56,9 @@ static pcsound_driver_t *drivers[] =\n #ifdef _WIN32\n &pcsound_win32_driver,\n #endif\n+#ifndef DISABLE_SDL2MIXER\n &pcsound_sdl_driver,\n+#endif // DISABLE_SDL2MIXER\n NULL,\n };\n " + }, + { + "sha": "e77c7b0d29de1b0c665686004dfda311c9d96719", + "filename": "pcsound/pcsound_sdl.c", + "status": "modified", + "additions": 6, + "deletions": 0, + "changes": 6, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/pcsound%2Fpcsound_sdl.c", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/pcsound%2Fpcsound_sdl.c", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/pcsound%2Fpcsound_sdl.c?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -24,6 +24,10 @@\n #include \"pcsound.h\"\n #include \"pcsound_internal.h\"\n \n+\n+#ifndef DISABLE_SDL2MIXER\n+\n+\n #define MAX_SOUND_SLICE_TIME 70 /* ms */\n #define SQUARE_WAVE_AMP 0x2000\n \n@@ -248,3 +252,5 @@ pcsound_driver_t pcsound_sdl_driver =\n PCSound_SDL_Shutdown,\n };\n \n+\n+#endif // DISABLE_SDL2MIXER" + }, + { + "sha": "bbb877641f2a8de6e6eb7dad5b8c866e147faf7d", + "filename": "src/CMakeLists.txt", + "status": "modified", + "additions": 18, + "deletions": 3, + "changes": 21, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2FCMakeLists.txt", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2FCMakeLists.txt", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/src%2FCMakeLists.txt?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -32,7 +32,10 @@ set(DEDSERV_FILES\n add_executable(\"${PROGRAM_PREFIX}server\" WIN32 ${COMMON_SOURCE_FILES} ${DEDSERV_FILES})\n target_include_directories(\"${PROGRAM_PREFIX}server\"\n PRIVATE \"${CMAKE_CURRENT_BINARY_DIR}/../\")\n-target_link_libraries(\"${PROGRAM_PREFIX}server\" SDL2::SDL2main SDL2::net)\n+target_link_libraries(\"${PROGRAM_PREFIX}server\" SDL2::SDL2main SDL2::SDL2)\n+if(ENABLE_SDL2_NET)\n+ target_link_libraries(\"${PROGRAM_PREFIX}server\" SDL2::net)\n+endif()\n \n # Source files used by the game binaries (chocolate-doom, etc.)\n \n@@ -121,7 +124,13 @@ set(DEHACKED_SOURCE_FILES\n set(SOURCE_FILES ${COMMON_SOURCE_FILES} ${GAME_SOURCE_FILES})\n set(SOURCE_FILES_WITH_DEH ${SOURCE_FILES} ${DEHACKED_SOURCE_FILES})\n \n-set(EXTRA_LIBS SDL2::SDL2main SDL2::SDL2 SDL2::mixer SDL2::net textscreen pcsound opl)\n+set(EXTRA_LIBS SDL2::SDL2main SDL2::SDL2 textscreen pcsound opl)\n+if(ENABLE_SDL2_MIXER)\n+ list(APPEND EXTRA_LIBS SDL2::mixer)\n+endif()\n+if(ENABLE_SDL2_NET)\n+ list(APPEND EXTRA_LIBS SDL2::net)\n+endif()\n if(SAMPLERATE_FOUND)\n list(APPEND EXTRA_LIBS samplerate::samplerate)\n endif()\n@@ -213,7 +222,13 @@ endif()\n \n target_include_directories(\"${PROGRAM_PREFIX}setup\"\n PRIVATE \"${CMAKE_CURRENT_BINARY_DIR}/../\")\n-target_link_libraries(\"${PROGRAM_PREFIX}setup\" SDL2::SDL2main SDL2::SDL2 SDL2::mixer SDL2::net setup textscreen)\n+target_link_libraries(\"${PROGRAM_PREFIX}setup\" SDL2::SDL2main SDL2::SDL2 setup textscreen)\n+if(ENABLE_SDL2_mixer)\n+ target_link_libraries(\"${PROGRAM_PREFIX}setup\" SDL2::mixer)\n+endif()\n+if(ENABLE_SDL2_NET)\n+ target_link_libraries(\"${PROGRAM_PREFIX}setup\" SDL2::net)\n+endif()\n \n if(MSVC)\n set_target_properties(\"${PROGRAM_PREFIX}setup\" PROPERTIES" + }, + { + "sha": "82b114b4178595085c6745bc70a570922415be1f", + "filename": "src/doom/CMakeLists.txt", + "status": "modified", + "additions": 7, + "deletions": 1, + "changes": 8, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fdoom%2FCMakeLists.txt", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fdoom%2FCMakeLists.txt", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/src%2Fdoom%2FCMakeLists.txt?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -68,4 +68,10 @@ add_library(doom STATIC\n wi_stuff.c wi_stuff.h)\n \n target_include_directories(doom PRIVATE \"../\" \"${CMAKE_CURRENT_BINARY_DIR}/../../\")\n-target_link_libraries(doom SDL2::SDL2 SDL2::mixer SDL2::net)\n+target_link_libraries(doom SDL2::SDL2)\n+if(ENABLE_SDL2_mixer)\n+ target_link_libraries(doom SDL2::mixer)\n+endif()\n+if(ENABLE_SDL2_NET)\n+ target_link_libraries(doom SDL2::net)\n+endif()" + }, + { + "sha": "1ea060bfdb8b3147e36b3431fee27934c8a93b34", + "filename": "src/heretic/CMakeLists.txt", + "status": "modified", + "additions": 7, + "deletions": 1, + "changes": 8, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fheretic%2FCMakeLists.txt", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fheretic%2FCMakeLists.txt", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/src%2Fheretic%2FCMakeLists.txt?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -54,4 +54,10 @@ add_library(heretic STATIC\n s_sound.c s_sound.h)\n \n target_include_directories(heretic PRIVATE \"../\" \"${CMAKE_CURRENT_BINARY_DIR}/../../\")\n-target_link_libraries(heretic textscreen SDL2::SDL2 SDL2::mixer SDL2::net)\n+target_link_libraries(heretic textscreen SDL2::SDL2)\n+if(ENABLE_SDL2_mixer)\n+ target_link_libraries(heretic SDL2::mixer)\n+endif()\n+if(ENABLE_SDL2_NET)\n+ target_link_libraries(heretic SDL2::net)\n+endif()" + }, + { + "sha": "0dbd170bfdb06c8209ec654d4d5377e334be886e", + "filename": "src/hexen/CMakeLists.txt", + "status": "modified", + "additions": 7, + "deletions": 1, + "changes": 8, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fhexen%2FCMakeLists.txt", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fhexen%2FCMakeLists.txt", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/src%2Fhexen%2FCMakeLists.txt?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -55,4 +55,10 @@ add_library(hexen STATIC\n xddefs.h)\n \n target_include_directories(hexen PRIVATE \"../\" \"${CMAKE_CURRENT_BINARY_DIR}/../../\")\n-target_link_libraries(hexen SDL2::SDL2 SDL2::mixer SDL2::net)\n+target_link_libraries(hexen SDL2::SDL2)\n+if(ENABLE_SDL2_mixer)\n+ target_link_libraries(hexen SDL2::mixer)\n+endif()\n+if(ENABLE_SDL2_NET)\n+ target_link_libraries(hexen SDL2::net)\n+endif()" + }, + { + "sha": "3facce6f01d02c5e8eef3088fa60576b9e949829", + "filename": "src/i_musicpack.c", + "status": "modified", + "additions": 87, + "deletions": 1, + "changes": 88, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fi_musicpack.c", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fi_musicpack.c", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/src%2Fi_musicpack.c?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -44,6 +44,13 @@\n #include \"w_wad.h\"\n #include \"z_zone.h\"\n \n+\n+char *music_pack_path = \"\";\n+\n+\n+#ifndef DISABLE_SDL2MIXER\n+\n+\n #define MID_HEADER_MAGIC \"MThd\"\n #define MUS_HEADER_MAGIC \"MUS\\x1a\"\n \n@@ -99,7 +106,6 @@ static boolean music_initialized = false;\n \n static boolean sdl_was_initialized = false;\n \n-char *music_pack_path = \"\";\n \n // If true, we are playing a substitute digital track rather than in-WAD\n // MIDI/MUS track, and file_metadata contains loop metadata.\n@@ -1375,3 +1381,83 @@ music_module_t music_pack_module =\n I_MP_PollMusic,\n };\n \n+\n+#else // DISABLE_SDL2MIXER\n+\n+\n+static boolean I_NULL_InitMusic(void)\n+{\n+ return false;\n+}\n+\n+\n+static void I_NULL_ShutdownMusic(void)\n+{\n+}\n+\n+\n+static void I_NULL_SetMusicVolume(int volume)\n+{\n+}\n+\n+\n+static void I_NULL_PauseSong(void)\n+{\n+}\n+\n+\n+static void I_NULL_ResumeSong(void)\n+{\n+}\n+\n+\n+static void *I_NULL_RegisterSong(void *data, int len)\n+{\n+ return NULL;\n+}\n+\n+\n+static void I_NULL_UnRegisterSong(void *handle)\n+{\n+}\n+\n+\n+static void I_NULL_PlaySong(void *handle, boolean looping)\n+{\n+}\n+\n+\n+static void I_NULL_StopSong(void)\n+{\n+}\n+\n+\n+static boolean I_NULL_MusicIsPlaying(void)\n+{\n+ return false;\n+}\n+\n+\n+static void I_NULL_PollMusic(void)\n+{\n+}\n+\n+music_module_t music_pack_module =\n+{\n+ NULL,\n+ 0,\n+ I_NULL_InitMusic,\n+ I_NULL_ShutdownMusic,\n+ I_NULL_SetMusicVolume,\n+ I_NULL_PauseSong,\n+ I_NULL_ResumeSong,\n+ I_NULL_RegisterSong,\n+ I_NULL_UnRegisterSong,\n+ I_NULL_PlaySong,\n+ I_NULL_StopSong,\n+ I_NULL_MusicIsPlaying,\n+ I_NULL_PollMusic,\n+};\n+\n+\n+#endif // DISABLE_SDL2MIXER" + }, + { + "sha": "48fedfbd7608ad8c6575a4bd82a5e1cb3646e071", + "filename": "src/i_sdlmusic.c", + "status": "modified", + "additions": 21, + "deletions": 13, + "changes": 34, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fi_sdlmusic.c", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fi_sdlmusic.c", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/src%2Fi_sdlmusic.c?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -44,19 +44,6 @@\n #include \"w_wad.h\"\n #include \"z_zone.h\"\n \n-#define MAXMIDLENGTH (96 * 1024)\n-\n-static boolean music_initialized = false;\n-\n-// If this is true, this module initialized SDL sound and has the \n-// responsibility to shut it down\n-\n-static boolean sdl_was_initialized = false;\n-\n-static boolean win_midi_stream_opened = false;\n-\n-static boolean musicpaused = false;\n-static int current_music_volume;\n \n char *fluidsynth_sf_path = \"\";\n char *timidity_cfg_path = \"\";\n@@ -138,6 +125,25 @@ void I_InitTimidityConfig(void)\n }\n }\n \n+\n+#ifndef DISABLE_SDL2MIXER\n+\n+\n+#define MAXMIDLENGTH (96 * 1024)\n+\n+static boolean music_initialized = false;\n+\n+// If this is true, this module initialized SDL sound and has the\n+// responsibility to shut it down\n+\n+static boolean sdl_was_initialized = false;\n+\n+static boolean win_midi_stream_opened = false;\n+\n+static boolean musicpaused = false;\n+static int current_music_volume;\n+\n+\n // Remove the temporary config file generated by I_InitTimidityConfig().\n \n static void RemoveTimidityConfig(void)\n@@ -588,3 +594,5 @@ music_module_t music_sdl_module =\n NULL, // Poll\n };\n \n+\n+#endif // DISABLE_SDL2MIXER" + }, + { + "sha": "7f2a26096b218c5323f5dc569c6f70ab399782bf", + "filename": "src/i_sdlsound.c", + "status": "modified", + "additions": 17, + "deletions": 9, + "changes": 26, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fi_sdlsound.c", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fi_sdlsound.c", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/src%2Fi_sdlsound.c?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -41,6 +41,21 @@\n \n #include \"doomtype.h\"\n \n+\n+int use_libsamplerate = 0;\n+\n+// Scale factor used when converting libsamplerate floating point numbers\n+// to integers. Too high means the sounds can clip; too low means they\n+// will be too quiet. This is an amount that should avoid clipping most\n+// of the time: with all the Doom IWAD sound effects, at least. If a PWAD\n+// is used, clipping might occur.\n+\n+float libsamplerate_scale = 0.65f;\n+\n+\n+#ifndef DISABLE_SDL2MIXER\n+\n+\n #define LOW_PASS_FILTER\n //#define DEBUG_DUMP_WAVS\n #define NUM_CHANNELS 16\n@@ -77,15 +92,6 @@ static allocated_sound_t *allocated_sounds_head = NULL;\n static allocated_sound_t *allocated_sounds_tail = NULL;\n static int allocated_sounds_size = 0;\n \n-int use_libsamplerate = 0;\n-\n-// Scale factor used when converting libsamplerate floating point numbers\n-// to integers. Too high means the sounds can clip; too low means they\n-// will be too quiet. This is an amount that should avoid clipping most\n-// of the time: with all the Doom IWAD sound effects, at least. If a PWAD\n-// is used, clipping might occur.\n-\n-float libsamplerate_scale = 0.65f;\n \n // Hook a sound into the linked list at the head.\n \n@@ -1135,3 +1141,5 @@ sound_module_t sound_sdl_module =\n I_SDL_PrecacheSounds,\n };\n \n+\n+#endif // DISABLE_SDL2MIXER" + }, + { + "sha": "9cf1fd95db13504ffc6f98ead8c9150f03db72aa", + "filename": "src/i_sound.c", + "status": "modified", + "additions": 4, + "deletions": 0, + "changes": 4, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fi_sound.c", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fi_sound.c", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/src%2Fi_sound.c?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -99,7 +99,9 @@ static int snd_mport = 0;\n \n static sound_module_t *sound_modules[] = \n {\n+#ifndef DISABLE_SDL2MIXER\n &sound_sdl_module,\n+#endif // DISABLE_SDL2MIXER\n &sound_pcsound_module,\n NULL,\n };\n@@ -108,7 +110,9 @@ static sound_module_t *sound_modules[] =\n \n static music_module_t *music_modules[] =\n {\n+#ifndef DISABLE_SDL2MIXER\n &music_sdl_module,\n+#endif // DISABLE_SDL2MIXER\n &music_opl_module,\n NULL,\n };" + }, + { + "sha": "c1f701c0b9f653eb3a36b71627747a6eda963d8f", + "filename": "src/net_sdl.c", + "status": "modified", + "additions": 63, + "deletions": 0, + "changes": 63, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fnet_sdl.c", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fnet_sdl.c", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/src%2Fnet_sdl.c?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -33,6 +33,10 @@\n // NETWORKING\n //\n \n+\n+#ifndef DISABLE_SDL2NET\n+\n+\n #include \n \n #define DEFAULT_PORT 2342\n@@ -376,3 +380,62 @@ net_module_t net_sdl_module =\n NET_SDL_ResolveAddress,\n };\n \n+\n+#else // DISABLE_SDL2NET\n+\n+// no-op implementation\n+\n+\n+static boolean NET_NULL_InitClient(void)\n+{\n+ return false;\n+}\n+\n+\n+static boolean NET_NULL_InitServer(void)\n+{\n+ return false;\n+}\n+\n+\n+static void NET_NULL_SendPacket(net_addr_t *addr, net_packet_t *packet)\n+{\n+}\n+\n+\n+static boolean NET_NULL_RecvPacket(net_addr_t **addr, net_packet_t **packet)\n+{\n+ return false;\n+}\n+\n+\n+static void NET_NULL_AddrToString(net_addr_t *addr, char *buffer, int buffer_len)\n+{\n+\n+}\n+\n+\n+static void NET_NULL_FreeAddress(net_addr_t *addr)\n+{\n+}\n+\n+\n+net_addr_t *NET_NULL_ResolveAddress(const char *address)\n+{\n+ return NULL;\n+}\n+\n+\n+net_module_t net_sdl_module =\n+{\n+ NET_NULL_InitClient,\n+ NET_NULL_InitServer,\n+ NET_NULL_SendPacket,\n+ NET_NULL_RecvPacket,\n+ NET_NULL_AddrToString,\n+ NET_NULL_FreeAddress,\n+ NET_NULL_ResolveAddress,\n+};\n+\n+\n+#endif // DISABLE_SDL2NET" + }, + { + "sha": "90df2114163152ff732a9ba4a8bc18ef3d45d1bd", + "filename": "src/setup/CMakeLists.txt", + "status": "modified", + "additions": 4, + "deletions": 1, + "changes": 5, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fsetup%2FCMakeLists.txt", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fsetup%2FCMakeLists.txt", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/src%2Fsetup%2FCMakeLists.txt?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -15,4 +15,7 @@ add_library(setup STATIC\n txt_mouseinput.c txt_mouseinput.h)\n \n target_include_directories(setup PRIVATE \"../\" \"${CMAKE_CURRENT_BINARY_DIR}/../../\")\n-target_link_libraries(setup textscreen SDL2::SDL2 SDL2::mixer)\n+target_link_libraries(setup textscreen SDL2::SDL2)\n+if(ENABLE_SDL2_mixer)\n+ target_link_libraries(setup SDL2::mixer)\n+endif()" + }, + { + "sha": "37b17ade983155369c0897c0935d40dfeb2048c7", + "filename": "src/strife/CMakeLists.txt", + "status": "modified", + "additions": 7, + "deletions": 1, + "changes": 8, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fstrife%2FCMakeLists.txt", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fstrife%2FCMakeLists.txt", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/src%2Fstrife%2FCMakeLists.txt?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -70,4 +70,10 @@ set(STRIFE_SOURCES\n add_library(strife STATIC ${STRIFE_SOURCES})\n \n target_include_directories(strife PRIVATE \"../\" \"../../win32/\" \"${CMAKE_CURRENT_BINARY_DIR}/../../\")\n-target_link_libraries(strife textscreen SDL2::SDL2 SDL2::mixer SDL2::net)\n+target_link_libraries(strife textscreen SDL2::SDL2)\n+if(ENABLE_SDL2_mixer)\n+ target_link_libraries(strife SDL2::mixer)\n+endif()\n+if(ENABLE_SDL2_NET)\n+ target_link_libraries(strife SDL2::net)\n+endif()" + } +] diff --git a/tests/capture_tools_output/expected_result.json b/tests/capture_tools_output/expected_result.json new file mode 100644 index 00000000..0912b966 --- /dev/null +++ b/tests/capture_tools_output/expected_result.json @@ -0,0 +1,320 @@ +[ + { + "sha": "46e082cf77c5e60affc7689f7357c926fc00cda3", + "filename": "opl/opl.c", + "status": "modified", + "additions": 2, + "deletions": 0, + "changes": 2, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/opl%2Fopl.c", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/opl%2Fopl.c", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/opl%2Fopl.c?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -50,7 +50,9 @@ static opl_driver_t *drivers[] =\n #ifdef _WIN32\n &opl_win32_driver,\n #endif\n+#ifndef DISABLE_SDL2MIXER\n &opl_sdl_driver,\n+#endif // DISABLE_SDL2MIXER\n NULL\n };\n ", + "line_filter": { + "diff_chunks": [ + [ + 50, + 59 + ] + ], + "lines_added": [ + [ + 53, + 54 + ], + [ + 55, + 56 + ] + ] + } + }, + { + "sha": "6bd4e7e1f3374e289d3dc19972dcb1d5379d6b03", + "filename": "opl/opl_sdl.c", + "status": "modified", + "additions": 6, + "deletions": 0, + "changes": 6, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/opl%2Fopl_sdl.c", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/opl%2Fopl_sdl.c", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/opl%2Fopl_sdl.c?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -33,6 +33,10 @@\n \n #include \"opl_queue.h\"\n \n+\n+#ifndef DISABLE_SDL2MIXER\n+\n+\n #define MAX_SOUND_SLICE_TIME 100 /* ms */\n \n typedef struct\n@@ -511,3 +515,5 @@ opl_driver_t opl_sdl_driver =\n OPL_SDL_AdjustCallbacks,\n };\n \n+\n+#endif // DISABLE_SDL2MIXER", + "line_filter": { + "diff_chunks": [ + [ + 33, + 43 + ], + [ + 515, + 520 + ] + ], + "lines_added": [ + [ + 36, + 40 + ], + [ + 518, + 520 + ] + ] + } + }, + { + "sha": "58f9b75c2affd5f1dd990aaedab79d834575bf83", + "filename": "pcsound/pcsound.c", + "status": "modified", + "additions": 2, + "deletions": 0, + "changes": 2, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/pcsound%2Fpcsound.c", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/pcsound%2Fpcsound.c", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/pcsound%2Fpcsound.c?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -56,7 +56,9 @@ static pcsound_driver_t *drivers[] =\n #ifdef _WIN32\n &pcsound_win32_driver,\n #endif\n+#ifndef DISABLE_SDL2MIXER\n &pcsound_sdl_driver,\n+#endif // DISABLE_SDL2MIXER\n NULL,\n };\n ", + "line_filter": { + "diff_chunks": [ + [ + 56, + 65 + ] + ], + "lines_added": [ + [ + 59, + 60 + ], + [ + 61, + 62 + ] + ] + } + }, + { + "sha": "e77c7b0d29de1b0c665686004dfda311c9d96719", + "filename": "pcsound/pcsound_sdl.c", + "status": "modified", + "additions": 6, + "deletions": 0, + "changes": 6, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/pcsound%2Fpcsound_sdl.c", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/pcsound%2Fpcsound_sdl.c", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/pcsound%2Fpcsound_sdl.c?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -24,6 +24,10 @@\n #include \"pcsound.h\"\n #include \"pcsound_internal.h\"\n \n+\n+#ifndef DISABLE_SDL2MIXER\n+\n+\n #define MAX_SOUND_SLICE_TIME 70 /* ms */\n #define SQUARE_WAVE_AMP 0x2000\n \n@@ -248,3 +252,5 @@ pcsound_driver_t pcsound_sdl_driver =\n PCSound_SDL_Shutdown,\n };\n \n+\n+#endif // DISABLE_SDL2MIXER", + "line_filter": { + "diff_chunks": [ + [ + 24, + 34 + ], + [ + 252, + 257 + ] + ], + "lines_added": [ + [ + 27, + 31 + ], + [ + 255, + 257 + ] + ] + } + }, + { + "sha": "3facce6f01d02c5e8eef3088fa60576b9e949829", + "filename": "src/i_musicpack.c", + "status": "modified", + "additions": 87, + "deletions": 1, + "changes": 88, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fi_musicpack.c", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fi_musicpack.c", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/src%2Fi_musicpack.c?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -44,6 +44,13 @@\n #include \"w_wad.h\"\n #include \"z_zone.h\"\n \n+\n+char *music_pack_path = \"\";\n+\n+\n+#ifndef DISABLE_SDL2MIXER\n+\n+\n #define MID_HEADER_MAGIC \"MThd\"\n #define MUS_HEADER_MAGIC \"MUS\\x1a\"\n \n@@ -99,7 +106,6 @@ static boolean music_initialized = false;\n \n static boolean sdl_was_initialized = false;\n \n-char *music_pack_path = \"\";\n \n // If true, we are playing a substitute digital track rather than in-WAD\n // MIDI/MUS track, and file_metadata contains loop metadata.\n@@ -1375,3 +1381,83 @@ music_module_t music_pack_module =\n I_MP_PollMusic,\n };\n \n+\n+#else // DISABLE_SDL2MIXER\n+\n+\n+static boolean I_NULL_InitMusic(void)\n+{\n+ return false;\n+}\n+\n+\n+static void I_NULL_ShutdownMusic(void)\n+{\n+}\n+\n+\n+static void I_NULL_SetMusicVolume(int volume)\n+{\n+}\n+\n+\n+static void I_NULL_PauseSong(void)\n+{\n+}\n+\n+\n+static void I_NULL_ResumeSong(void)\n+{\n+}\n+\n+\n+static void *I_NULL_RegisterSong(void *data, int len)\n+{\n+ return NULL;\n+}\n+\n+\n+static void I_NULL_UnRegisterSong(void *handle)\n+{\n+}\n+\n+\n+static void I_NULL_PlaySong(void *handle, boolean looping)\n+{\n+}\n+\n+\n+static void I_NULL_StopSong(void)\n+{\n+}\n+\n+\n+static boolean I_NULL_MusicIsPlaying(void)\n+{\n+ return false;\n+}\n+\n+\n+static void I_NULL_PollMusic(void)\n+{\n+}\n+\n+music_module_t music_pack_module =\n+{\n+ NULL,\n+ 0,\n+ I_NULL_InitMusic,\n+ I_NULL_ShutdownMusic,\n+ I_NULL_SetMusicVolume,\n+ I_NULL_PauseSong,\n+ I_NULL_ResumeSong,\n+ I_NULL_RegisterSong,\n+ I_NULL_UnRegisterSong,\n+ I_NULL_PlaySong,\n+ I_NULL_StopSong,\n+ I_NULL_MusicIsPlaying,\n+ I_NULL_PollMusic,\n+};\n+\n+\n+#endif // DISABLE_SDL2MIXER", + "line_filter": { + "diff_chunks": [ + [ + 44, + 57 + ], + [ + 106, + 112 + ], + [ + 1381, + 1464 + ] + ], + "lines_added": [ + [ + 47, + 54 + ], + [ + 1384, + 1464 + ] + ] + } + }, + { + "sha": "48fedfbd7608ad8c6575a4bd82a5e1cb3646e071", + "filename": "src/i_sdlmusic.c", + "status": "modified", + "additions": 21, + "deletions": 13, + "changes": 34, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fi_sdlmusic.c", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fi_sdlmusic.c", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/src%2Fi_sdlmusic.c?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -44,19 +44,6 @@\n #include \"w_wad.h\"\n #include \"z_zone.h\"\n \n-#define MAXMIDLENGTH (96 * 1024)\n-\n-static boolean music_initialized = false;\n-\n-// If this is true, this module initialized SDL sound and has the \n-// responsibility to shut it down\n-\n-static boolean sdl_was_initialized = false;\n-\n-static boolean win_midi_stream_opened = false;\n-\n-static boolean musicpaused = false;\n-static int current_music_volume;\n \n char *fluidsynth_sf_path = \"\";\n char *timidity_cfg_path = \"\";\n@@ -138,6 +125,25 @@ void I_InitTimidityConfig(void)\n }\n }\n \n+\n+#ifndef DISABLE_SDL2MIXER\n+\n+\n+#define MAXMIDLENGTH (96 * 1024)\n+\n+static boolean music_initialized = false;\n+\n+// If this is true, this module initialized SDL sound and has the\n+// responsibility to shut it down\n+\n+static boolean sdl_was_initialized = false;\n+\n+static boolean win_midi_stream_opened = false;\n+\n+static boolean musicpaused = false;\n+static int current_music_volume;\n+\n+\n // Remove the temporary config file generated by I_InitTimidityConfig().\n \n static void RemoveTimidityConfig(void)\n@@ -588,3 +594,5 @@ music_module_t music_sdl_module =\n NULL, // Poll\n };\n \n+\n+#endif // DISABLE_SDL2MIXER", + "line_filter": { + "diff_chunks": [ + [ + 44, + 50 + ], + [ + 125, + 150 + ], + [ + 594, + 599 + ] + ], + "lines_added": [ + [ + 128, + 147 + ], + [ + 597, + 599 + ] + ] + } + }, + { + "sha": "7f2a26096b218c5323f5dc569c6f70ab399782bf", + "filename": "src/i_sdlsound.c", + "status": "modified", + "additions": 17, + "deletions": 9, + "changes": 26, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fi_sdlsound.c", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fi_sdlsound.c", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/src%2Fi_sdlsound.c?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -41,6 +41,21 @@\n \n #include \"doomtype.h\"\n \n+\n+int use_libsamplerate = 0;\n+\n+// Scale factor used when converting libsamplerate floating point numbers\n+// to integers. Too high means the sounds can clip; too low means they\n+// will be too quiet. This is an amount that should avoid clipping most\n+// of the time: with all the Doom IWAD sound effects, at least. If a PWAD\n+// is used, clipping might occur.\n+\n+float libsamplerate_scale = 0.65f;\n+\n+\n+#ifndef DISABLE_SDL2MIXER\n+\n+\n #define LOW_PASS_FILTER\n //#define DEBUG_DUMP_WAVS\n #define NUM_CHANNELS 16\n@@ -77,15 +92,6 @@ static allocated_sound_t *allocated_sounds_head = NULL;\n static allocated_sound_t *allocated_sounds_tail = NULL;\n static int allocated_sounds_size = 0;\n \n-int use_libsamplerate = 0;\n-\n-// Scale factor used when converting libsamplerate floating point numbers\n-// to integers. Too high means the sounds can clip; too low means they\n-// will be too quiet. This is an amount that should avoid clipping most\n-// of the time: with all the Doom IWAD sound effects, at least. If a PWAD\n-// is used, clipping might occur.\n-\n-float libsamplerate_scale = 0.65f;\n \n // Hook a sound into the linked list at the head.\n \n@@ -1135,3 +1141,5 @@ sound_module_t sound_sdl_module =\n I_SDL_PrecacheSounds,\n };\n \n+\n+#endif // DISABLE_SDL2MIXER", + "line_filter": { + "diff_chunks": [ + [ + 41, + 62 + ], + [ + 92, + 98 + ], + [ + 1141, + 1146 + ] + ], + "lines_added": [ + [ + 44, + 59 + ], + [ + 1144, + 1146 + ] + ] + } + }, + { + "sha": "9cf1fd95db13504ffc6f98ead8c9150f03db72aa", + "filename": "src/i_sound.c", + "status": "modified", + "additions": 4, + "deletions": 0, + "changes": 4, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fi_sound.c", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fi_sound.c", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/src%2Fi_sound.c?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -99,7 +99,9 @@ static int snd_mport = 0;\n \n static sound_module_t *sound_modules[] = \n {\n+#ifndef DISABLE_SDL2MIXER\n &sound_sdl_module,\n+#endif // DISABLE_SDL2MIXER\n &sound_pcsound_module,\n NULL,\n };\n@@ -108,7 +110,9 @@ static sound_module_t *sound_modules[] =\n \n static music_module_t *music_modules[] =\n {\n+#ifndef DISABLE_SDL2MIXER\n &music_sdl_module,\n+#endif // DISABLE_SDL2MIXER\n &music_opl_module,\n NULL,\n };", + "line_filter": { + "diff_chunks": [ + [ + 99, + 108 + ], + [ + 110, + 119 + ] + ], + "lines_added": [ + [ + 102, + 103 + ], + [ + 104, + 105 + ], + [ + 113, + 114 + ], + [ + 115, + 116 + ] + ] + } + }, + { + "sha": "c1f701c0b9f653eb3a36b71627747a6eda963d8f", + "filename": "src/net_sdl.c", + "status": "modified", + "additions": 63, + "deletions": 0, + "changes": 63, + "blob_url": "https://github.com/chocolate-doom/chocolate-doom/blob/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fnet_sdl.c", + "raw_url": "https://github.com/chocolate-doom/chocolate-doom/raw/3c35acde7d398c32f6fad50fa902de391f573ffa/src%2Fnet_sdl.c", + "contents_url": "https://api.github.com/repos/chocolate-doom/chocolate-doom/contents/src%2Fnet_sdl.c?ref=3c35acde7d398c32f6fad50fa902de391f573ffa", + "patch": "@@ -33,6 +33,10 @@\n // NETWORKING\n //\n \n+\n+#ifndef DISABLE_SDL2NET\n+\n+\n #include \n \n #define DEFAULT_PORT 2342\n@@ -376,3 +380,62 @@ net_module_t net_sdl_module =\n NET_SDL_ResolveAddress,\n };\n \n+\n+#else // DISABLE_SDL2NET\n+\n+// no-op implementation\n+\n+\n+static boolean NET_NULL_InitClient(void)\n+{\n+ return false;\n+}\n+\n+\n+static boolean NET_NULL_InitServer(void)\n+{\n+ return false;\n+}\n+\n+\n+static void NET_NULL_SendPacket(net_addr_t *addr, net_packet_t *packet)\n+{\n+}\n+\n+\n+static boolean NET_NULL_RecvPacket(net_addr_t **addr, net_packet_t **packet)\n+{\n+ return false;\n+}\n+\n+\n+static void NET_NULL_AddrToString(net_addr_t *addr, char *buffer, int buffer_len)\n+{\n+\n+}\n+\n+\n+static void NET_NULL_FreeAddress(net_addr_t *addr)\n+{\n+}\n+\n+\n+net_addr_t *NET_NULL_ResolveAddress(const char *address)\n+{\n+ return NULL;\n+}\n+\n+\n+net_module_t net_sdl_module =\n+{\n+ NET_NULL_InitClient,\n+ NET_NULL_InitServer,\n+ NET_NULL_SendPacket,\n+ NET_NULL_RecvPacket,\n+ NET_NULL_AddrToString,\n+ NET_NULL_FreeAddress,\n+ NET_NULL_ResolveAddress,\n+};\n+\n+\n+#endif // DISABLE_SDL2NET", + "line_filter": { + "diff_chunks": [ + [ + 33, + 43 + ], + [ + 380, + 442 + ] + ], + "lines_added": [ + [ + 36, + 40 + ], + [ + 383, + 442 + ] + ] + } + } +] diff --git a/tests/capture_tools_output/test_database_path.py b/tests/capture_tools_output/test_database_path.py new file mode 100644 index 00000000..da275a30 --- /dev/null +++ b/tests/capture_tools_output/test_database_path.py @@ -0,0 +1,57 @@ +"""Tests specific to specifying the compilation database path.""" +from typing import List +from pathlib import Path, PurePath +import logging +import re +import pytest +from cpp_linter import logger +import cpp_linter.run +from cpp_linter.run import run_clang_tidy + +CLANG_TIDY_COMMAND = re.compile(r"\"clang-tidy(.*)(?:\")") + +ABS_DB_PATH = str(Path(PurePath(__file__).parent / "../../demo").resolve()) + + +@pytest.mark.parametrize( + "database,expected_args", + [ + # implicit path to the compilation database + ("", []), + # explicit relative path to the compilation database + ("../../demo", ["-p", ABS_DB_PATH]), + # explicit absolute path to the compilation database + (ABS_DB_PATH, ["-p", ABS_DB_PATH]), + ], +) +def test_db_detection( + caplog: pytest.LogCaptureFixture, + monkeypatch: pytest.MonkeyPatch, + database: str, + expected_args: List[str], +): + """test clang-tidy using a implicit path to the compilation database.""" + monkeypatch.chdir(PurePath(__file__).parent.as_posix()) + demo_src = "../../demo/demo.cpp" + rel_root = str(Path(*Path(__file__).parts[-2:])) + cpp_linter.run.RUNNER_WORKSPACE = ( + Path(PurePath(__file__).parent / "../../").resolve().as_posix() + ) + caplog.set_level(logging.DEBUG, logger=logger.name) + run_clang_tidy( + filename=(demo_src), + file_obj={}, # only used when filtering lines + version="", + checks="", # let clang-tidy use a .clang-tidy config file + lines_changed_only=0, # analyze complete file + database=database, + repo_root=rel_root, + ) + matched_args = [] + for record in caplog.records: + msg_match = CLANG_TIDY_COMMAND.search(record.message) + if msg_match is not None: + matched_args = msg_match.group(0)[:-1].split()[2:] + assert "Error while trying to load a compilation database" not in record.message + expected_args.append(demo_src) + assert matched_args == expected_args diff --git a/tests/capture_tools_output/test_tools_output.py b/tests/capture_tools_output/test_tools_output.py new file mode 100644 index 00000000..c7467fa3 --- /dev/null +++ b/tests/capture_tools_output/test_tools_output.py @@ -0,0 +1,207 @@ +"""Various tests related to the ``lines_changed_only`` option.""" +import os +import logging +from typing import Dict, Any, cast, List, Optional +from pathlib import Path +import json +import re +import pytest +import cpp_linter +from cpp_linter.run import ( + filter_out_non_source_files, + verify_files_are_present, + capture_clang_tools_output, + make_annotations, + log_commander, +) +from cpp_linter.thread_comments import list_diff_comments + +CLANG_VERSION = os.getenv("CLANG_VERSION", "12") + + +@pytest.mark.parametrize( + "extensions", [(["c"]), pytest.param(["h"], marks=pytest.mark.xfail)] +) +def test_lines_changed_only( + monkeypatch: pytest.MonkeyPatch, + caplog: pytest.LogCaptureFixture, + extensions: List[str], +): + """Test for lines changes in diff. + + This checks for + 1. ranges of diff chunks. + 2. ranges of lines in diff that only contain additions. + """ + monkeypatch.chdir(str(Path(__file__).parent)) + caplog.set_level(logging.DEBUG, logger=cpp_linter.logger.name) + cpp_linter.Globals.FILES = json.loads( + Path("event_files.json").read_text(encoding="utf-8") + ) + if filter_out_non_source_files( + ext_list=extensions, + ignored=[".github"], + not_ignored=[], + ): + test_result = Path("expected_result.json").read_text(encoding="utf-8") + for file, result in zip( + cpp_linter.Globals.FILES, + json.loads(test_result), + ): + expected = result["line_filter"]["diff_chunks"] + assert file["line_filter"]["diff_chunks"] == expected + expected = result["line_filter"]["lines_added"] + assert file["line_filter"]["lines_added"] == expected + else: + raise RuntimeError("test failed to find files") + + +TEST_REPO = re.compile(r".*github.com/(?:\w|\-|_)+/((?:\w|\-|_)+)/.*") + + +@pytest.fixture(autouse=True) +def setup_test_repo(monkeypatch: pytest.MonkeyPatch) -> None: + """Setup a test repo to run the rest of the tests in this module.""" + test_root = Path(__file__).parent + cpp_linter.Globals.FILES = json.loads( + Path(test_root / "expected_result.json").read_text(encoding="utf-8") + ) + # flush output from any previous tests + cpp_linter.Globals.OUTPUT = "" + cpp_linter.GlobalParser.format_advice = [] + cpp_linter.GlobalParser.tidy_notes = [] + cpp_linter.GlobalParser.tidy_advice = [] + + repo_root = TEST_REPO.sub("\\1", cpp_linter.Globals.FILES[0]["blob_url"]) + return_path = test_root / repo_root + if not return_path.exists(): + return_path.mkdir() + monkeypatch.chdir(str(return_path)) + verify_files_are_present() + + +def match_file_json(filename: str) -> Optional[Dict[str, Any]]: + """A helper function to match a given filename with a file's JSON object.""" + for file in cpp_linter.Globals.FILES: + if file["filename"] == filename: + return file + print("file", filename, "not found in expected_result.json") + return None + + +RECORD_FILE = re.compile(r".*file=(.*?),.*") +FORMAT_RECORD = re.compile(r"Run clang-format on ") +FORMAT_RECORD_LINES = re.compile(r".*\(lines (.*)\).*") +TIDY_RECORD = re.compile(r":\d+:\d+ \[.*\]::") +TIDY_RECORD_LINE = re.compile(r".*,line=(\d+).*") + + +@pytest.mark.parametrize( + "lines_changed_only", [0, 1, 2], ids=["all lines", "only diff", "only added"] +) +@pytest.mark.parametrize("style", ["file", "llvm", "google"]) +def test_format_annotations( + caplog: pytest.LogCaptureFixture, + lines_changed_only: int, + style: str, +): + """Test clang-format annotations.""" + capture_clang_tools_output( + version=CLANG_VERSION, + checks="-*", # disable clang-tidy output + style=style, + lines_changed_only=lines_changed_only, + database="", + repo_root="", + ) + assert "Output from `clang-tidy`" not in cpp_linter.Globals.OUTPUT + caplog.set_level(logging.INFO, logger=log_commander.name) + log_commander.propagate = True + make_annotations( + style=style, file_annotations=True, lines_changed_only=lines_changed_only + ) + for message in [r.message for r in caplog.records if r.levelno == logging.INFO]: + if FORMAT_RECORD.search(message) is not None: + lines = [ + int(l.strip()) + for l in FORMAT_RECORD_LINES.sub("\\1", message).split(",") + ] + file = match_file_json(RECORD_FILE.sub("\\1", message).replace("\\", "/")) + if file is None: + continue + ranges = cpp_linter.range_of_changed_lines(file, lines_changed_only) + if ranges: # an empty list if lines_changed_only == 0 + for line in lines: + assert line in ranges + else: + raise RuntimeWarning(f"unrecognized record: {message}") + + +@pytest.mark.parametrize( + "lines_changed_only", [0, 1, 2], ids=["all lines", "only diff", "only added"] +) +@pytest.mark.parametrize( + "checks", + [ + "", + "boost-*,bugprone-*,performance-*,readability-*,portability-*,modernize-*," + "clang-analyzer-*,cppcoreguidelines-*", + ], + ids=["config file", "action defaults"], +) +def test_tidy_annotations( + caplog: pytest.LogCaptureFixture, + lines_changed_only: int, + checks: str, +): + """Test clang-tidy annotations.""" + capture_clang_tools_output( + version=CLANG_VERSION, + checks=checks, + style="", # disable clang-format output + lines_changed_only=lines_changed_only, + database="", + repo_root="", + ) + assert "Run `clang-format` on the following files" not in cpp_linter.Globals.OUTPUT + caplog.set_level(logging.INFO, logger=log_commander.name) + log_commander.propagate = True + make_annotations( + style="", file_annotations=True, lines_changed_only=lines_changed_only + ) + for message in [r.message for r in caplog.records if r.levelno == logging.INFO]: + if TIDY_RECORD.search(message) is not None: + line = int(TIDY_RECORD_LINE.sub("\\1", message)) + file = match_file_json(RECORD_FILE.sub("\\1", message).replace("\\", "/")) + if file is None: + continue + ranges = cpp_linter.range_of_changed_lines(file, lines_changed_only) + if ranges: # an empty list if lines_changed_only == 0 + assert line in ranges + else: + raise RuntimeWarning(f"unrecognized record: {message}") + + +@pytest.mark.parametrize("lines_changed_only", [1, 2], ids=["only diff", "only added"]) +def test_diff_comment(lines_changed_only: int): + """Tests code that isn't actually used (yet) for posting + comments (not annotations) in the event's diff. + + Remember, diff comments should only focus on lines in the diff.""" + capture_clang_tools_output( + version=CLANG_VERSION, + checks="", + style="file", + lines_changed_only=lines_changed_only, + database="", + repo_root="", + ) + diff_comments = list_diff_comments(lines_changed_only) + # output = Path(__file__).parent / "diff_comments.json" + # output.write_text(json.dumps(diff_comments, indent=2), encoding="utf-8") + for comment in diff_comments: + file = match_file_json(cast(str, comment["path"])) + if file is None: + continue + ranges = cpp_linter.range_of_changed_lines(file, lines_changed_only) + assert comment["line"] in ranges diff --git a/tests/ignored_paths/.gitmodules b/tests/ignored_paths/.gitmodules new file mode 100644 index 00000000..3696a312 --- /dev/null +++ b/tests/ignored_paths/.gitmodules @@ -0,0 +1,12 @@ +[submodule "RF24"] + path = RF24 + url = https://github.com/nRF24/RF24.git +[submodule "RF24Network"] + path = RF24Network + url = https://github.com/nRF24/RF24Network.git +[submodule "RF24Mesh"] + path = RF24Mesh + url = https://github.com/nRF24/RF24Mesh.git +[submodule "pybind11"] + path = pybind11 + url = https://github.com/pybind/pybind11.git diff --git a/tests/ignored_paths/test_ignored_paths.py b/tests/ignored_paths/test_ignored_paths.py new file mode 100644 index 00000000..b27d3b8c --- /dev/null +++ b/tests/ignored_paths/test_ignored_paths.py @@ -0,0 +1,32 @@ +"""Tests that focus on the ``ignore`` option's parsing.""" +from pathlib import Path +from typing import List +import pytest +from cpp_linter.run import parse_ignore_option, is_file_in_list + + +@pytest.mark.parametrize( + "user_in,is_ignored,is_not_ignored,expected", + [ + ("src", "src", "src", [True, False]), + ("!src|./", "", "src", [True, True]), + ], +) +def test_ignore( + user_in: str, is_ignored: str, is_not_ignored: str, expected: List[bool] +): + """test ignoring of a specified path.""" + ignored, not_ignored = parse_ignore_option(user_in) + assert expected == [ + is_file_in_list(ignored, is_ignored, "ignored"), + is_file_in_list(not_ignored, is_not_ignored, "not ignored"), + ] + + +def test_ignore_submodule(monkeypatch: pytest.MonkeyPatch): + """test auto detection of submodules and ignore the paths appropriately.""" + monkeypatch.chdir(str(Path(__file__).parent)) + ignored, not_ignored = parse_ignore_option("!pybind11") + for ignored_submodule in ["RF24", "RF24Network", "RF24Mesh"]: + assert ignored_submodule in ignored + assert "pybind11" in not_ignored diff --git a/tests/test_cli_args.py b/tests/test_cli_args.py new file mode 100644 index 00000000..764cb6a9 --- /dev/null +++ b/tests/test_cli_args.py @@ -0,0 +1,75 @@ +"""Tests related parsing input from CLI arguments.""" +from typing import List, Union +import pytest +from cpp_linter.run import cli_arg_parser + + +class Args: + """A pseudo namespace declaration. Each attribute is initialized with the + corresponding CLI arg's default value.""" + + verbosity: int = 10 + database: str = "" + style: str = "llvm" + tidy_checks: str = ( + "boost-*,bugprone-*,performance-*,readability-*,portability-*,modernize-*," + "clang-analyzer-*,cppcoreguidelines-*" + ) + version: str = "" + extensions: List[str] = [ + "c", + "h", + "C", + "H", + "cpp", + "hpp", + "cc", + "hh", + "c++", + "h++", + "cxx", + "hxx", + ] + repo_root: str = "." + ignore: str = ".github" + lines_changed_only: int = 0 + files_changed_only: bool = False + thread_comments: bool = False + file_annotations: bool = True + + +def test_defaults(): + """test default values""" + args = cli_arg_parser.parse_args("") + for key in args.__dict__.keys(): + assert args.__dict__[key] == getattr(Args, key) + + +@pytest.mark.parametrize( + "arg_name,arg_value,attr_name,attr_value", + [ + ("verbosity", "20", "verbosity", 20), + ("database", "build", "database", "build"), + ("style", "file", "style", "file"), + ("tidy-checks", "-*", "tidy_checks", "-*"), + ("version", "14", "version", "14"), + ("extensions", ".cpp, .h", "extensions", ["cpp", "h"]), + ("extensions", "cxx,.hpp", "extensions", ["cxx", "hpp"]), + ("repo-root", "src", "repo_root", "src"), + ("ignore", "!src|", "ignore", "!src|"), + ("lines-changed-only", "True", "lines_changed_only", 1), + ("lines-changed-only", "stricT", "lines_changed_only", 2), + ("files-changed-only", "True", "files_changed_only", True), + ("thread-comments", "True", "thread_comments", True), + ("file-annotations", "False", "file_annotations", False), + ], +) +def test_arg_parser( + arg_name: str, + arg_value: str, + attr_name: str, + attr_value: Union[int, str, List[str], bool], +): + """parameterized test of specific args compared to their parsed value""" + args = cli_arg_parser.parse_args([f"--{arg_name}={arg_value}"]) + assert getattr(args, attr_name) == attr_value diff --git a/tests/test_misc.py b/tests/test_misc.py new file mode 100644 index 00000000..df624fb6 --- /dev/null +++ b/tests/test_misc.py @@ -0,0 +1,104 @@ +"""Tests that complete coverage that aren't prone to failure.""" +import logging +from pathlib import Path +from typing import List +import pytest +import requests +import cpp_linter +import cpp_linter.run +from cpp_linter import Globals, log_response_msg, get_line_cnt_from_cols +from cpp_linter.run import ( + log_commander, + start_log_group, + end_log_group, + set_exit_code, + list_source_files, + get_list_of_changed_files, +) + + +def test_exit_override(): + """Test exit code that indicates if action encountered lining errors.""" + assert 1 == set_exit_code(1) + + +def test_exit_implicit(): + """Test the exit code issued when a thread comment is to be made.""" + Globals.OUTPUT = "TEST" # fake content for a thread comment + assert 1 == set_exit_code() + + +# see https://github.com/pytest-dev/pytest/issues/5997 +def test_end_group(caplog: pytest.LogCaptureFixture): + """Test the output that concludes a group of runner logs.""" + caplog.set_level(logging.INFO, logger=log_commander.name) + log_commander.propagate = True + end_log_group() + messages = caplog.messages + assert "::endgroup::" in messages + + +# see https://github.com/pytest-dev/pytest/issues/5997 +def test_start_group(caplog: pytest.LogCaptureFixture): + """Test the output that begins a group of runner logs.""" + caplog.set_level(logging.INFO, logger=log_commander.name) + log_commander.propagate = True + start_log_group("TEST") + messages = caplog.messages + assert "::group::TEST" in messages + + +@pytest.mark.parametrize( + "url", + [ + ("https://api.github.com/users/cpp-linter/starred"), + pytest.param(("https://github.com/cpp-linter/repo"), marks=pytest.mark.xfail), + ], +) +def test_response_logs(url: str): + """Test the log output for a requests.response buffer.""" + Globals.response_buffer = requests.get(url) + assert log_response_msg() + + +@pytest.mark.parametrize( + "extensions", + [ + (["cpp", "hpp"]), + pytest.param(["cxx", "h"], marks=pytest.mark.xfail), + ], +) +def test_list_src_files( + monkeypatch: pytest.MonkeyPatch, + caplog: pytest.LogCaptureFixture, + extensions: List[str], +): + """List the source files in the demo folder of this repo.""" + Globals.FILES = [] + monkeypatch.chdir(Path(__file__).parent.parent.as_posix()) + caplog.set_level(logging.DEBUG, logger=cpp_linter.logger.name) + assert list_source_files(ext_list=extensions, ignored_paths=[], not_ignored=[]) + + +def test_get_changed_files(caplog: pytest.LogCaptureFixture): + """test getting a list of changed files for an event. + + This is expected to fail if a github token not supplied as an env var. + We don't need to supply one for this test because the tested code will + execute anyway. + """ + caplog.set_level(logging.DEBUG, logger=cpp_linter.logger.name) + cpp_linter.run.GITHUB_REPOSITORY = "cpp-linter/test-cpp-linter-action" + cpp_linter.run.GITHUB_SHA = "76adde5367196cd57da5bef49a4f09af6175fd3f" + cpp_linter.run.GITHUB_EVENT_NAME = "push" + get_list_of_changed_files() + # pylint: disable=no-member + assert Globals.FILES + # pylint: enable=no-member + + +@pytest.mark.parametrize("line,cols,offset", [(13, 5, 144), (19, 1, 189)]) +def test_file_offset_translation(line: int, cols: int, offset: int): + """Validate output from ``get_line_cnt_from_cols()``""" + test_file = str(Path("demo/demo.cpp").resolve()) + assert (line, cols) == get_line_cnt_from_cols(test_file, offset)