Continuous Integration
Continuous integration is an essential part of the software’s development workflow, allowing the automation of multiple steps including code compilation, testing and deployment. This package utilizes several popular CI platforms to perform continuous integration: GitHub Actions and Travis-CI.
Unit tests
Our unit tests are written compatible with pytest module, which
provides straightforward test execution and error reporting as well as an easy solution to assess test
coverage.
pytest
follows the standard test discovery rules that can be found
here <https://docs.pytest.org/en/stable/goodpractices.html#conventions-for-python-test-discovery>_.
For simple tests, we just need to write some functions with prefix test
, while it is also possible
to write test classes with prefix Test
.
Additionally, pytest
also provides a variety of features including fixtures and parameterization to
facilitate unit testing.
CI Configuration for GitHub Actions
GitHub Actions is a continuous integration platform provided by GitHub with a variety of community built action
,
that can be easily used to execute tasks with minimum configuration.
For example, with our unit testing workflow, we uses the actions/checkout@v2
action to check out the repositories,
the actions/setup-python@v2
to setup python of a specific version.
At the moment we implemented 2 types of workflow to perform unit tests and deploy the package.
Unit Testing Workflow
In the unit testing workflow, our goal is to compile and unit test our package on both ubuntu
and macos
operating systems,
with both Python 3.8 and 3.9versions. The workflow is executed whenever a branch recieves a push or a pull request (excluding branches with prefix text
),
and the workflow can also be triggered manually for testing purposes.
The workflow consits of the following steps:
Check out the package repository with
actions/checkout@v2
action and install python of the specified version withactions/setup-python@v2
action;Installed build and runtime dependencies using
apt
/brew
andpip
;Compile and install the package;
Check out the unit test repository with
actions/checkout@v2
action;Install test dependencies with
pip
;Run unit tests with
py.test
and assess test coverage withcoverage
.
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: Build and Test Python Package
on:
push:
branhes-ignore:
- test**
pull_request:
branhes-ignore:
- test**
workflow_dispatch:
jobs:
build:
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.experimental }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
python-version: [3.8, 3.9]
experimental: [false]
# include:
# - os: macos-latest
# python-version: 3.9
# experimental: true
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
if [ "${RUNNER_OS}" == "Linux" ]; then
echo "OS is Linux"
sudo apt update
sudo apt install gcc gfortran libopenblas-dev libgomp1 libhdf5-dev make
elif [ "${RUNNER_OS}" == "macOS" ]; then
echo "OS is macOS"
brew install gcc openblas
else
echo "OS $RUNNER_OS not supported"
exit 1
fi
python -m pip install --upgrade pip
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Compile and install the package
run: |
if [ "${RUNNER_OS}" == "macOS" ]; then
export CC=gcc-11
export CXX=g++-11
export F90=gfortran-11
fi
python setup.py build
python setup.py install
# - name: Lint with flake8
# run: |
# python -m pip install flake8
# # stop the build if there are Python syntax errors or undefined names
# flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
# flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Checkout tests
uses: actions/checkout@v2
with:
repository: marianettigroup/principia-materia-tests
token: ${{ secrets.TEST_PAT }} # `GitHub_PAT` is a secret that contains your PAT
path: tests
- name: Test with pytest
run: |
python -m pip install -r tests/requirements.txt
cd tests/tests && make coverage
Deployment Workflow
The goal of the deployment workflow is the build the package and its extensions into python wheels for a variety of platforms for easy distribution.
This is usually a very cumbersome task.
However, with the cibuildwheel package and the pypa/cibuildwheel@v2.2.2
action provided by Python Packaging Authority (PyPA).
The wheels for a variety of platforms and python versions can be built with only a few lines of configuration.
name: Build and upload to PyPI
# Build on every branch push, tag push, and pull request change:
# on: [push, pull_request]
# Alternatively, to publish when a (published) GitHub Release is created, use the following:
on:
# push:
# pull_request:
release:
types:
- published
workflow_dispatch:
jobs:
build_wheels:
name: Build wheels on ${{ matrix.os }}
continue-on-error: ${{ matrix.experimental }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
# os: [ubuntu-latest, windows-latest, macos-latest]
experimental: [true]
python-version: [3.9]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
name: Install Python
with:
python-version: ${{ matrix.python-version }}
- name: Build wheels
uses: pypa/cibuildwheel@v2.2.2
- uses: actions/upload-artifact@v2
with:
path: ./wheelhouse/*.whl
build_sdist:
name: Build source distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
name: Install Python
with:
python-version: '3.9'
- name: Build sdist
run: |
pip install numpy
python setup.py sdist
- uses: actions/upload-artifact@v2
with:
path: dist/*.tar.gz
# upload_pypi:
# needs: [build_wheels, build_sdist]
# runs-on: ubuntu-latest
# # upload to PyPI on every tag starting with 'v'
# if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v')
# # alternatively, to publish when a GitHub Release is created, use the following rule:
# # if: github.event_name == 'release' && github.event.action == 'published'
# steps:
# - uses: actions/download-artifact@v2
# with:
# name: artifact
# path: dist
#
# - uses: pypa/gh-action-pypi-publish@v1.4.2
# with:
# user: __token__
# password: ${{ secrets.pypi_password }}
# # To test: repository_url: https://test.pypi.org/legacy/
CI Configuration for Travis-CI
Here we demonstrate the Configuration of CI workflow on Travis-CI of the package:
Set up the environment. Since the package mainly uses Python, the environment is fairly easy to setup. Folowing is an exmple for Travis-CI, that configures an linux environment with a matrix of 2 Python version: 3.8 and 3.9.
language: python
os:
- linux
cache: pip
virtualenv:
system_site_packages: false
python:
- "3.8"
- "3.9"
The next step is to install dependencies. Both dependencies for the extensions and the required Python libraries will be installed in this step.
# command to install dependencies
before_install:
- sudo apt-get update
- sudo apt-get install -y gcc libopenblas-dev libgomp1 make
- python -m pip install --ignore-installed -r requirements.txt
Then the package needs to be compiled and installed, in order to be tested.
# command to compile and install code
install: |
echo "Build Package"
python setup.py build
python setup.py install
After the package is successfully installed, the unit tests need to be checked out and executed. Tests coverage will be assessed in the meantime.
# command to run tests
script: |
echo "Test package"
git clone git@github.com:marianettigroup/principia-materia-tests.git
cd principia-materia-tests
python -m pip install coverage-badge
python -m pip install -r requirements.txt
cd tests && make
After the tests are successfully executed, one can compute the coverage statistics and generate coverage report and coverage badge. Meanwhile it can also be setup in a way when a new release is pushed to the git repository, the code will be compiled, packaged and uploaded to software repository platforms.
# command to create coverate report and badge
after_success: |
cd ${TRAVIS_BUILD_DIR}/principia-materia-tests/tests
coverage xml
coverage html
coverage-badge -o coverage.svg
CI for Documentation
The documentation website of the package is hosted with GitHub Pages and is built and deployed conveniently using GitHub Action.
The build process is similar to a regular python package with running sphinx-build
command at the end.
The peaceiris/actions-gh-pages@v3
action is used to deploy the built website to the gh-pages
branch of the marianettigroup/marianettigroup.github.io
repository, making it accessible from the URL marianettigroup.github.io.
# This workflow will build the sphinx documentation and deploy it to github pages
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: Sphinx documentation
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: [3.9]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y pandoc
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
- name: Checkout pm code repository
uses: actions/checkout@v2
with:
repository: marianettigroup/principia-materia
path: principia-materia
token: ${{ secrets.PERSONAL_TOKEN }}
- name: Install the package dependencies and build pm package for API documentation
run: |
sudo apt update
sudo apt install libblas-dev liblapack-dev libomp-dev gfortran make
cd principia-materia
pip install -U numpy
pip install -U -r requirements.txt
python setup.py build
python setup.py develop
cd ../
- name: Build documentation with sphinx
run: |
sphinx-build -M html "." "_build"
- name: Deploy to marianettigroup.github.io
uses: peaceiris/actions-gh-pages@v3
if: github.ref == 'refs/heads/master'
with:
personal_token: ${{ secrets.PERSONAL_TOKEN }}
external_repository: marianettigroup/marianettigroup.github.io
publish_dir: ./_build/html