Repository: QuTech-Delft/QMI (language: python)

Issue:
Add CI workflows into the .github directory
Description
-------------

Add the CI pipeline into the repo. This should go under a new folder in `.github` directory called `workflows` with the name `github-ci.yml`. This could be copied over from our GitLab QMI project with all the GitLab-specific parts either adjusted for GitHub or removed. For example, the `twine` username and password for installing `wheel` and creating the package should be figured out. Consult @Fer to figure out how this should be done as it also reflects to creating the package in the TBD PyPi project page.

Make sure that the `scripts` directory with the relevant run scripts is present before running the pipeline for the first time. Check and upload those with this ticket if applicable.

Files to be added
-------------------
- `.github\workflows\github-ci.yml`


Add CI workflows into the .github directory
Description
-------------

Add the CI pipeline into the repo. This should go under a new folder in `.github` directory called `workflows` with the name `github-ci.yml`. This could be copied over from our GitLab QMI project with all the GitLab-specific parts either adjusted for GitHub or removed. For example, the `twine` username and password for installing `wheel` and creating the package should be figured out. Consult @Fer to figure out how this should be done as it also reflects to creating the package in the TBD PyPi project page.

Make sure that the `scripts` directory with the relevant run scripts is present before running the pipeline for the first time. Check and upload those with this ticket if applicable.

Files to be added
-------------------
- `.github\workflows\github-ci.yml`


Add CI workflows into the .github directory
Description
-------------

Add the CI pipeline into the repo. This should go under a new folder in `.github` directory called `workflows` with the name `github-ci.yml`. This could be copied over from our GitLab QMI project with all the GitLab-specific parts either adjusted for GitHub or removed. For example, the `twine` username and password for installing `wheel` and creating the package should be figured out. Consult @Fer to figure out how this should be done as it also reflects to creating the package in the TBD PyPi project page.

Make sure that the `scripts` directory with the relevant run scripts is present before running the pipeline for the first time. Check and upload those with this ticket if applicable.

Files to be added
-------------------
- `.github\workflows\github-ci.yml`


Add CI workflows into the .github directory
Description
-------------

Add the CI pipeline into the repo. This should go under a new folder in `.github` directory called `workflows` with the name `github-ci.yml`. This could be copied over from our GitLab QMI project with all the GitLab-specific parts either adjusted for GitHub or removed. For example, the `twine` username and password for installing `wheel` and creating the package should be figured out. Consult @Fer to figure out how this should be done as it also reflects to creating the package in the TBD PyPi project page.

Make sure that the `scripts` directory with the relevant run scripts is present before running the pipeline for the first time. Check and upload those with this ticket if applicable.

Files to be added
-------------------
- `.github\workflows\github-ci.yml`


Add CI workflows into the .github directory
Description
-------------

Add the CI pipeline into the repo. This should go under a new folder in `.github` directory called `workflows` with the name `github-ci.yml`. This could be copied over from our GitLab QMI project with all the GitLab-specific parts either adjusted for GitHub or removed. For example, the `twine` username and password for installing `wheel` and creating the package should be figured out. Consult @Fer to figure out how this should be done as it also reflects to creating the package in the TBD PyPi project page.

Make sure that the `scripts` directory with the relevant run scripts is present before running the pipeline for the first time. Check and upload those with this ticket if applicable.

Files to be added
-------------------
- `.github\workflows\github-ci.yml`


Add CI workflows into the .github directory
Description
-------------

Add the CI pipeline into the repo. This should go under a new folder in `.github` directory called `workflows` with the name `github-ci.yml`. This could be copied over from our GitLab QMI project with all the GitLab-specific parts either adjusted for GitHub or removed. For example, the `twine` username and password for installing `wheel` and creating the package should be figured out. Consult @Fer to figure out how this should be done as it also reflects to creating the package in the TBD PyPi project page.

Make sure that the `scripts` directory with the relevant run scripts is present before running the pipeline for the first time. Check and upload those with this ticket if applicable.

Files to be added
-------------------
- `.github\workflows\github-ci.yml`




Discussion / hints:

