diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..adffaee --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +[flake8] +ignore = E501,W503 +exclude = .git,__pycache__ \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 298df66..a1de3a8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,7 +6,7 @@ on: pull_request: jobs: test-check: - name: runner / (github-check) + name: runner / pytest (github-check) runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -19,7 +19,7 @@ jobs: test-pr-check: if: github.event_name == 'pull_request' - name: runner / (github-pr-check) + name: runner / pytest (github-pr-check) runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -33,7 +33,7 @@ jobs: test-pr-review: if: github.event_name == 'pull_request' - name: runner / (github-pr-review) + name: runner / pytest (github-pr-review) runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 diff --git a/.gitignore b/.gitignore index 34a39ac..92845f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,157 @@ +.report.json + # Folders to ignore -.vscode/ \ No newline at end of file +.vscode/ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/Dockerfile b/Dockerfile index c9acb95..d118557 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,24 @@ -FROM alpine:3.15 +FROM python:alpine ENV REVIEWDOG_VERSION=v0.13.0 +WORKDIR /src + SHELL ["/bin/ash", "-eo", "pipefail", "-c"] -# hadolint ignore=DL3006 -RUN apk --no-cache add git +# hadolint ignore=DL3006,DL3018 +RUN apk --no-cache add build-base libffi-dev git graphviz + +COPY pyproject.toml pyproject.toml +COPY poetry.lock poetry.lock -RUN wget -O - -q https://raw.githubusercontent.com/reviewdog/reviewdog/master/install.sh| sh -s -- -b /usr/local/bin/ ${REVIEWDOG_VERSION} +RUN wget -O - -q https://raw.githubusercontent.com/reviewdog/reviewdog/master/install.sh| sh -s -- -b /usr/local/bin ${REVIEWDOG_VERSION} -# TODO: Install a linter and/or change docker image as you need. -RUN wget -O - -q https://git.io/misspell | sh -s -- -b /usr/local/bin/ +# hadolint ignore=DL3013,SC2169,SC3001 +RUN python -m pip install --no-cache-dir --upgrade pip && \ + python -m pip install --no-cache-dir --upgrade poetry && \ + python -m pip install --no-cache-dir --requirement <(poetry export --format requirements.txt) -COPY entrypoint.sh /entrypoint.sh +COPY entrypoint.sh entrypoint.sh -ENTRYPOINT ["/entrypoint.sh"] +ENTRYPOINT ["./entrypoint.sh"] diff --git a/README.md b/README.md index b4bad24..7af2569 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,46 @@ jobs: level: warning ``` +## Inputs + +### `github_token` + +**Required**. The [GITHUB_TOKEN](https://docs.github.com/en/free-pro-team@latest/actions/reference/authentication-in-a-workflow). Must be in form of `github_token: ${{ secrets.github_token }}`. Defaults to `${{ github.token }}`. + +### `workdir` + +**Optional**. The directory to run pytest on. Defaults to `.`. + +#### `pytest_args` + +**Optional**. Command-line arguments to the pytest executable. Defaults to `""`. + +Please note that some command-line arguments can be defined with other fields in your configuration. You may combine the `pytest_args` setting with the other settings below, or use `pytest_args` to configure pytest without the other Action settings. + +#### `level` + +**Optional**. The log level of the reviewdog reporter. Options: `[info, warning, error]`. Defaults to `error`. + +#### `reporter` + +**Optional**. Reporter of reviewdog command `[github-pr-check, github-pr-review, github-check]`. Defaults to `github-pr-check`. + +#### `filter_mode` + +**Optional**. Filtering mode for the reviewdog command `[added, diff_context, file, nofilter]`. Defaults to `added`. + +#### `fail_on_error` + +**Optional**. Exit code for reviewdog when errors are found `[true, false]`. Defaults to `false`. + +#### `reviewdog_flags` + +**Optional**. Additional reviewdog flags. Defaults to `""`. + +#### `tool_name` + +**Optional**. Tool name to use for reviewdog reporter. Defaults to `pytest`. + ## Development ### Release diff --git a/action.yml b/action.yml index 9e7aa23..7387016 100644 --- a/action.yml +++ b/action.yml @@ -1,43 +1,81 @@ -name: 'TODO: Run with reviewdog' -description: 'TODO: 🐶 Run with reviewdog on pull requests to improve code review experience.' -author: 'TODO: ' +name: 'Run pytest with reviewdog' +description: '🐶 Run pytest with reviewdog on pull requests to improve code review experience.' +author: 'Ross Chadwick' inputs: + ### Generic flags ### github_token: description: 'GITHUB_TOKEN' default: '${{ github.token }}' + required: true workdir: description: 'Working directory relative to the root directory.' + required: false default: '.' ### Flags for reviewdog ### level: description: 'Report level for reviewdog [info,warning,error]' + required: false default: 'error' reporter: description: 'Reporter of reviewdog command [github-pr-check,github-pr-review].' + required: false default: 'github-pr-check' filter_mode: description: | Filtering mode for the reviewdog command [added,diff_context,file,nofilter]. Default is added. + required: false default: 'added' fail_on_error: description: | Exit code for reviewdog when errors are found [true,false] Default is `false`. + required: false default: 'false' + tool_name: + description: "Tool name to use for reviewdog reporter." + required: false + default: "pytest" reviewdog_flags: description: 'Additional reviewdog flags' + required: false default: '' - ### Flags for ### - locale: - description: '-locale flag of misspell. (US/UK)' - default: '' + ### Flags for pytest ### + pytest_coverage: + description: "Enables coverage reports output for pytest." + required: false + default: "" + pytest_profiling: + description: "Enables profiling reports output for pytest." + required: false + default: "" + pytest_profiling_visual: + description: "Enables visual profiling output for pytest. If both `pytest_profiling_visual` and `pytest_profiling` are true, visual mode takes precedence." + required: false + default: "" + pytest_args: + description: "Additional pytest arguments in standard command-line format, separated by spaces. If a custom argument conflicts with a pre-defined one, the custom argument will take precedence." + required: false + default: "" + runs: using: 'docker' image: 'Dockerfile' + env: + INPUT_WORKDIR: ${{ inputs.workdir }} + INPUT_PYTEST_CUSTOM_ARGS: ${{ inputs.pytest_args }} + INPUT_PYTEST_PROFILING: ${{ inputs.pytest_profiling }} + INPUT_PYTEST_PROFILING_VISUAL: ${{ inputs.pytest_profiling_visual }} + INPUT_PYTEST_COVERAGE: ${{ inputs.pytest_coverage }} + INPUT_GITHUB_TOKEN: ${{ inputs.github_token }} + INPUT_TOOL_NAME: ${{ inputs.tool_name }} + INPUT_LEVEL: ${{ inputs.level }} + INPUT_REPORTER: ${{ inputs.reporter }} + INPUT_FILTER_MODE: ${{ inputs.filter_mode }} + INPUT_FAIL_ON_ERROR: ${{ inputs.fail_on_error }} + INPUT_REVIEWDOG_FLAGS: ${{ inputs.reviewdog_flags }} # Ref: https://haya14busa.github.io/github-action-brandings/ -# TODO: update branding if you want. branding: icon: 'check' color: 'blue' diff --git a/entrypoint.sh b/entrypoint.sh index e3885ba..212e064 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,17 +1,60 @@ -#!/bin/sh -set -e +#!/bin/bash +set -eu -if [ -n "${GITHUB_WORKSPACE}" ] ; then +BASE_PATH="$(cd "$(dirname "$0")" && pwd)" + +if [ -n "${GITHUB_WORKSPACE}" ]; then cd "${GITHUB_WORKSPACE}/${INPUT_WORKDIR}" || exit fi export REVIEWDOG_GITHUB_API_TOKEN="${INPUT_GITHUB_TOKEN}" -misspell -locale="${INPUT_LOCALE}" . \ - | reviewdog -efm="%f:%l:%c: %m" \ - -name="linter-name (misspell)" \ - -reporter="${INPUT_REPORTER:-github-pr-check}" \ - -filter-mode="${INPUT_FILTER_MODE}" \ - -fail-on-error="${INPUT_FAIL_ON_ERROR}" \ - -level="${INPUT_LEVEL}" \ - ${INPUT_REVIEWDOG_FLAGS} +echo "[action-pytest] Pytest version:" +pytest --version + +pytestArgsArr=("--json-report" "--json-report-file=./.report.json") +IFS=' ' read -ra pytestArgsArr <<<"${INPUT_PYTEST_CUSTOM_ARGS}" + +# Check profiling arguments and take precedence with svg output, if true +if [ "${PYTEST_PROFILING_VISUAL}" == true ]; then + pytestArgsArr+=("--profile-svg") +elif [ "${PYTEST_PROFILING}" == true ]; then + pytestArgsArr+=("--profile") +fi + +# Check coverage argument +if [ "${PYTEST_COVERAGE}" == true ]; then + pytestArgsArr+=("--cov") +fi + +# remove any duplicates +# TODO: give priority to custom args +# TODO: make this split on value as well +IFS=" " read -r -a pytestArgsArr <<<"$(tr ' ' '\n' <<<"${pytestArgsArr[@]}" | sort -u | tr '\n' ' ')" + +pytestArgs=${pytestArgsArr[*]} +echo pytestArgs + +echo "[action-pytest] Running pytest on \"${INPUT_WORKDIR}\"..." +exit_val="0" +pytest "${pytestArgs}" "${INPUT_WORKDIR}" + +python "${BASE_PATH}/pytest_rdjson/pytest_rdjson.py" | + /tmp/reviewdog -f=rdjson \ + -name="${INPUT_TOOL_NAME}" \ + -reporter="${INPUT_REPORTER}" \ + -filter-mode="${INPUT_FILTER_MODE}" \ + -fail-on-error="${INPUT_FAIL_ON_ERROR}" \ + "${INPUT_REVIEWDOG_FLAGS}" || exit_val="$?" + +# TODO: handle SVG profiling image as reviewdog comment somehow +# if [ "${PYTEST_PROFILING_VISUAL}" == true ]; then + +# fi + +echo "[action-pytest] Clean up reviewdog..." +rm /tmp/reviewdog + +if [[ "${exit_val}" -ne "0" ]]; then + exit 1 +fi diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..8c5a52f --- /dev/null +++ b/poetry.lock @@ -0,0 +1,435 @@ +[[package]] +name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "21.4.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] + +[[package]] +name = "black" +version = "22.1.0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = ">=1.1.0" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "click" +version = "8.0.3" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "coverage" +version = "6.3.1" +description = "Code coverage measurement for Python" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "gprof2dot" +version = "2021.2.21" +description = "Generate a dot graph from the output of several profilers." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "pathspec" +version = "0.9.0" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[[package]] +name = "platformdirs" +version = "2.4.1" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pyparsing" +version = "3.0.7" +description = "Python parsing module" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pytest" +version = "6.2.5" +description = "pytest: simple powerful testing with Python" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +py = ">=1.8.2" +toml = "*" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "3.0.0" +description = "Pytest plugin for measuring coverage." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] + +[[package]] +name = "pytest-json-report" +version = "1.4.1" +description = "A pytest plugin to report test results as JSON files" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +pytest = ">=3.8.0" +pytest-metadata = "*" + +[[package]] +name = "pytest-metadata" +version = "1.11.0" +description = "pytest plugin for test session metadata" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.dependencies] +pytest = ">=2.9.0" + +[[package]] +name = "pytest-profiling" +version = "1.7.0" +description = "Profiling plugin for py.test" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +gprof2dot = "*" +pytest = "*" +six = "*" + +[package.extras] +tests = ["pytest-virtualenv"] + +[[package]] +name = "pytest-timeout" +version = "2.1.0" +description = "pytest plugin to abort hanging tests" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pytest = ">=5.0.0" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "2.0.0" +description = "A lil' TOML parser" +category = "main" +optional = false +python-versions = ">=3.7" + +[metadata] +lock-version = "1.1" +python-versions = "^3.10" +content-hash = "8710f8052fc5f5c5d3c1858e4eb02c0446c0164cc454b47c36e5918565e87219" + +[metadata.files] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, +] +black = [ + {file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"}, + {file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"}, + {file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"}, + {file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"}, + {file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"}, + {file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"}, + {file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"}, + {file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"}, + {file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"}, + {file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"}, + {file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"}, + {file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"}, + {file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"}, + {file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"}, + {file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"}, + {file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"}, + {file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"}, + {file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"}, + {file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"}, + {file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"}, + {file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"}, + {file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"}, + {file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"}, +] +click = [ + {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, + {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +coverage = [ + {file = "coverage-6.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeffd96882d8c06d31b65dddcf51db7c612547babc1c4c5db6a011abe9798525"}, + {file = "coverage-6.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:621f6ea7260ea2ffdaec64fe5cb521669984f567b66f62f81445221d4754df4c"}, + {file = "coverage-6.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84f2436d6742c01136dd940ee158bfc7cf5ced3da7e4c949662b8703b5cd8145"}, + {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de73fca6fb403dd72d4da517cfc49fcf791f74eee697d3219f6be29adf5af6ce"}, + {file = "coverage-6.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78fbb2be068a13a5d99dce9e1e7d168db880870f7bc73f876152130575bd6167"}, + {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f5a4551dfd09c3bd12fca8144d47fe7745275adf3229b7223c2f9e29a975ebda"}, + {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7bff3a98f63b47464480de1b5bdd80c8fade0ba2832c9381253c9b74c4153c27"}, + {file = "coverage-6.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a06c358f4aed05fa1099c39decc8022261bb07dfadc127c08cfbd1391b09689e"}, + {file = "coverage-6.3.1-cp310-cp310-win32.whl", hash = "sha256:9fff3ff052922cb99f9e52f63f985d4f7a54f6b94287463bc66b7cdf3eb41217"}, + {file = "coverage-6.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:276b13cc085474e482566c477c25ed66a097b44c6e77132f3304ac0b039f83eb"}, + {file = "coverage-6.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:56c4a409381ddd7bbff134e9756077860d4e8a583d310a6f38a2315b9ce301d0"}, + {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9eb494070aa060ceba6e4bbf44c1bc5fa97bfb883a0d9b0c9049415f9e944793"}, + {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e15d424b8153756b7c903bde6d4610be0c3daca3986173c18dd5c1a1625e4cd"}, + {file = "coverage-6.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d47a897c1e91f33f177c21de897267b38fbb45f2cd8e22a710bcef1df09ac1"}, + {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:25e73d4c81efa8ea3785274a2f7f3bfbbeccb6fcba2a0bdd3be9223371c37554"}, + {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:fac0bcc5b7e8169bffa87f0dcc24435446d329cbc2b5486d155c2e0f3b493ae1"}, + {file = "coverage-6.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:72128176fea72012063200b7b395ed8a57849282b207321124d7ff14e26988e8"}, + {file = "coverage-6.3.1-cp37-cp37m-win32.whl", hash = "sha256:1bc6d709939ff262fd1432f03f080c5042dc6508b6e0d3d20e61dd045456a1a0"}, + {file = "coverage-6.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:618eeba986cea7f621d8607ee378ecc8c2504b98b3fdc4952b30fe3578304687"}, + {file = "coverage-6.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ed164af5c9078596cfc40b078c3b337911190d3faeac830c3f1274f26b8320"}, + {file = "coverage-6.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:352c68e233409c31048a3725c446a9e48bbff36e39db92774d4f2380d630d8f8"}, + {file = "coverage-6.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:448d7bde7ceb6c69e08474c2ddbc5b4cd13c9e4aa4a717467f716b5fc938a734"}, + {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fde6b90889522c220dd56a670102ceef24955d994ff7af2cb786b4ba8fe11e4"}, + {file = "coverage-6.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e647a0be741edbb529a72644e999acb09f2ad60465f80757da183528941ff975"}, + {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a5cdc3adb4f8bb8d8f5e64c2e9e282bc12980ef055ec6da59db562ee9bdfefa"}, + {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2dd70a167843b4b4b2630c0c56f1b586fe965b4f8ac5da05b6690344fd065c6b"}, + {file = "coverage-6.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9ad0a117b8dc2061ce9461ea4c1b4799e55edceb236522c5b8f958ce9ed8fa9a"}, + {file = "coverage-6.3.1-cp38-cp38-win32.whl", hash = "sha256:e92c7a5f7d62edff50f60a045dc9542bf939758c95b2fcd686175dd10ce0ed10"}, + {file = "coverage-6.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:482fb42eea6164894ff82abbcf33d526362de5d1a7ed25af7ecbdddd28fc124f"}, + {file = "coverage-6.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c5b81fb37db76ebea79aa963b76d96ff854e7662921ce742293463635a87a78d"}, + {file = "coverage-6.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a4f923b9ab265136e57cc14794a15b9dcea07a9c578609cd5dbbfff28a0d15e6"}, + {file = "coverage-6.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d296cbc8254a7dffdd7bcc2eb70be5a233aae7c01856d2d936f5ac4e8ac1f1"}, + {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1245ab82e8554fa88c4b2ab1e098ae051faac5af829efdcf2ce6b34dccd5567c"}, + {file = "coverage-6.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f2b05757c92ad96b33dbf8e8ec8d4ccb9af6ae3c9e9bd141c7cc44d20c6bcba"}, + {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9e3dd806f34de38d4c01416344e98eab2437ac450b3ae39c62a0ede2f8b5e4ed"}, + {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d651fde74a4d3122e5562705824507e2f5b2d3d57557f1916c4b27635f8fbe3f"}, + {file = "coverage-6.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:704f89b87c4f4737da2860695a18c852b78ec7279b24eedacab10b29067d3a38"}, + {file = "coverage-6.3.1-cp39-cp39-win32.whl", hash = "sha256:2aed4761809640f02e44e16b8b32c1a5dee5e80ea30a0ff0912158bde9c501f2"}, + {file = "coverage-6.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:9976fb0a5709988778ac9bc44f3d50fccd989987876dfd7716dee28beed0a9fa"}, + {file = "coverage-6.3.1-pp36.pp37.pp38-none-any.whl", hash = "sha256:463e52616ea687fd323888e86bf25e864a3cc6335a043fad6bbb037dbf49bbe2"}, + {file = "coverage-6.3.1.tar.gz", hash = "sha256:6c3f6158b02ac403868eea390930ae64e9a9a2a5bbfafefbb920d29258d9f2f8"}, +] +gprof2dot = [ + {file = "gprof2dot-2021.2.21.tar.gz", hash = "sha256:1223189383b53dcc8ecfd45787ac48c0ed7b4dbc16ee8b88695d053eea1acabf"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] +pathspec = [ + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, +] +platformdirs = [ + {file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"}, + {file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"}, +] +pluggy = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] +py = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] +pyparsing = [ + {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, + {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, +] +pytest = [ + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, +] +pytest-cov = [ + {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, + {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, +] +pytest-json-report = [ + {file = "pytest-json-report-1.4.1.tar.gz", hash = "sha256:87206752e1d9c6c86e8fdc66b34b7a452ad82766a621b263cd5e828d19caee55"}, + {file = "pytest_json_report-1.4.1-py3-none-any.whl", hash = "sha256:722425e8910f62079ed0cf039581e70e042fcba0ca8f91094df2ef2239d3f8de"}, +] +pytest-metadata = [ + {file = "pytest-metadata-1.11.0.tar.gz", hash = "sha256:71b506d49d34e539cc3cfdb7ce2c5f072bea5c953320002c95968e0238f8ecf1"}, + {file = "pytest_metadata-1.11.0-py2.py3-none-any.whl", hash = "sha256:576055b8336dd4a9006dd2a47615f76f2f8c30ab12b1b1c039d99e834583523f"}, +] +pytest-profiling = [ + {file = "pytest-profiling-1.7.0.tar.gz", hash = "sha256:93938f147662225d2b8bd5af89587b979652426a8a6ffd7e73ec4a23e24b7f29"}, + {file = "pytest_profiling-1.7.0-py2.7.egg", hash = "sha256:3b255f9db36cb2dd7536a8e7e294c612c0be7f7850a7d30754878e4315d56600"}, + {file = "pytest_profiling-1.7.0-py2.py3-none-any.whl", hash = "sha256:999cc9ac94f2e528e3f5d43465da277429984a1c237ae9818f8cfd0b06acb019"}, + {file = "pytest_profiling-1.7.0-py3.6.egg", hash = "sha256:6bce4e2edc04409d2f3158c16750fab8074f62d404cc38eeb075dff7fcbb996c"}, +] +pytest-timeout = [ + {file = "pytest-timeout-2.1.0.tar.gz", hash = "sha256:c07ca07404c612f8abbe22294b23c368e2e5104b521c1790195561f37e1ac3d9"}, + {file = "pytest_timeout-2.1.0-py3-none-any.whl", hash = "sha256:f6f50101443ce70ad325ceb4473c4255e9d74e3c7cd0ef827309dfa4c0d975c6"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +tomli = [ + {file = "tomli-2.0.0-py3-none-any.whl", hash = "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"}, + {file = "tomli-2.0.0.tar.gz", hash = "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..96f1a15 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,39 @@ +[tool.poetry] +name = "action-pytest" +version = "0.1.0" +description = "Run pytest with Reviewdog :dog:" +authors = ["Ross "] +license = "MIT" + +[tool.poetry.dependencies] +python = "^3.10" +pytest = "^6.2.5" +pytest-cov = "^3.0.0" +pytest-profiling = "^1.7.0" +pytest-timeout = "^2.1.0" +pytest-json-report = "^1.4.1" + +[tool.poetry.dev-dependencies] +black = "^22.1.0" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.black] +# Use the more relaxed max line length permitted in PEP8. +line-length = 99 +exclude = ''' +/( + \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | typings + | build + | dist + | migrations +)/ +''' \ No newline at end of file diff --git a/testdata/.gitkeep b/pytest_rdjson/__init__.py similarity index 100% rename from testdata/.gitkeep rename to pytest_rdjson/__init__.py diff --git a/pytest_rdjson/expected_rdjson.json b/pytest_rdjson/expected_rdjson.json new file mode 100644 index 0000000..38b1ae9 --- /dev/null +++ b/pytest_rdjson/expected_rdjson.json @@ -0,0 +1,43 @@ +{ + "source": { + "name": "pytest", + "url": "https://github.com/pytest-dev/pytest" + }, + "severity": "WARNING", + "diagnostics": [ + { + "message": "Traceback:\n\t- Location: [testdata/fail/test_fail.py](testdata/fail/test_fail.py#L18)\n- Line: 18\n- Message:AssertionError", + "severity": "ERROR", + "location": { + "path": "/home/marvin/git/github/reviewdog/action-pytest/testdata/fail/test_fail.py", + "range": { + "start": { + "line": 18, + "column": 1 + }, + "end": { + "line": 18, + "column": 1 + } + } + } + }, + { + "message": "Traceback:\n\t- Location: [testdata/fail/test_fail.py](testdata/fail/test_fail.py#L25)\n- Line: 25\n\t- Location: [testdata/fail/fail.py](testdata/fail/fail.py#L29)\n- Line: 29\n- Message:in fail_recursive_fib\n\t- Location: [testdata/fail/fail.py](testdata/fail/fail.py#L29)\n- Line: 29\n- Message:in fail_recursive_fib\n\t- Location: [testdata/fail/fail.py](testdata/fail/fail.py#L29)\n- Line: 29\n- Message:in fail_recursive_fib\n\t- Location: [testdata/fail/fail.py](testdata/fail/fail.py#L29)\n- Line: 29\n- Message:in fail_recursive_fib\n\t- Location: [testdata/fail/fail.py](testdata/fail/fail.py#L29)\n- Line: 29\n- Message:in fail_recursive_fib\n\t- Location: [testdata/fail/fail.py](testdata/fail/fail.py#L24)\n- Line: 24\n- Message:FibException", + "severity": "ERROR", + "location": { + "path": "/home/marvin/git/github/reviewdog/action-pytest/testdata/fail/fail.py", + "range": { + "start": { + "line": 24, + "column": 1 + }, + "end": { + "line": 24, + "column": 1 + } + } + } + } + ] +} \ No newline at end of file diff --git a/pytest_rdjson/pytest_rdjson.py b/pytest_rdjson/pytest_rdjson.py new file mode 100644 index 0000000..f030288 --- /dev/null +++ b/pytest_rdjson/pytest_rdjson.py @@ -0,0 +1,106 @@ +"""Convert pytest report json to rdjson for reviewdog.""" + +import json +from typing import Any + +PYTEST_SEVERITY_ERROR = "ERROR" +PYTEST_SEVERITY_WARNING = "WARNING" + +TEST_OUTCOME_FAILED = "failed" + + +def _get_rdjson_entry( + message: str, + severity: str, + path: str, + line_start: int, + line_end: int, + col_start: int = 1, + col_end: int = 1, +) -> dict[str, Any]: + return { + "message": message, + "severity": severity, + "location": { + "path": path, + "range": { + "start": { + "line": line_start, + "column": col_start, + }, + "end": { + "line": line_end, + "column": col_end, + }, + }, + }, + } + + +def pytest_to_rdjson(file_path: str) -> str: + """Convert the given file_path pytest report JSON file, to rdjson for reviewdog.""" + rdjson: dict[str, str | Any] = { + "source": {"name": "pytest", "url": "https://github.com/pytest-dev/pytest"}, + "severity": PYTEST_SEVERITY_WARNING, + "diagnostics": None, + } + rdjson["diagnostics"] = [] + + with open(file_path) as f: + pytest_report = json.load(f) + + # Construct tests output + if "tests" in pytest_report: + for test_result in pytest_report["tests"]: + test_result_outcome = test_result["outcome"] + if test_result_outcome != TEST_OUTCOME_FAILED: + continue + + crash_report = test_result["call"]["crash"] + crash_path = crash_report["path"] + crash_line = crash_report["lineno"] + + # construct a message using markdown, to display the traceback + test_message = "Traceback:" + traceback = test_result["call"]["traceback"] + for trace in traceback: + trace_path = trace["path"] + trace_line = trace["lineno"] + trace_message = trace["message"] + + traceback_message = ( + f"""\n\t- Location: [{trace_path}]({trace_path}#L{trace_line})\n""" + f"""- Line: {trace_line}""" + ) + + if trace_message != "": + traceback_message += f"\n- Message:{trace_message}" + + test_message += traceback_message + + entry = _get_rdjson_entry( + test_message, PYTEST_SEVERITY_ERROR, crash_path, crash_line, crash_line + ) + rdjson["diagnostics"].append(entry) + + # Construct warnings output + if "warnings" in pytest_report: + for warning in pytest_report["warnings"]: + warning_message = warning["message"] + warning_path = warning["filename"] + warning_line = warning["lineno"] + warning_category = warning["category"] + + if warning_category != "": + warning_message = f"{warning_category}: {warning_message}" + + entry = _get_rdjson_entry( + warning_message, + PYTEST_SEVERITY_WARNING, + warning_path, + warning_line, + warning_line, + ) + rdjson["diagnostics"].append(entry) + + return json.dumps(rdjson) diff --git a/pytest_rdjson/test_pytest_rdjson.py b/pytest_rdjson/test_pytest_rdjson.py new file mode 100644 index 0000000..244c379 --- /dev/null +++ b/pytest_rdjson/test_pytest_rdjson.py @@ -0,0 +1,73 @@ +"""Testing pytest to rdjson with pytest (things are getting pretty circular).""" + +import pytest +import json +from typing import Any + +from .pytest_rdjson import pytest_to_rdjson + + +def ordered(obj): + # credit to https://stackoverflow.com/a/25851972 + + if isinstance(obj, dict): + return sorted((k, ordered(v)) for k, v in obj.items()) + if isinstance(obj, list): + return sorted(ordered(x) for x in obj) + else: + return obj + + +def remove_paths(rdjson_obj: dict[str, Any]) -> dict[str, Any]: + for i in range(len(rdjson_obj["diagnostics"])): + del rdjson_obj["diagnostics"][i]["location"]["path"] + + return rdjson_obj + + +class TestPytestRdjson: + """Tests for pytest to rdjson.""" + + @pytest.mark.timeout(10) + def test_pytest_to_rdjson(self): + """Test that pytest_to_rdjson gives the expected output.""" + pytest_json_filepath = "./.report.json" + rdjson_filepath = "./pytest_rdjson/expected_rdjson.json" + + with open(rdjson_filepath, "r") as f: + rdjson_exp = f.read() + + rdjson_exp_obj = json.loads(rdjson_exp) + + pytest_args = [ + "--json-report", + "./testdata", + ] + exit_code = pytest.main(pytest_args) + assert exit_code == 1 # testdata tests should fail + + rdjson_out = pytest_to_rdjson(pytest_json_filepath) + rdjson_out_obj = json.loads(rdjson_out) + + # remove the "path" key/value from each object, since these will never be constants + rdjson_exp_obj = remove_paths(rdjson_exp_obj) + rdjson_out_obj = remove_paths(rdjson_out_obj) + + # # uncomment this to get a new rdjson file + # with open("./out.json", "w") as f: + # json.dump(rdjson_out_obj, f) + + difference_out = { + k: rdjson_out_obj[k] + for k in rdjson_out_obj + if k in rdjson_exp_obj and rdjson_out_obj[k] != rdjson_exp_obj[k] + } + + difference_exp = { + k: rdjson_exp_obj[k] + for k in rdjson_out_obj + if k in rdjson_exp_obj and rdjson_out_obj[k] != rdjson_exp_obj[k] + } + + # TODO: Figure out why this is False still + assert ordered(rdjson_out_obj) == ordered(rdjson_exp_obj) diff --git a/testdata/fail/__init__.py b/testdata/fail/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/testdata/fail/fail.py b/testdata/fail/fail.py new file mode 100644 index 0000000..9a22e89 --- /dev/null +++ b/testdata/fail/fail.py @@ -0,0 +1,29 @@ +"""Very bad, much fail.""" +import random + + +class FibException(Exception): + """Fib exception.""" + + pass + + +def fail_sum(value_a: int, value_b: int) -> int: + """Return the sum of value_a and value_b.""" + return value_a + value_b - random.randint(1, value_a + value_b) + + +def fail_recursive_fib(n: int) -> int: + """ + Return the value of the fibonacci sequence at n. + + Very complicated, may break randomly with FibException, + as if Fibonacci is just flipping a coin? + """ + if random.random() < 0.5: + raise FibException("too complicated") + + if n <= 1: + return n + else: + return fail_recursive_fib(n - 1) + fail_recursive_fib(n - 2) diff --git a/testdata/fail/test_fail.py b/testdata/fail/test_fail.py new file mode 100644 index 0000000..b72d1de --- /dev/null +++ b/testdata/fail/test_fail.py @@ -0,0 +1,27 @@ +"""Fail tests.""" + +import pytest + +from . import fail + + +class TestFail: + """Tests for the fails.""" + + @pytest.mark.doesnotexist(99) + def test_fail_sum(self): + """Test fail_sum.""" + a = 5 + b = 4 + fail_sum = fail.fail_sum(a, b) + + assert fail_sum == a + b + + @pytest.mark.timeout(5) + def test_fail_recursive_fib(self): + """Test fail_recursive_fib.""" + n = 20 + + fib = fail.fail_recursive_fib(n) + + assert fib == 6765