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:

  1. Check out the package repository with actions/checkout@v2 action and install python of the specified version with actions/setup-python@v2 action;

  2. Installed build and runtime dependencies using apt/brew and pip;

  3. Compile and install the package;

  4. Check out the unit test repository with actions/checkout@v2 action;

  5. Install test dependencies with pip;

  6. Run unit tests with py.test and assess test coverage with coverage.

python-package.yml
# 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.

github-deploy.yml
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:

  1. 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"
  1. 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
  1. 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
  1. 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
  1. 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