diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..3a047c5 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,34 @@ +name: CI + +on: + push: + branches: [master] + tags: ['v*.*.*'] + +jobs: + test: + name: Test + uses: daniil-berg/reusable-workflows/.github/workflows/python-test.yaml@v0.2.1 + with: + versions: '["3.9", "3.10", "3.11"]' + unittest-command: 'scripts/test.sh' + coverage-command: 'scripts/cov.sh' + unittest-requirements: "-e '.[dev]'" + typecheck-command: 'scripts/typecheck.sh' + typecheck-requirements: '-Ur requirements/dev.txt' + typecheck-all-versions: true + lint-command: 'scripts/lint.sh' + lint-requirements: '-Ur requirements/dev.txt' + + release: + name: Release + if: ${{ github.ref_type == 'tag' }} + needs: test + uses: daniil-berg/reusable-workflows/.github/workflows/python-release.yaml@v0.2.1 + with: + git-ref: ${{ github.ref_name }} + secrets: + release-token: ${{ secrets.TOKEN_GITHUB_CREATE_RELEASE }} + publish-token: ${{ secrets.TOKEN_PYPI_PROJECT }} + permissions: + contents: write diff --git a/scripts/ci.sh b/scripts/ci.sh new file mode 100755 index 0000000..4716704 --- /dev/null +++ b/scripts/ci.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# Runs full CI pipeline (test, typecheck, lint). + +typeset scripts_dir="$(dirname $(realpath $0))" + +source "${scripts_dir}/util.sh" + +"${scripts_dir}/test.sh" +"${scripts_dir}/typecheck.sh" +"${scripts_dir}/lint.sh" + +echo -e "${background_black}${bold_green}✅ 🎉 All checks passed!${color_reset}" diff --git a/scripts/cov.sh b/scripts/cov.sh new file mode 100755 index 0000000..10072a7 --- /dev/null +++ b/scripts/cov.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# Runs unit tests. +# If successful, prints only the coverage percentage. +# If an error occurs, prints the entire unit tests progress output. + +source "$(dirname $(realpath $0))/util.sh" + +coverage erase +run_and_capture coverage run +coverage report | awk '$1 == "TOTAL" {print $NF; exit}' diff --git a/scripts/lint.sh b/scripts/lint.sh index 7bf4a58..38855d1 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -1,16 +1,17 @@ #!/usr/bin/env bash -# Runs type checker and linters. +# Runs various linters. -# Ensure that we return to the current working directory -# and exit the script immediately in case of an error: -trap "cd $(realpath ${PWD}); exit 1" ERR -# Change into project root directory: -cd "$(dirname $(dirname $(realpath $0)))" - -echo 'Performing type checks...' -mypy -echo +source "$(dirname $(realpath $0))/util.sh" echo 'Linting source and test files...' -flake8 src/ tests/ -echo -e 'No issues found.' + +echo ' isort - consistent imports' +isort src/ tests/ --check-only + +echo ' ruff - extensive linting' +ruff src/ tests/ + +echo ' black - consistent style' +run_and_capture black src/ tests/ --check + +echo -e "${bold_green}No issues found${color_reset}\n" diff --git a/scripts/test.sh b/scripts/test.sh index a0d41fe..23e3945 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -1,17 +1,12 @@ #!/usr/bin/env bash -# Runs unit tests and prints only coverage percentage, if successful. -# If an error occurs, prints the entire unit tests progress output. +# Runs unit tests and reports coverage percentage. -# Ensure that we return to the current working directory in case of an error: -trap "cd $(realpath ${PWD})" ERR -# Change into project root directory: -cd "$(dirname $(dirname $(realpath $0)))" +source "$(dirname $(realpath $0))/util.sh" -coverage erase -# Capture the test progression in a variable: -typeset progress -progress=$(coverage run 2>&1) -# If tests failed or produced errors, write progress/messages to stderr and exit: -[[ $? -eq 0 ]] || { >&2 echo "${progress}"; exit 1; } -# Otherwise extract the total coverage percentage from the produced report and write it to stdout: -coverage report | awk '$1 == "TOTAL" {print $NF; exit}' +echo 'Running unit tests...' +coverage run +typeset percentage +typeset color +percentage="$(coverage report | awk '$1 == "TOTAL" {print $NF; exit}')" +[[ $percentage == "100%" ]] && color="${bold_green}" || color="${yellow}" +echo -e "${color}${percentage} coverage${color_reset}\n" diff --git a/scripts/typecheck.sh b/scripts/typecheck.sh new file mode 100755 index 0000000..d9d9bbd --- /dev/null +++ b/scripts/typecheck.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Runs type checker. + +source "$(dirname $(realpath $0))/util.sh" + +echo 'Performing type checks...' +mypy +echo diff --git a/scripts/util.sh b/scripts/util.sh new file mode 100644 index 0000000..bf1fd5c --- /dev/null +++ b/scripts/util.sh @@ -0,0 +1,20 @@ +run_and_capture() { + # Captures stderr of any command passed to it + # and releases it only if the command exits with a non-zero code. + typeset output + output=$($@ 2>&1) + typeset exit_status=$? + [[ $exit_status == 0 ]] || >&2 echo "${output}" + return $exit_status +} + +# Ensure that we return to the current working directory +# and exit the script immediately in case of an error: +trap "cd $(realpath ${PWD}); exit 1" ERR +# Change into project root directory: +cd "$(dirname $(dirname $(realpath $0)))" + +typeset background_black='\033[40m' +typeset bold_green='\033[1;92m' +typeset yellow='\033[0;33m' +typeset color_reset='\033[0m'