diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..ab147ec --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,67 @@ +name: Tests + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + + tests: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ "ubuntu-latest", "macos-latest" ] + python-version: [ "3.6", "3.7", "3.8", "3.9", "3.10" ] + fail-fast: false + + env: + OS: ${{ matrix.os }} + PYTHON: ${{ matrix.python-version }} + + defaults: + run: + shell: bash + + name: Python ${{ matrix.python-version }} on ${{ matrix.os }} + steps: + + - name: Acquire sources + uses: actions/checkout@v3 + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + architecture: x64 + cache: 'pip' + cache-dependency-path: 'setup.py' + + - name: Install prerequisites (Linux) + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt install --yes libsnmp-dev snmp-mibs-downloader + + - name: Install prerequisites (macOS) + if: matrix.os == 'macos-latest' + run: | + brew install net-snmp + + - name: Run linter + run: | + make lint + + - name: Run tests, with coverage + run: | + make test + + - name: Upload coverage results to Codecov + uses: codecov/codecov-action@v3 + with: + file: ./coverage.xml + flags: unittests + env_vars: OS,PYTHON + name: codecov-umbrella + fail_ci_if_error: false diff --git a/.gitignore b/.gitignore index 8468f27..d4d043a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .venv* __pycache__ *.egg-info +.coverage +coverage.xml diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f6bb000 --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +$(eval venv := .venv) +$(eval pip := $(venv)/bin/pip) +$(eval python := $(venv)/bin/python) +$(eval black := $(venv)/bin/black) +$(eval isort := $(venv)/bin/isort) +$(eval pytest := $(venv)/bin/pytest) +$(eval twine := $(venv)/bin/twine) +$(eval flake8 := $(venv)/bin/pflake8) +$(eval proselint := $(venv)/bin/proselint) + +setup-virtualenv: + @test -e $(python) || python3 -m venv $(venv) || python -m venv $(venv) + +format: setup-virtualenv + $(pip) install --requirement=requirements-utils.txt + $(black) . + $(isort) . + +lint: setup-virtualenv + $(pip) install --requirement=requirements-utils.txt + $(flake8) --exit-zero *.py + $(MAKE) proselint + +proselint: + $(proselint) *.md || true + +test: setup-virtualenv + $(pip) install --editable=.[test] + $(pytest) + +publish: setup-virtualenv + $(pip) install build twine + $(python) -m build + $(twine) upload --skip-existing --verbose dist/*{.tar.gz,.whl} diff --git a/README.md b/README.md index 719f74c..fed9e28 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,8 @@ -# check_synology [![Release](https://img.shields.io/github/release/wernerfred/check_synology.svg)](https://github.com/wernerfred/check_synology/releases) +# check_synology + +[![Tests](https://github.com/wernerfred/check_synology/actions/workflows/tests.yml/badge.svg)](https://github.com/wernerfred/check_synology/actions/workflows/tests.yml) +[![Code coverage](https://codecov.io/gh/wernerfred/check_synology/branch/master/graph/badge.svg)](https://codecov.io/gh/wernerfred/check_synology) +[![Release](https://img.shields.io/github/release/wernerfred/check_synology.svg)](https://github.com/wernerfred/check_synology/releases) ## About @@ -81,20 +85,18 @@ ln -s $(which check_synology) /usr/lib/nagios/plugins/check_synology ## Development -For setting up a development sandbox, you might want to follow this walkthrough. +For setting up a development sandbox and running the software tests, you might +want to follow this walkthrough. -Acquire sources: ```shell git clone https://github.com/wernerfred/check_synology cd check_synology +make test ``` -Install program in development mode into a Python virtual environment: -```shell -python3 -m venv .venv -source .venv/bin/activate -pip install --editable=. -``` +By running `make test`, a Python virtual environment will be created within the +`.venv` folder of your working tree. Use `source .venv/bin/activate` to +activate it. ## Contributors ✨ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5d74761 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,28 @@ +[tool.black] +line-length = 120 + +[tool.isort] +profile = "black" +line_length = 120 +multi_line_output = 3 + +[tool.flake8] +max-line-length = 120 + +[tool.pytest.ini_options] +minversion = "2.0" +addopts = "-rsfEX -p pytester --strict-markers --verbosity=3 --cov --cov-report=term-missing --cov-report=xml" +log_level = "DEBUG" +testpaths = ["testing"] +xfail_strict = true +markers = [ +] + +[tool.coverage.run] +omit = [ + "testing/*", +] + +[tool.coverage.report] +fail_under = 0 +show_missing = true diff --git a/requirements-utils.txt b/requirements-utils.txt new file mode 100644 index 0000000..15f02a6 --- /dev/null +++ b/requirements-utils.txt @@ -0,0 +1,5 @@ +black +isort +pyproject-flake8 +flake8<5 +proselint diff --git a/setup.py b/setup.py index abc930f..6e97c3f 100644 --- a/setup.py +++ b/setup.py @@ -21,20 +21,33 @@ def read(path): long_description=long_description, long_description_content_type="text/markdown", platforms=["any"], - license="AGPL-3.0", + license="MIT", keywords="synology, synology-diskstation, snmp, snmpv3, monitoring, monitoring-plugin, nagios, icinga2, icinga2-plugin", py_modules=["check_synology"], - entry_points={"console_scripts": ["check_synology = check_synology"]}, - python_requires=">=3.4", + entry_points={"console_scripts": ["check_synology = check_synology:__main__"]}, + python_requires=">=3.6", install_requires=["easysnmp>=0.2.6,<1"], + extras_require={ + "test": [ + "pytest<8", + "pytest-mock<4", + "pytest-cov<4", + ], + }, classifiers=[ "Development Status :: 4 - Beta", - "License :: OSI Approved :: GNU Affero General Public License v3", + "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: OS Independent", "Environment :: Console", "Programming Language :: Python", + "Programming Language :: Python", "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Programming Language :: Python :: Implementation :: CPython", "Intended Audience :: Developers", "Intended Audience :: Education", diff --git a/testing/test_cli.py b/testing/test_cli.py new file mode 100644 index 0000000..8055d34 --- /dev/null +++ b/testing/test_cli.py @@ -0,0 +1,40 @@ +import sys + + +def run_program(*args) -> int: + """ + Run `check_synology` program and return exit code. + + Support a variable number of command line options. + """ + sys.argv = ["check_synology", *args] + try: + import check_synology + except SystemExit as ex: + return ex.code % 256 + + +def test_no_options(capsys): + """ + Verify running the program without options croaks as expected. + """ + exitcode = run_program() + response = capsys.readouterr() + assert exitcode == 2 + assert response.out == "" + assert ( + "check_synology: error: the following arguments are required: " + "hostname, username, authkey, privkey, mode" in response.err + ) + + +def test_help(capsys): + """ + Verify running the program with the `--help` option works as expected. + """ + exitcode = run_program("--help") + response = capsys.readouterr() + assert exitcode == 0 + assert "the hostname" in response.out + assert "critical value for selected mode" in response.out + assert response.err == ""