Fix patch:
diff --git a/.github/requirements-ci.txt b/.github/requirements-ci.txt
new file mode 100644
index 00000000..b7240b5d
--- /dev/null
+++ b/.github/requirements-ci.txt
@@ -0,0 +1,20 @@
+# For generating an installable package and deploying
+setuptools
+wheel
+twine
+# For scientific data processing and visualisation
+numpy
+scipy
+h5py>=3.7.0
+matplotlib
+# For hardware interfacing
+pyserial
+pyusb
+python-vxi11
+# For miscellaneous functionality
+pytz
+psutil
+colorama
+jsonschema
+# Instrument AD2
+pydwf
\ No newline at end of file
diff --git a/.github/workflows/github-ci.yml b/.github/workflows/github-ci.yml
new file mode 100644
index 00000000..83765c45
--- /dev/null
+++ b/.github/workflows/github-ci.yml
@@ -0,0 +1,117 @@
+name: QMI Python CI runner
+
+on:
+  push:
+    branches: [ "main" ]
+
+env:
+  # minimum pylint code quality score (max. 10)
+  PYLINT_MIN_SCORE: "9.00"
+  # minimum code coverage (goal: 80%; max. 100%)
+  COVERAGE_MIN_PERC: "68"
+  # maximum code complexity (goal: <= 15; unbounded)
+  COMPLEXITY_MAX_SCORE: "38"
+  # minimum maintainability index (goal: >=20; max. 100)
+  MAINTAINABILITY_MIN_INDEX: "10.62"
+  SOURCE_DIRS: "qmi/"
+
+jobs:
+  on_push:
+
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v3
+      - name: Set up Python-latest
+        uses: actions/setup-python@v4
+        with:
+          # Take latest Python 3 version
+          python-version: "3.x"
+      - name: Install dependencies
+        run: |
+          sudo apt-get update -qy
+          sudo apt-get install -y bc
+          python3 --version
+          pip install --upgrade pip
+          pip install -e '.[dev]'
+      - name: pylint
+        run: |
+          pip install pylint
+          chmod a+x scripts/run_analysis_pylint.sh 
+          SCORE=$( scripts/run_analysis_pylint.sh )
+          echo $SCORE $PYLINT_MIN_SCORE
+          echo $SCORE > pylint_score
+          # bc evaluates to `1` if relation is true
+          exit $( echo "$SCORE < $PYLINT_MIN_SCORE" | bc )
+        continue-on-error: true
+      - name: upload pylint test results
+        uses: actions/upload-artifact@v3
+        with:
+          name: pylint-results
+          path: pylint.log
+        if: ${{ always() }}
+      - name: mypy
+        run: |
+          pip install mypy
+          mypy --namespace-packages $SOURCE_DIRS | tee mypy.log
+        continue-on-error: true
+      - name: upload mypy test results
+        uses: actions/upload-artifact@v3
+        with:
+          name: mypy-results
+          path: mypy.log
+        if: ${{ always() }}
+      - name: radon-cc
+        run: |
+          pip install radon
+          chmod a+x scripts/run_analysis_cc.sh
+          MAX_SCORE=$( scripts/run_analysis_cc.sh $SOURCE_DIRS )
+          echo $MAX_SCORE $COMPLEXITY_MAX_SCORE
+          echo $MAX_SCORE > cc_max_score
+          # bc evaluates to `1` if relation is true
+          exit $( echo "$MAX_SCORE > $COMPLEXITY_MAX_SCORE" | bc )
+        continue-on-error: true
+      - name: upload radon-cc results
+        uses: actions/upload-artifact@v3
+        with:
+          name: radon-cc-results
+          path: radon-cc.log
+        if: ${{ always() }}
+      - name: radon-mi
+        run: |
+          pip install radon
+          chmod a+x scripts/run_analysis_mi.sh
+          MIN_INDEX=$( scripts/run_analysis_mi.sh $SOURCE_DIRS )
+          echo $MIN_INDEX $MAINTAINABILITY_MIN_INDEX
+          echo $MIN_INDEX > mi_min_index
+          exit $( echo "$MIN_INDEX < $MAINTAINABILITY_MIN_INDEX" | bc )  # bc evaluates to `1` if relation is true
+        continue-on-error: true
+      - name: upload radon-mi results
+        uses: actions/upload-artifact@v3
+        with:
+          name: radon-mi-results
+          path: radon-mi.log
+        if: ${{ always() }}
+      - name: coverage
+        run: |
+          pip install coverage
+          chmod a+x scripts/run_unit_tests_with_coverage.sh
+          scripts/run_unit_tests_with_coverage.sh $SOURCE_DIRS
+          coverage report --show-missing --fail-under=$COVERAGE_MIN_PERC | tee coverage.log
+          # coverage: '/^TOTAL.+?(\d+\%)$/'
+      - name: upload coverage results
+        uses: actions/upload-artifact@v3
+        with:
+          name: coverage-results
+          path: coverage.log
+        if: ${{ always() }}
+      - name: unit-tests latest
+        run: |
+          pip install unittest-xml-reporting
+          python -m xmlrunner --output-file testresults.xml discover --start-directory=tests --pattern="test_*.py"
+      - name: upload unit-tests results
+        uses: actions/upload-artifact@v3
+        with:
+          name: unit-test-results
+          path: testresults.xml
+        if: ${{ always() }}
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
new file mode 100644
index 00000000..2d4036ba
--- /dev/null
+++ b/.github/workflows/pull_request.yml
@@ -0,0 +1,152 @@
+name: QMI Python CI full test runner on pull request
+
+on:
+  pull_request:
+    branches: [ "main" ]
+
+env:
+  # minimum pylint code quality score (max. 10)
+  PYLINT_MIN_SCORE: "9.00"
+  # minimum code coverage (goal: 80%; max. 100%)
+  COVERAGE_MIN_PERC: "68"
+  # maximum code complexity (goal: <= 15; unbounded)
+  COMPLEXITY_MAX_SCORE: "38"
+  # minimum maintainability index (goal: >=20; max. 100)
+  MAINTAINABILITY_MIN_INDEX: "10.62"
+  SOURCE_DIRS: "qmi/"
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+    strategy:
+      max-parallel: 1
+      matrix:
+        python-version: ["3.8", "3.9", "3.10", "3.11"]
+    steps:
+      - uses: actions/checkout@v3
+      - name: Set up Python-latest
+        uses: actions/setup-python@v4
+        with:
+          python-version: ${{ matrix.python-version }}
+      - name: Install dependencies
+        run: |
+          sudo apt-get update -qy
+          sudo apt-get install -y bc
+          python3 --version
+          pip install --upgrade pip
+          pip install -e '.[dev]'
+          pip install anybadge
+      - name: pylint
+        run: |
+          pip install pylint
+          chmod a+x scripts/run_analysis_pylint.sh
+          SCORE=$( scripts/run_analysis_pylint.sh )
+          echo $SCORE $PYLINT_MIN_SCORE
+          echo $SCORE > pylint_score
+          # bc evaluates to `1` if relation is true
+          exit $( echo "$SCORE < $PYLINT_MIN_SCORE" | bc )
+          SCORE=$( cat pylint_score )
+          anybadge -l pylint -v $SCORE -f pylint.svg 2=red 4=orange 8=yellow 10=green
+        continue-on-error: true
+      - name: upload pylint test results
+        uses: actions/upload-artifact@v3
+        with:
+          name: pylint-results
+          path: pylint.log
+        if: ${{ always() }}
+      - name: upload pylint test badge
+        uses: actions/upload-artifact@v3
+        with:
+          name: pylint-badge
+          path: pylint.svg
+        if: ${{ always() }}
+      - name: mypy
+        run: |
+          pip install mypy
+          mypy --namespace-packages $SOURCE_DIRS | tee mypy.log
+          if [ -n "$( tail -n 1 mypy.log | grep -e '^Succes' )" ]; then RESULT="passed"; else RESULT="failed"; fi
+          anybadge -l mypy -v $RESULT -f mypy.svg failed=red passed=green
+        continue-on-error: true
+      - name: upload mypy test results
+        uses: actions/upload-artifact@v3
+        with:
+          name: mypy-results
+          path: mypy.log
+        if: ${{ always() }}
+      - name: upload mypy test badge
+        uses: actions/upload-artifact@v3
+        with:
+          name: mypy-badge
+          path: mypy.svg
+        if: ${{ always() }}
+      - name: radon-cc
+        run: |
+          pip install radon
+          chmod a+x scripts/run_analysis_cc.sh
+          MAX_SCORE=$( scripts/run_analysis_cc.sh $SOURCE_DIRS )
+          echo $MAX_SCORE $COMPLEXITY_MAX_SCORE
+          echo $MAX_SCORE > cc_max_score
+          # bc evaluates to `1` if relation is true
+          exit $( echo "$MAX_SCORE > $COMPLEXITY_MAX_SCORE" | bc )
+          MAX_SCORE=$( cat cc_max_score )
+          anybadge -l radon-cc -v $MAX_SCORE -f radon-cc.svg 11=green 21=yellow 31=orange 100=red
+        continue-on-error: true
+      - name: upload radon-cc results
+        uses: actions/upload-artifact@v3
+        with:
+          name: radon-cc-results
+          path: radon-cc.log
+        if: ${{ always() }}
+      - name: upload radon-cc badge
+        uses: actions/upload-artifact@v3
+        with:
+          name: radon-cc-badge
+          path: radon-cc.svg
+        if: ${{ always() }}
+      - name: radon-mi
+        run: |
+          pip install radon
+          chmod a+x scripts/run_analysis_mi.sh
+          MIN_INDEX=$( scripts/run_analysis_mi.sh $SOURCE_DIRS )
+          echo $MIN_INDEX $MAINTAINABILITY_MIN_INDEX
+          echo $MIN_INDEX > mi_min_index
+          exit $( echo "$MIN_INDEX < $MAINTAINABILITY_MIN_INDEX" | bc )  # bc evaluates to `1` if relation is true
+          MIN_INDEX=$( cat mi_min_index )
+          anybadge -l radon-mi -v $MIN_INDEX -f radon-mi.svg 10=red 20=orange 100=green
+        continue-on-error: true
+      - name: upload radon-mi results
+        uses: actions/upload-artifact@v3
+        with:
+          name: radon-mi-results
+          path: radon-mi.log
+        if: ${{ always() }}
+      - name: upload radon-mi badge
+        uses: actions/upload-artifact@v3
+        with:
+          name: radon-mi-badge
+          path: radon-mi.svg
+        if: ${{ always() }}
+      - name: coverage
+        run: |
+          pip install coverage
+          chmod a+x scripts/run_unit_tests_with_coverage.sh
+          scripts/run_unit_tests_with_coverage.sh $SOURCE_DIRS
+          coverage report --show-missing --fail-under=$COVERAGE_MIN_PERC | tee coverage.log
+          # coverage: '/^TOTAL.+?(\d+\%)$/'
+      - name: upload coverage results
+        uses: actions/upload-artifact@v3
+        with:
+          name: coverage-results
+          path: coverage.log
+        if: ${{ always() }}
+      - name: unit-tests latest
+        run: |
+          pip install unittest-xml-reporting
+          python -m xmlrunner --output-file testresults.xml discover --start-directory=tests --pattern="test_*.py"
+      - name: upload unit-tests results
+        uses: actions/upload-artifact@v3
+        with:
+          name: unit-test-results
+          path: testresults.xml
+        if: ${{ always() }}
diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml
new file mode 100644
index 00000000..351a4187
--- /dev/null
+++ b/.github/workflows/python-publish.yml
@@ -0,0 +1,48 @@
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+# GitHub recommends pinning actions to a commit SHA.
+# To get a newer version, you will need to update the SHA.
+# You can also reference a tag or branch, but the action may change without warning.
+
+name: Publish to Pypi
+
+on:
+  release:
+    branches:
+      - "main"
+    types: [published]
+
+permissions:
+  contents: read
+
+jobs:
+  deploy:
+
+    runs-on: ubuntu-latest
+    strategy:
+      max-parallel: 1
+      matrix:
+        python-version: ["3.8", "3.9", "3.10", "3.11"]
+    steps:
+    - uses: actions/checkout@v3
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python@v4
+      with:
+        python-version: ${{ matrix.python-version }}
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        # pip install build
+        pip install twine wheel
+    - name: Build wheel
+       # run: python -m build
+      run: python setup.py sdist bdist_wheel
+    - name: Publish package
+      run: python -m twine upload --verbose --repository-url ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi dist/*
+       # uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
+      with:
+        user: __token__
+        password: ${{ secrets.PYPI_API_TOKEN }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3ddbbb64..cad0caaf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Added
 - ACKNOWLEDGEMENTS.md
 - Copyright waiver in README.md
+- CI workflows for regular push, pull request and for publishing.
+- Scheduled CI workflow for main branch
 
 ### Changed
 - LICENSE.md with a small change on first sentence
diff --git a/LICENSE b/LICENSE.md
similarity index 100%
rename from LICENSE
rename to LICENSE.md
diff --git a/README.md b/README.md
index ec2a6096..687de805 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,8 @@
+![pylint](https://github.com/QuTech-Delft/QMI/tree/main/pylint.svg)
+ ![mypy](https://github.com/QuTech-Delft/QMI/tree/main/mypy.svg)
+ ![radon-cc](https://github.com/QuTech-Delft/QMI/tree/main/radon-cc.svg)
+ ![radon-mi](https://github.com/QuTech-Delft/QMI/tree/main/radon-mi.svg)
+
 # Quantum Measurement Infrastructure
 
 QMI is a Python 3 framework for controlling laboratory equipment. It is suitable for anything ranging from one-off
@@ -33,8 +38,6 @@ systems and virtual environments.
 
 ## Documentation
 
-Download the latest documentation from
-
 `<TBD>`
 
 ## Contribute
diff --git a/documentation/sphinx/README_DOCUMENTATION b/documentation/sphinx/README_DOCUMENTATION
deleted file mode 100644
index 7eb83cda..00000000
--- a/documentation/sphinx/README_DOCUMENTATION
+++ /dev/null
@@ -1,44 +0,0 @@
-Answered Questions
-==================
-
-  * Is it possible to render stuff that is in the same RST file, on different pages?
-
-    No. See:
-
-    https://stackoverflow.com/questions/44777009/can-i-split-a-single-restructuredtext-file-into-multiple-html-pages-by-section
-
-  * Can we add a hook to autodoc (eg for @rpc_method decorators)?
-
-    Yes, using events, inside a self-written Sphinx extension (or inside "conf.py").
-
-  * How do we document class member variables?
-
-    --> Use the "Attributes" section in Google Docs style.
-
-Unanswered questions
-====================
-
-  * Is it possible to suppress stuff (make it invisible for autodoc)?
-
-    Check the
-         :exclude-members: option!
-
-  * How do we extend the toc-tree inside the code tree?
-
-  * Is 'autosummary' worth it?
-
-  * How do I widen the page?
-
-    This is theme-specific.
-
-
-Issues
-======
-
-(1) WARNING: more than one target found for cross-reference
-
-    We accept them, for now.
-
-(2) We use autodoc but not autosummary, for now.
-
-(3) We get a warning from the sphinx_rtd_theme while building the documentation.
diff --git a/documentation/sphinx/make-docs.sh b/documentation/sphinx/make-docs.sh
index 2162a55a..a8a831dd 100644
--- a/documentation/sphinx/make-docs.sh
+++ b/documentation/sphinx/make-docs.sh
@@ -1,5 +1,5 @@
 #! /usr/bin/env bash
 
 rm -rf source/generated
-sphinx-apidoc --no-toc --module-first --separate -o source/generated ../../qmi ../../qmi/core/usbtmc.py ../../qmi/gui
+sphinx-apidoc --no-toc --module-first --separate -o source/generated ../../qmi ../../qmi/core/usbtmc.py
 exec make clean html
diff --git a/documentation/sphinx/source/conf.py b/documentation/sphinx/source/conf.py
index b3331f0b..3f941759 100644
--- a/documentation/sphinx/source/conf.py
+++ b/documentation/sphinx/source/conf.py
@@ -24,7 +24,7 @@
 author = 'QuTech'
 
 # The full version, including alpha/beta/rc tags
-release = '0.36.0-beta.0'
+release = '1.0.0-beta.0'
 
 # The default master_doc used to be 'index', but it was changed to 'contents'.
 # Override that here (maybe rename the file to the new default later).
diff --git a/qmi/__init__.py b/qmi/__init__.py
index ea58612c..a02e1f4c 100644
--- a/qmi/__init__.py
+++ b/qmi/__init__.py
@@ -7,7 +7,7 @@
 import atexit
 
 
-__version__ = "0.36.0-beta.0"
+__version__ = "1.0.0-beta.0"
 
 
 # Check Python version.
diff --git a/scripts/run_docs_sphinx.sh b/scripts/run_docs_sphinx.sh
index a1b59ff8..9729439f 100644
--- a/scripts/run_docs_sphinx.sh
+++ b/scripts/run_docs_sphinx.sh
@@ -2,5 +2,5 @@
 
 DOC_DIR="documentation/sphinx"
 rm -rf $DOC_DIR/source/generated
-sphinx-apidoc --no-toc --module-first --separate -o $DOC_DIR/source/generated qmi qmi/core/usbtmc.py qmi/gui
+sphinx-apidoc --no-toc --module-first --separate -o $DOC_DIR/source/generated qmi qmi/core/usbtmc.py
 exec make -C $DOC_DIR clean html


Test patch:
diff --git a/.github/workflows/schedule-full-ci-test-main.yml b/.github/workflows/schedule-full-ci-test-main.yml
new file mode 100644
index 00000000..7bc8e1d0
--- /dev/null
+++ b/.github/workflows/schedule-full-ci-test-main.yml
@@ -0,0 +1,153 @@
+name: QMI Python CI full test runner
+
+on:
+  schedule:
+    # Schedule for every day at 7am
+    - cron: "0 7 * * *"
+
+env:
+  # minimum pylint code quality score (max. 10)
+  PYLINT_MIN_SCORE: "9.00"
+  # minimum code coverage (goal: 80%; max. 100%)
+  COVERAGE_MIN_PERC: "68"
+  # maximum code complexity (goal: <= 15; unbounded)
+  COMPLEXITY_MAX_SCORE: "38"
+  # minimum maintainability index (goal: >=20; max. 100)
+  MAINTAINABILITY_MIN_INDEX: "10.62"
+  SOURCE_DIRS: "qmi/"
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+    strategy:
+      max-parallel: 1
+      matrix:
+        python-version: ["3.8", "3.9", "3.10", "3.11"]
+    steps:
+      - uses: actions/checkout@v3
+      - name: Set up Python-latest
+        uses: actions/setup-python@v4
+        with:
+          python-version: ${{ matrix.python-version }}
+      - name: Install dependencies
+        run: |
+          sudo apt-get update -qy
+          sudo apt-get install -y bc
+          python3 --version
+          pip install --upgrade pip
+          pip install -e '.[dev]'
+          pip install anybadge
+      - name: pylint
+        run: |
+          pip install pylint
+          chmod a+x scripts/run_analysis_pylint.sh
+          SCORE=$( scripts/run_analysis_pylint.sh )
+          echo $SCORE $PYLINT_MIN_SCORE
+          echo $SCORE > pylint_score
+          # bc evaluates to `1` if relation is true
+          exit $( echo "$SCORE < $PYLINT_MIN_SCORE" | bc )
+          SCORE=$( cat pylint_score )
+          anybadge -l pylint -v $SCORE -f pylint.svg 2=red 4=orange 8=yellow 10=green
+        continue-on-error: true
+      - name: upload pylint test results
+        uses: actions/upload-artifact@v3
+        with:
+          name: pylint-results
+          path: pylint.log
+        if: ${{ always() }}
+      - name: upload pylint test badge
+        uses: actions/upload-artifact@v3
+        with:
+          name: pylint-badge
+          path: pylint.svg
+        if: ${{ always() }}
+      - name: mypy
+        run: |
+          pip install mypy
+          mypy --namespace-packages $SOURCE_DIRS | tee mypy.log
+          if [ -n "$( tail -n 1 mypy.log | grep -e '^Succes' )" ]; then RESULT="passed"; else RESULT="failed"; fi
+          anybadge -l mypy -v $RESULT -f mypy.svg failed=red passed=green
+        continue-on-error: true
+      - name: upload mypy test results
+        uses: actions/upload-artifact@v3
+        with:
+          name: mypy-results
+          path: mypy.log
+        if: ${{ always() }}
+      - name: upload mypy test badge
+        uses: actions/upload-artifact@v3
+        with:
+          name: mypy-badge
+          path: mypy.svg
+        if: ${{ always() }}
+      - name: radon-cc
+        run: |
+          pip install radon
+          chmod a+x scripts/run_analysis_cc.sh
+          MAX_SCORE=$( scripts/run_analysis_cc.sh $SOURCE_DIRS )
+          echo $MAX_SCORE $COMPLEXITY_MAX_SCORE
+          echo $MAX_SCORE > cc_max_score
+          # bc evaluates to `1` if relation is true
+          exit $( echo "$MAX_SCORE > $COMPLEXITY_MAX_SCORE" | bc )
+          MAX_SCORE=$( cat cc_max_score )
+          anybadge -l radon-cc -v $MAX_SCORE -f radon-cc.svg 11=green 21=yellow 31=orange 100=red
+        continue-on-error: true
+      - name: upload radon-cc results
+        uses: actions/upload-artifact@v3
+        with:
+          name: radon-cc-results
+          path: radon-cc.log
+        if: ${{ always() }}
+      - name: upload radon-cc badge
+        uses: actions/upload-artifact@v3
+        with:
+          name: radon-cc-badge
+          path: radon-cc.svg
+        if: ${{ always() }}
+      - name: radon-mi
+        run: |
+          pip install radon
+          chmod a+x scripts/run_analysis_mi.sh
+          MIN_INDEX=$( scripts/run_analysis_mi.sh $SOURCE_DIRS )
+          echo $MIN_INDEX $MAINTAINABILITY_MIN_INDEX
+          echo $MIN_INDEX > mi_min_index
+          exit $( echo "$MIN_INDEX < $MAINTAINABILITY_MIN_INDEX" | bc )  # bc evaluates to `1` if relation is true
+          MIN_INDEX=$( cat mi_min_index )
+          anybadge -l radon-mi -v $MIN_INDEX -f radon-mi.svg 10=red 20=orange 100=green
+        continue-on-error: true
+      - name: upload radon-mi results
+        uses: actions/upload-artifact@v3
+        with:
+          name: radon-mi-results
+          path: radon-mi.log
+        if: ${{ always() }}
+      - name: upload radon-mi badge
+        uses: actions/upload-artifact@v3
+        with:
+          name: radon-mi-badge
+          path: radon-mi.svg
+        if: ${{ always() }}
+      - name: coverage
+        run: |
+          pip install coverage
+          chmod a+x scripts/run_unit_tests_with_coverage.sh
+          scripts/run_unit_tests_with_coverage.sh $SOURCE_DIRS
+          coverage report --show-missing --fail-under=$COVERAGE_MIN_PERC | tee coverage.log
+          # coverage: '/^TOTAL.+?(\d+\%)$/'
+      - name: upload coverage results
+        uses: actions/upload-artifact@v3
+        with:
+          name: coverage-results
+          path: coverage.log
+        if: ${{ always() }}
+      - name: unit-tests latest
+        run: |
+          pip install unittest-xml-reporting
+          python -m xmlrunner --output-file testresults.xml discover --start-directory=tests --pattern="test_*.py"
+      - name: upload unit-tests results
+        uses: actions/upload-artifact@v3
+        with:
+          name: unit-test-results
+          path: testresults.xml
+        if: ${{ always() }}
diff --git a/tests/instruments/picoquant/test_library_wrapper.py b/tests/instruments/picoquant/test_library_wrapper.py
index aa064095..c756601d 100644
--- a/tests/instruments/picoquant/test_library_wrapper.py
+++ b/tests/instruments/picoquant/test_library_wrapper.py
@@ -1,9 +1,7 @@
 import unittest.mock
 
 from qmi.instruments.picoquant._library_wrapper import _LibWrapper
-from qmi.instruments.picoquant import (
-    _mhlib_function_signatures, _hhlib_function_signatures, _phlib_function_signatures, _th260lib_function_signatures
-)
+from qmi.instruments.picoquant import _hhlib_function_signatures
 
 
 class LibraryWrapperTestCase(unittest.TestCase):
@@ -11,7 +9,7 @@ class LibraryWrapperTestCase(unittest.TestCase):
     @unittest.mock.patch("sys.platform", "darwin")
     def test_unsupported_platform_fail(self):
         with self.assertRaises(OSError) as exc:
-            _LibWrapper("TH260")
+            _LibWrapper("HH")
 
         self.assertEqual(str(exc.exception), "Unsupported platform.")
 
@@ -31,15 +29,6 @@ def test_unsupported_harp_win(self):
 
         self.assertEqual(str(exc.exception), "Unknown library: {lib}.".format(lib=false_lib))
 
-    @unittest.mock.patch("sys.platform", "linux")
-    def test_multiharp_wrapper_annotations_lin(self):
-        attr_list = [fs[0] for fs in _mhlib_function_signatures._mhlib_function_signatures]
-        with unittest.mock.patch("ctypes.cdll"):
-            lib_wrapper = _LibWrapper("MH")
-            annotations = dir(lib_wrapper._lib)
-            for a in attr_list:
-                self.assertIn(a, annotations)
-
     @unittest.mock.patch("sys.platform", "linux")
     def test_hydraharp_wrapper_annotations_lin(self):
         attr_list = [fs[0] for fs in _hhlib_function_signatures._hhlib_function_signatures]
@@ -49,33 +38,6 @@ def test_hydraharp_wrapper_annotations_lin(self):
             for a in attr_list:
                 self.assertIn(a, annotations)
 
-    @unittest.mock.patch("sys.platform", "linux")
-    def test_picoharp_wrapper_annotations_lin(self):
-        attr_list = [fs[0] for fs in _phlib_function_signatures._phlib_function_signatures]
-        with unittest.mock.patch("ctypes.cdll"):
-            lib_wrapper = _LibWrapper("PH")
-            annotations = dir(lib_wrapper._lib)
-            for a in attr_list:
-                self.assertIn(a, annotations)
-
-    @unittest.mock.patch("sys.platform", "linux")
-    def test_timeharp_wrapper_annotations_lin(self):
-        attr_list = [fs[0] for fs in _th260lib_function_signatures._th260lib_function_signatures]
-        with unittest.mock.patch("ctypes.cdll"):
-            lib_wrapper = _LibWrapper("TH260")
-            annotations = dir(lib_wrapper._lib)
-            for a in attr_list:
-                self.assertIn(a, annotations)
-
-    @unittest.mock.patch("sys.platform", "win32")
-    def test_multiharp_wrapper_annotations_win(self):
-        attr_list = [fs[0] for fs in _mhlib_function_signatures._mhlib_function_signatures]
-        with unittest.mock.patch("ctypes.WinDLL", create=True):
-            lib_wrapper = _LibWrapper("MH")
-            annotations = dir(lib_wrapper._lib)
-            for a in attr_list:
-                self.assertIn(a, annotations)
-
     @unittest.mock.patch("sys.platform", "win32")
     def test_hydraharp_wrapper_annotations_win(self):
         attr_list = [fs[0] for fs in _hhlib_function_signatures._hhlib_function_signatures]
@@ -85,35 +47,9 @@ def test_hydraharp_wrapper_annotations_win(self):
             for a in attr_list:
                 self.assertIn(a, annotations)
 
-    @unittest.mock.patch("sys.platform", "win32")
-    def test_picoharp_wrapper_annotations_win(self):
-        attr_list = [fs[0] for fs in _phlib_function_signatures._phlib_function_signatures]
-        with unittest.mock.patch("ctypes.WinDLL", create=True):
-            lib_wrapper = _LibWrapper("PH")
-            annotations = dir(lib_wrapper._lib)
-            for a in attr_list:
-                self.assertIn(a, annotations)
-
-    @unittest.mock.patch("sys.platform", "win32")
-    def test_timeharp_wrapper_annotations_win(self):
-        attr_list = [fs[0] for fs in _th260lib_function_signatures._th260lib_function_signatures]
-        with unittest.mock.patch("ctypes.WinDLL", create=True):
-            lib_wrapper = _LibWrapper("TH260")
-            annotations = dir(lib_wrapper._lib)
-            for a in attr_list:
-                self.assertIn(a, annotations)
-
     @unittest.mock.patch("sys.platform", "win32")
     def test_wrapper_exceptions_32bit_win(self):
         """Test that the library wrapper will try to call also for the 32-bit library if 64-bit library call excepts."""
-        with unittest.mock.patch("ctypes.WinDLL", create=True, side_effect=[BaseException("No lib"), None]) \
-                as ptc:
-
-            _LibWrapper("MH")
-
-        ptc.assert_any_call("mhlib64.dll")
-        ptc.assert_called_with("mhlib.dll")
-
         with unittest.mock.patch("ctypes.WinDLL", create=True, side_effect=[BaseException("No lib"), None]) \
                 as ptc:
 
@@ -122,22 +58,6 @@ def test_wrapper_exceptions_32bit_win(self):
         ptc.assert_any_call("hhlib64.dll")
         ptc.assert_called_with("hhlib.dll")
 
-        with unittest.mock.patch("ctypes.WinDLL", create=True, side_effect=[BaseException("No lib"), None]) \
-                as ptc:
-
-            _LibWrapper("PH")
-
-        ptc.assert_any_call("phlib64.dll")
-        ptc.assert_called_with("phlib.dll")
-
-        with unittest.mock.patch("ctypes.WinDLL", create=True, side_effect=[BaseException("No lib"), None]) \
-                as ptc:
-
-            _LibWrapper("TH260")
-
-        ptc.assert_any_call("th260lib64.dll")
-        ptc.assert_called_with("th260lib.dll")
-
 
 if __name__ == "__main__":
     unittest.main()