224 lines
9.3 KiB
YAML
224 lines
9.3 KiB
YAML
name: Parallel Tests
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
test_file:
|
|
description: Run one discovered test file
|
|
required: true
|
|
default: archivebox/tests/test_cli_list.py
|
|
type: string
|
|
workflow_call:
|
|
inputs:
|
|
test_file:
|
|
description: Run one discovered test file; empty runs the complete matrix
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
|
|
env:
|
|
PYTHONIOENCODING: utf-8
|
|
PYTHONLEGACYWINDOWSSTDIO: utf-8
|
|
USE_COLOR: False
|
|
UV_NO_SOURCES: "1"
|
|
CI_PYTHON_VERSION: "3.13"
|
|
|
|
jobs:
|
|
discover-tests:
|
|
name: Discover every test
|
|
# Selection must run on GitHub so an offline NAS cannot block the fallback.
|
|
runs-on: ubuntu-24.04
|
|
outputs:
|
|
tests: ${{ steps.discover.outputs.tests }}
|
|
runner: ${{ steps.runner.outputs.use-runner || '["ubuntu-24.04"]' }}
|
|
|
|
steps:
|
|
- name: Select available CI capacity
|
|
id: runner
|
|
if: vars.UGNAS_CI_ENABLED == 'true' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main')
|
|
uses: mikehardy/runner-fallback-action@dc7732f9e532c451b2527b288ba8d58fd4b0a4ca # v1.1.0
|
|
with:
|
|
github-token: ${{ secrets.RUNNER_STATUS_TOKEN }}
|
|
organization: ArchiveBox
|
|
primary-runner: self-hosted,ugnas-linux
|
|
fallback-runner: ubuntu-24.04
|
|
# Jobs queued for this pool must not be mistaken for idle runner slots:
|
|
# requiring MAX_JOBS idle runners prevents assigning a second wave.
|
|
primaries-required: ${{ vars.UGNAS_CI_MIN_IDLE || 1 }}
|
|
fallback-on-error: true
|
|
|
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
|
with:
|
|
submodules: true
|
|
fetch-depth: 1
|
|
|
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
|
with:
|
|
python-version: ${{ env.CI_PYTHON_VERSION }}
|
|
|
|
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
|
|
with:
|
|
version: "0.11.3"
|
|
enable-cache: false
|
|
cache-dependency-glob: uv.lock
|
|
|
|
- name: Resolve discovery tools through abxpkg
|
|
env:
|
|
ABXPKG_LIB_DIR: ${{ runner.temp }}/abx-lib
|
|
run: |
|
|
set -Eeuo pipefail
|
|
ABXPKG_SPEC="$(uv run --no-cache --no-project python -c 'import tomllib; package = next(item for item in tomllib.load(open("uv.lock", "rb"))["package"] if item["name"] == "abxpkg"); wheel = package["wheels"][0]; print("abxpkg @ {}#{}".format(wheel["url"], wheel["hash"].replace(":", "=")))')"
|
|
test -n "$ABXPKG_SPEC"
|
|
mkdir -p "$ABXPKG_LIB_DIR/env/bin"
|
|
export PYTHON_BINARY="$pythonLocation/bin/python"
|
|
test -x "$PYTHON_BINARY"
|
|
uv run --no-cache --no-project --with "$ABXPKG_SPEC" abxpkg env \
|
|
--install \
|
|
--lib="$ABXPKG_LIB_DIR" \
|
|
--deps-from="$GITHUB_WORKSPACE/.github/configs/ci-tooling.json:ci_binaries"
|
|
{
|
|
echo "PYTHON_BINARY=$ABXPKG_LIB_DIR/env/bin/python"
|
|
echo "JQ_BINARY=$ABXPKG_LIB_DIR/env/bin/jq"
|
|
} >> "$GITHUB_ENV"
|
|
test -L "$ABXPKG_LIB_DIR/env/bin/python"
|
|
test -x "$ABXPKG_LIB_DIR/env/bin/python"
|
|
test -L "$ABXPKG_LIB_DIR/env/bin/jq"
|
|
test -x "$ABXPKG_LIB_DIR/env/bin/jq"
|
|
|
|
- name: Discover every test exactly once
|
|
id: discover
|
|
env:
|
|
TEST_FILE: ${{ inputs.test_file }}
|
|
UGNAS_CI_MAX_JOBS: ${{ vars.UGNAS_CI_MAX_JOBS || 3 }}
|
|
run: |
|
|
set -Eeuo pipefail
|
|
mapfile -t output < <("$PYTHON_BINARY" .github/scripts/discover_test_matrix.py)
|
|
test "${#output[@]}" -eq 2
|
|
if [[ -n "$TEST_FILE" ]]; then
|
|
output[1]="$("$JQ_BINARY" -ce '[.[] | select(.paths == [env.TEST_FILE])] | select(length == 1)' <<< "${output[1]}")"
|
|
fi
|
|
echo "tests=${output[1]}" >> "$GITHUB_OUTPUT"
|
|
echo "${output[0]}"
|
|
"$JQ_BINARY" -e 'length > 0 and length <= 256 and ([.[].paths[]] | length == (unique | length))' <<< "${output[1]}"
|
|
|
|
tests:
|
|
name: ${{ matrix.test.name }}
|
|
needs: discover-tests
|
|
# Opt in trusted branches only; PRs always use GitHub-hosted runners.
|
|
# Ordinary Linux tests are eligible; # ci-runner: hosted declares exceptions.
|
|
# The hosted discovery job falls back when workers are offline/busy or the
|
|
# availability API fails. UGNAS_CI_ENABLED=false disables the pool entirely.
|
|
runs-on: ${{ fromJSON(matrix.test.ugnas && needs.discover-tests.outputs.runner || '["ubuntu-24.04"]') }}
|
|
env:
|
|
CHROME_HEADLESS: "true"
|
|
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
test: ${{ fromJson(needs.discover-tests.outputs.tests) }}
|
|
|
|
steps:
|
|
- name: Set job-local runtime paths
|
|
run: |
|
|
{
|
|
echo "ABXPKG_LIB_DIR=$RUNNER_TEMP/archivebox-lib"
|
|
echo "DATA_DIR=$RUNNER_TEMP/archivebox-test-data"
|
|
echo "PERSONAS_DIR=$RUNNER_TEMP/abx-personas"
|
|
echo "CHROME_USER_DATA_DIR=$RUNNER_TEMP/abx-personas/Default/chrome_profile"
|
|
} >> "$GITHUB_ENV"
|
|
|
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
|
with:
|
|
submodules: true
|
|
fetch-depth: 1
|
|
|
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
|
with:
|
|
python-version: ${{ env.CI_PYTHON_VERSION }}
|
|
architecture: x64
|
|
|
|
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
|
|
with:
|
|
version: "0.11.3"
|
|
enable-cache: false
|
|
cache-dependency-glob: uv.lock
|
|
|
|
- name: Resolve optional build dependencies through abxpkg
|
|
if: matrix.test.extra != '' || contains(matrix.test.paths_arg, 'test_postgres_backend')
|
|
env:
|
|
TEST_EXTRA: ${{ matrix.test.extra }}
|
|
TEST_PATHS: ${{ matrix.test.paths_arg }}
|
|
run: |
|
|
set -Eeuo pipefail
|
|
ABXPKG_SPEC="$(uv run --no-cache --no-project python -c 'import tomllib; package = next(item for item in tomllib.load(open("uv.lock", "rb"))["package"] if item["name"] == "abxpkg"); wheel = package["wheels"][0]; print("abxpkg @ {}#{}".format(wheel["url"], wheel["hash"].replace(":", "=")))')"
|
|
test -n "$ABXPKG_SPEC"
|
|
dependency_sources=()
|
|
if [[ -n "$TEST_EXTRA" ]]; then
|
|
dependency_sources+=(--deps-from="$GITHUB_WORKSPACE/.github/configs/ci-linux-build.json:required_binaries")
|
|
fi
|
|
if [[ "$TEST_PATHS" == *test_postgres_backend* ]]; then
|
|
dependency_sources+=(--deps-from="$GITHUB_WORKSPACE/.github/configs/ci-linux-build.json:postgres_binaries")
|
|
fi
|
|
uv run --no-cache --no-project --with "$ABXPKG_SPEC" abxpkg env \
|
|
--install \
|
|
--no-cache \
|
|
--json \
|
|
--lib="$ABXPKG_LIB_DIR" \
|
|
"${dependency_sources[@]}"
|
|
|
|
- name: Install test dependencies
|
|
env:
|
|
TEST_EXTRA: ${{ matrix.test.extra }}
|
|
run: |
|
|
set -Eeuo pipefail
|
|
extra_args=()
|
|
if [[ -n "$TEST_EXTRA" ]]; then
|
|
extra_args=(--extra "$TEST_EXTRA")
|
|
fi
|
|
uv sync --no-cache --locked --dev "${extra_args[@]}"
|
|
- name: Install Playwright Chromium for browser tests
|
|
if: contains(matrix.test.paths_arg, 'test_proof_previews') || contains(matrix.test.paths_arg, 'test_snapshot_output_stacks') || contains(matrix.test.paths_arg, 'test_ui_admin_snapshot') || contains(matrix.test.paths_arg, 'test_cli_extract')
|
|
run: uv run --no-cache --no-sync --no-sources playwright install chromium
|
|
|
|
- name: Run ${{ matrix.test.name }}
|
|
env:
|
|
TEST_PATHS: ${{ matrix.test.paths_arg }}
|
|
run: |
|
|
set -Eeuo pipefail
|
|
read -r -a test_paths <<< "$TEST_PATHS"
|
|
for test_path in "${test_paths[@]}"; do
|
|
test -f "$test_path"
|
|
done
|
|
test_status=0
|
|
for test_path in "${test_paths[@]}"; do
|
|
test_name="$(basename "$test_path" .py)"
|
|
test_slug="${test_name//[^A-Za-z0-9_.-]/_}"
|
|
export DATA_DIR="$RUNNER_TEMP/archivebox-test-data/$test_slug"
|
|
export PERSONAS_DIR="$RUNNER_TEMP/abx-personas/$test_slug"
|
|
export CHROME_USER_DATA_DIR="$PERSONAS_DIR/Default/chrome_profile"
|
|
if (( ${#test_paths[@]} > 1 )); then
|
|
export ABXPKG_LIB_DIR="$RUNNER_TEMP/archivebox-lib/$test_slug"
|
|
fi
|
|
mkdir -p "$DATA_DIR" "$PERSONAS_DIR" "$RUNNER_TEMP/pytest/$test_slug"
|
|
uv run --no-cache --no-sync --no-sources pytest -vs "$test_path" --durations=20 \
|
|
--basetemp="$RUNNER_TEMP/pytest/$test_slug" \
|
|
--junitxml="$RUNNER_TEMP/test-results/$test_slug.xml" || test_status=1
|
|
done
|
|
exit "$test_status"
|
|
- name: Save test results
|
|
if: always()
|
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
|
with:
|
|
name: test-results-${{ strategy.job-index }}
|
|
path: ${{ runner.temp }}/test-results/*.xml
|
|
if-no-files-found: error
|
|
retention-days: 14
|
|
- name: Save failed runtime diagnostics
|
|
if: failure()
|
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
|
with:
|
|
name: runtime-logs-${{ strategy.job-index }}
|
|
path: ${{ runner.temp }}/pytest/**/*.log
|
|
if-no-files-found: ignore
|
|
retention-days: 14
|