Re-implement unit tests on Linux using Github actions instead of CircleCI #179
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: CI | |
on: | |
push: | |
branches: | |
- "master" | |
pull_request: | |
# At the start of each workflow run, GitHub creates a unique | |
# GITHUB_TOKEN secret to use in the workflow. It is a good idea for | |
# this GITHUB_TOKEN to have the minimum of permissions. See: | |
# | |
# - https://docs.github.com/en/actions/security-guides/automatic-token-authentication | |
# - https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions | |
# | |
permissions: | |
contents: read | |
# Control to what degree jobs in this workflow will run concurrently with | |
# other instances of themselves. | |
# | |
# https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#concurrency | |
concurrency: | |
# We want every revision on master to run the workflow completely. | |
# "head_ref" is not set for the "push" event but it is set for the | |
# "pull_request" event. If it is set then it is the name of the branch and | |
# we can use it to make sure each branch has only one active workflow at a | |
# time. If it is not set then we can compute a unique string that gives | |
# every master/push workflow its own group. | |
group: "${{ github.head_ref || format('{0}-{1}', github.run_number, github.run_attempt) }}" | |
# Then, we say that if a new workflow wants to start in the same group as a | |
# running workflow, the running workflow should be cancelled. | |
cancel-in-progress: true | |
env: | |
# Tell Hypothesis which configuration we want it to use. | |
TAHOE_LAFS_HYPOTHESIS_PROFILE: "ci" | |
jobs: | |
coverage: | |
runs-on: ${{ matrix.os }} | |
strategy: | |
fail-fast: false | |
matrix: | |
include: | |
- os: macos-12 | |
python-version: "3.12" | |
# We only support PyPy on Linux at the moment. | |
- os: ubuntu-latest | |
python-version: "pypy-3.9" | |
- os: ubuntu-latest | |
python-version: "3.12" | |
- os: windows-latest | |
python-version: "3.12" | |
steps: | |
# See https://github.com/actions/checkout. A fetch-depth of 0 | |
# fetches all tags and branches. | |
- name: Check out Tahoe-LAFS sources | |
uses: actions/checkout@v3 | |
with: | |
fetch-depth: 0 | |
- name: Set up Python ${{ matrix.python-version }} | |
uses: actions/setup-python@v4 | |
with: | |
python-version: ${{ matrix.python-version }} | |
cache: 'pip' # caching pip dependencies | |
- name: Install Python packages | |
run: | | |
pip install --upgrade tox tox-gh-actions setuptools | |
pip list | |
- name: Display tool versions | |
run: python misc/build_helpers/show-tool-versions.py | |
- name: Run tox for corresponding Python version | |
if: ${{ !contains(matrix.os, 'windows') }} | |
run: python -m tox | |
# On Windows, a non-blocking pipe might respond (when emulating Unix-y | |
# API) with ENOSPC to indicate buffer full. Trial doesn't handle this | |
# well, so it breaks test runs. To attempt to solve this, we pipe the | |
# output through passthrough.py that will hopefully be able to do the right | |
# thing by using Windows APIs. | |
- name: Run tox for corresponding Python version | |
if: ${{ contains(matrix.os, 'windows') }} | |
run: | | |
pip install twisted pywin32 | |
python -m tox | python misc/windows-enospc/passthrough.py | |
- name: Upload eliot.log | |
uses: actions/upload-artifact@v4 | |
with: | |
name: eliot-${{ matrix.runs-on }}.log | |
path: eliot.log | |
- name: Upload trial log | |
uses: actions/upload-artifact@v4 | |
with: | |
name: test-${{ matrix.runs-on }}.log | |
path: _trial_temp/test.log | |
# Upload this job's coverage data to Coveralls. While there is a GitHub | |
# Action for this, as of Jan 2021 it does not support Python coverage | |
# files - only lcov files. Therefore, we use coveralls-python, the | |
# coveralls.io-supplied Python reporter, for this. | |
- name: "Report Coverage to Coveralls" | |
run: | | |
pip3 install --upgrade coveralls==3.0.1 | |
python3 -m coveralls | |
env: | |
# Some magic value required for some magic reason. | |
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" | |
# Help coveralls identify our project. | |
COVERALLS_REPO_TOKEN: "JPf16rLB7T2yjgATIxFzTsEgMdN1UNq6o" | |
# Every source of coverage reports needs a unique "flag name". | |
# Construct one by smashing a few variables from the matrix together | |
# here. | |
COVERALLS_FLAG_NAME: "run-${{ matrix.os }}-${{ matrix.python-version }}" | |
# Mark the data as just one piece of many because we have more than | |
# one instance of this job (Windows, macOS) which collects and | |
# reports coverage. This is necessary to cause Coveralls to merge | |
# multiple coverage results into a single report. Note the merge | |
# only happens when we "finish" a particular build, as identified by | |
# its "build_num" (aka "service_number"). | |
COVERALLS_PARALLEL: true | |
# Tell Coveralls that we're done reporting coverage data. Since we're using | |
# the "parallel" mode where more than one coverage data file is merged into | |
# a single report, we have to tell Coveralls when we've uploaded all of the | |
# data files. This does it. We make sure it runs last by making it depend | |
# on *all* of the coverage-collecting jobs. | |
# | |
# See notes about parallel builds on GitHub Actions at | |
# https://coveralls-python.readthedocs.io/en/latest/usage/configuration.html | |
finish-coverage-report: | |
needs: | |
- "coverage" | |
runs-on: "ubuntu-latest" | |
container: "python:3-slim" | |
steps: | |
- name: "Indicate completion to coveralls.io" | |
run: | | |
pip3 install --upgrade coveralls==3.0.1 | |
python3 -m coveralls --finish | |
env: | |
# Some magic value required for some magic reason. | |
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" | |
integration: | |
runs-on: ${{ matrix.os }} | |
strategy: | |
fail-fast: false | |
matrix: | |
os: | |
# 22.04 has some issue with Tor at the moment: | |
# https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3943 | |
- ubuntu-20.04 | |
- macos-12 | |
- windows-latest | |
python-version: | |
- "3.11" | |
force-foolscap: | |
- false | |
include: | |
- os: ubuntu-20.04 | |
python-version: "3.12" | |
force-foolscap: true | |
steps: | |
- name: Install Tor [Ubuntu] | |
if: ${{ contains(matrix.os, 'ubuntu') }} | |
run: sudo apt install tor | |
# TODO: See https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3744. | |
# We have to use an older version of Tor for running integration | |
# tests on macOS. | |
- name: Install Tor [macOS, ${{ matrix.python-version }} ] | |
if: ${{ contains(matrix.os, 'macos') }} | |
run: | | |
brew install tor | |
- name: Install Tor [Windows] | |
if: matrix.os == 'windows-latest' | |
uses: crazy-max/ghaction-chocolatey@v2 | |
with: | |
args: install tor | |
- name: Check out Tahoe-LAFS sources | |
uses: actions/checkout@v3 | |
with: | |
fetch-depth: 0 | |
- name: Set up Python ${{ matrix.python-version }} | |
uses: actions/setup-python@v4 | |
with: | |
python-version: ${{ matrix.python-version }} | |
cache: 'pip' # caching pip dependencies | |
- name: Install Python packages | |
run: | | |
pip install --upgrade tox | |
pip list | |
- name: Display tool versions | |
run: python misc/build_helpers/show-tool-versions.py | |
- name: Run "Python 3 integration tests" | |
if: "${{ !matrix.force-foolscap }}" | |
env: | |
# On macOS this is necessary to ensure unix socket paths for tor | |
# aren't too long. On Windows tox won't pass it through so it has no | |
# effect. On Linux it doesn't make a difference one way or another. | |
TMPDIR: "/tmp" | |
run: | | |
tox -e integration | |
- name: Run "Python 3 integration tests (force Foolscap)" | |
if: "${{ matrix.force-foolscap }}" | |
env: | |
# On macOS this is necessary to ensure unix socket paths for tor | |
# aren't too long. On Windows tox won't pass it through so it has no | |
# effect. On Linux it doesn't make a difference one way or another. | |
TMPDIR: "/tmp" | |
run: | | |
tox -e integration -- --force-foolscap integration/ | |
- name: Upload eliot.log in case of failure | |
uses: actions/upload-artifact@v4 | |
if: failure() | |
with: | |
name: integration.eliot-${{ matrix.run-on }}.json | |
path: integration.eliot.json | |
unitests: | |
runs-on: ubuntu-22.04 | |
strategy: | |
fail-fast: false | |
matrix: | |
image: | |
- distro: "debian" | |
tag: "11" | |
python-version: "3.9" | |
tox_environment: "py39" | |
- distro: "debian" | |
tag: "12" | |
python-version: "3.11" | |
tox_environment: "py311" | |
- distro: "ubuntu" | |
tag: "20.04" | |
python-version: "3.9" | |
tox_environment: "py39" | |
- distro: "ubuntu" | |
tag: "22.04" | |
python-version: "3.10" | |
tox_environment: "py310" | |
- distro: "ubuntu" | |
tag: "24.04" | |
python-version: "3.12" | |
tox_environment: "py312" | |
- distro: "fedora" | |
tag: "35" | |
python-version: "3.10" | |
tox_environment: "py310" | |
- distro: "fedora" | |
tag: "40" | |
python-version: "3.12" | |
tox_environment: "py312" | |
- distro: "oraclelinux" | |
tag: "8" | |
python-version: "3.9" | |
tox_environment: "py39" | |
- distro: "oraclelinux" | |
tag: "9" | |
python-version: "3.11" | |
tox_environment: "py311" | |
allowed_failure: | |
- "no" | |
locale: | |
- "en_US.UTF-8" | |
include: | |
- image: | |
distro: "debian" | |
tag: "11" | |
python-version: "3.9" | |
tox_environment: "py39" | |
locale: "C" | |
- image: | |
distro: "debian" | |
tag: "11" | |
python-version: "3.9" | |
tox_environment: "py39" | |
locale: "en_US.ISO-8859-1" | |
env: | |
CONTEXT: .circleci # TODO: move all docker related files in a specific top-folder | |
PREFIX: tahoelafsci/ # Only to match the current name in CircleCI, could be anything | |
TAG: ${{ matrix.image.tag }} | |
PYTHON_VERSION: ${{ matrix.image.python-version }} | |
DISTRO: ${{ matrix.image.distro }} | |
LANG: ${{ matrix.locale }} | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v4 | |
- name: Cache docker images | |
# To save us some time if the docker context has not changed | |
# TODO: Maybe filter the context to keep only what's needed | |
# to build the images (e.g.: not the whole src) | |
uses: ScribeMD/[email protected] | |
with: | |
key: | | |
docker-${{ matrix.image.distro }}_${{ matrix.image.tag }}-py${{ matrix.image.python-version }}-${{ hashFiles( | |
format('{0}/Dockerfile.{1}', env.CONTEXT, matrix.image.distro) | |
) }} | |
- name: Build Docker image | |
# Only if not previously restored from the cache | |
run: | | |
docker images --filter "reference=${PREFIX}${DISTRO}" | |
docker images --filter "reference=${PREFIX}${DISTRO}:${TAG}-py${PYTHON_VERSION}" \ | |
--quiet | grep -v "^$" \ | |
|| docker \ | |
build \ | |
--build-arg TAG=${TAG} \ | |
--build-arg PYTHON_VERSION=${PYTHON_VERSION} \ | |
-t ${PREFIX}${DISTRO}:${TAG}-py${PYTHON_VERSION} \ | |
-f ${CONTEXT}/Dockerfile.${DISTRO} \ | |
. \ | |
&& docker images --filter "reference=${PREFIX}${DISTRO}" | |
- name: Prepare environment variables | |
# Write some variables in a file (ignored by git, similar to CircleCI) | |
# So we can pass them the runner AND the container in the next steps | |
# FIXME: There must be a better way to do this | |
id: prep | |
env: | |
# The following variables will be injected in the runner enviroment | |
# In general, the test suite is not allowed to fail while the job | |
# succeeds. But you can set this to "yes" if you want it to be | |
# otherwise. | |
ALLOWED_FAILURE: ${{ matrix.allowed_failure }} | |
# Select a tox environment to run for this job. | |
TAHOE_LAFS_TOX_ENVIRONMENT: ${{ matrix.image.tox_environment }} | |
# Additional arguments to pass to tox. | |
TAHOE_LAFS_TOX_ARGS: "-- allmydata.test.test_system" | |
# The path in which test artifacts will be placed. | |
ARTIFACTS_OUTPUT_PATH: /tmp/artifacts | |
# Convince all of our pip invocations to look at the cached wheelhouse | |
# we maintain. | |
WHEELHOUSE_PATH: /tmp/wheelhouse | |
PIP_FIND_LINKS: file:///tmp/wheelhouse | |
run: | | |
{ cat <<EOF | |
# The following variables will be injected in the container enviroment | |
# Convince all of our pip invocations to look at the cached wheelhouse | |
# we maintain. | |
WHEELHOUSE_PATH=${WHEELHOUSE_PATH} | |
PIP_FIND_LINKS=file://${WHEELHOUSE_PATH} | |
# Select a tox environment to run for this job. | |
# FIXME: this variable should not be duplicated here (already passed as an arg.)! | |
TAHOE_LAFS_TOX_ENVIRONMENT=${TAHOE_LAFS_TOX_ENVIRONMENT} | |
# Additional arguments to pass to tox. | |
# FIXME: this variable should not be duplicated here (already passed as an arg.)! | |
TAHOE_LAFS_TOX_ARGS=${TAHOE_LAFS_TOX_ARGS} | |
# Tell Hypothesis which configuration we want it to use. | |
TAHOE_LAFS_HYPOTHESIS_PROFILE=ci | |
# Tell the C runtime things about character encoding (mainly to do with | |
# filenames and argv). | |
# FIXME: this variable should not be duplicated here (already passed as an arg.)! | |
LANG=${LANG} | |
# The path in which test artifacts will be placed. | |
# FIXME: this variable should not be duplicated here (already passed as an arg.)! | |
ARTIFACTS_OUTPUT_PATH=/tmp/artifacts | |
# Upload the coverage report. | |
UPLOAD_COVERAGE="" | |
EOF | |
} | grep -v -E '^(|\s*#.*)$' \ | |
| tee -a $GITHUB_ENV \ | |
> secret-env-plain | |
# First, inject the variables in the runner environment, | |
# Then in a file used later to start the container | |
- name: Start the container | |
# So we can execute the next steps inside | |
id: start_container | |
run: | | |
CID=$(docker run \ | |
--rm --tty --detach \ | |
-v .:/tmp/project \ | |
-v "${ARTIFACTS_OUTPUT_PATH}":"${ARTIFACTS_OUTPUT_PATH}" \ | |
-w /tmp/project \ | |
--env-file secret-env-plain \ | |
"${PREFIX}${DISTRO}:${TAG}-py${PYTHON_VERSION}" \ | |
) | |
# Verify we have some python3 interpreter installed | |
docker exec $CID python3 -VV | |
# Print the environment within the container | |
docker exec $CID env | |
# Save the container id as output for the next steps | |
echo "cid=$CID" >> $GITHUB_OUTPUT | |
- name: Setup virtualenv | |
# Inside the container started above | |
run: | | |
docker exec \ | |
${{ steps.start_container.outputs.cid }} \ | |
/tmp/project/.circleci/setup-virtualenv.sh \ | |
"/tmp/venv" \ | |
"/tmp/project" \ | |
"${WHEELHOUSE_PATH}" \ | |
"${TAHOE_LAFS_TOX_ENVIRONMENT}" \ | |
"${TAHOE_LAFS_TOX_ARGS}" | |
- name: Run test suite | |
# Also inside the container | |
id: test_suite | |
run: | | |
docker exec \ | |
${{ steps.start_container.outputs.cid }} \ | |
/tmp/project/.circleci/run-tests.sh \ | |
"/tmp/venv" \ | |
"/tmp/project" \ | |
"${ALLOWED_FAILURE}" \ | |
"${ARTIFACTS_OUTPUT_PATH}" \ | |
"${TAHOE_LAFS_TOX_ENVIRONMENT}" \ | |
"${TAHOE_LAFS_TOX_ARGS}" | |
# trial output gets directed straight to a log. | |
# avoid the CI timeout while the test suite runs. | |
timeout-minutes: 20 | |
- name: Stop the container | |
# No longer needed for the next steps | |
if: ${{ always() && steps.start_container.outcome == 'success' }} | |
run: | | |
docker stop --time 3 \ | |
${{ steps.start_container.outputs.cid }} | |
- name: Store test results | |
# So we can download them if needed | |
if: ${{ !cancelled() }} | |
uses: actions/upload-artifact@v4 | |
with: | |
name: test-results_${{ env.DISTRO }}-${{ env.TAG }}_py${{ env.PYTHON_VERSION }}-${{ env.LANG }} | |
path: ${{ env.ARTIFACTS_OUTPUT_PATH }}/** | |
- name: Publish test reports | |
# To be visible from the build summary | |
id: test_reports | |
uses: mikepenz/action-junit-report@v5 | |
if: ${{ !cancelled() }} | |
with: | |
report_paths: ${{ env.ARTIFACTS_OUTPUT_PATH }}/junit/unittests/results.xml | |
annotate_only: true | |
detailed_summary: true | |
require_passed_tests: true | |
fail_on_failure: true | |
token: "${{ secrets.GITHUB_TOKEN }}" | |
- name: Conclusion | |
# Fail for the test results (parsed in the last step) if we care about them | |
if: ${{ !cancelled() }} | |
run: | | |
if [ "${{ steps.test_reports.outcome }}" = "failure" \ | |
-a "${{ matrix.image.allowed_failure }}" = "yes" ]; then | |
# If we have reached the test reporting, and some test have failed, | |
# succeed this build only if we allow the tests to fail | |
exit 0 | |
else | |
# Exit with the expected status w/o further expection | |
if [ "${{ job.status }}" = "success" ]; then | |
exit 0 | |
else | |
exit 1 | |
fi | |
fi | |
packaging: | |
runs-on: ${{ matrix.os }} | |
strategy: | |
fail-fast: false | |
matrix: | |
os: | |
- macos-12 | |
- windows-latest | |
- ubuntu-latest | |
python-version: | |
- 3.9 | |
steps: | |
- name: Check out Tahoe-LAFS sources | |
uses: actions/checkout@v3 | |
with: | |
fetch-depth: 0 | |
- name: Set up Python ${{ matrix.python-version }} | |
uses: actions/setup-python@v4 | |
with: | |
python-version: ${{ matrix.python-version }} | |
cache: 'pip' # caching pip dependencies | |
- name: Install Python packages | |
run: | | |
pip install --upgrade tox | |
pip list | |
- name: Display tool versions | |
run: python misc/build_helpers/show-tool-versions.py | |
- name: Run "tox -e pyinstaller" | |
run: tox -e pyinstaller | |
# This step is to ensure there are no packaging/import errors. | |
- name: Test PyInstaller executable | |
run: dist/Tahoe-LAFS/tahoe --version | |
- name: Upload PyInstaller package | |
uses: actions/upload-artifact@v4 | |
with: | |
name: Tahoe-LAFS-${{ matrix.os }}-Python-${{ matrix.python-version }} | |
path: dist/Tahoe-LAFS-*-*.* |