name: Parallel Tests on: workflow_dispatch: pull_request: branches: [dev, main, master] push: branches: [dev] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: PYTHONIOENCODING: utf-8 PYTHONLEGACYWINDOWSSTDIO: utf-8 USE_COLOR: False UV_NO_SOURCES: "1" jobs: discover-tests: name: Discover test files runs-on: ubuntu-24.04 outputs: test-files: ${{ steps.set-matrix.outputs.test-files }} plugin-tests: ${{ steps.set-plugin-matrix.outputs.plugin-tests }} steps: - uses: actions/checkout@v4 with: submodules: true fetch-depth: 1 - name: Discover test files id: set-matrix run: | # Find all main test files all_tests=$(find archivebox/tests -maxdepth 1 -name "test_*.py" -type f | sort) # Create JSON array with test file info json_array="[" first=true for test_file in $all_tests; do if [ "$first" = true ]; then first=false else json_array+="," fi # Extract a display name for the test name="main/$(basename $test_file .py | sed 's/^test_//')" needs_chromium=false if grep -Eiq '(chrom|archivewebpage|PLUGINS=.*title|--plugins=.*title|SAVE_TITLE.*[Tt]rue)' "$test_file"; then needs_chromium=true fi needs_sonic=false if grep -Eiq "shutil\\.which\\([\"']sonic|SEARCH_BACKEND_ENGINE=.*sonic|worker_sonic" "$test_file"; then needs_sonic=true fi json_array+="{\"path\":\"$test_file\",\"name\":\"$name\",\"needs_chromium\":$needs_chromium,\"needs_sonic\":$needs_sonic}" done json_array+="]" echo "test-files=$json_array" >> $GITHUB_OUTPUT echo "Found $(echo $all_tests | wc -w) test files" echo "$json_array" | jq '.' - name: Clone abx-plugins run: git clone --depth=1 https://github.com/ArchiveBox/abx-plugins.git abx-plugins - name: Discover plugin tests id: set-plugin-matrix run: | plugin_tests=$(find abx-plugins/abx_plugins/plugins -maxdepth 2 -type d -name tests | sed 's#abx-plugins/abx_plugins/plugins/##; s#/tests##' | sort) json_array="[" first=true for plugin_name in $plugin_tests; do if [ "$first" = true ]; then first=false else json_array+="," fi needs_chromium=false if grep -Riq "chrom" "abx-plugins/abx_plugins/plugins/$plugin_name"; then needs_chromium=true fi needs_sonic=false if [ "$plugin_name" = "search_backend_sonic" ]; then needs_sonic=true fi json_array+="{\"plugin\":\"$plugin_name\",\"name\":\"plugin/$plugin_name\",\"needs_chromium\":$needs_chromium,\"needs_sonic\":$needs_sonic}" done json_array+="]" echo "plugin-tests=$json_array" >> $GITHUB_OUTPUT echo "Found $(echo $plugin_tests | wc -w) plugin test suites" echo "$json_array" | jq '.' run-tests: name: ${{ matrix.test.name }} runs-on: ubuntu-24.04 needs: discover-tests env: PYTHONPATH: ${{ github.workspace }}/abxpkg:${{ github.workspace }}/abx-plugins:${{ github.workspace }}/abx-dl CHROME_HEADLESS: "true" CHROME_SANDBOX: "false" PERSONAS_DIR: /tmp/abx-personas CHROME_USER_DATA_DIR: /tmp/abx-personas/Default/chrome_profile strategy: fail-fast: false matrix: test: ${{ fromJson(needs.discover-tests.outputs.test-files) }} python: ["3.13"] steps: - uses: actions/checkout@v4 with: submodules: true fetch-depth: 1 - name: Clone abxpkg run: git clone --depth=1 https://github.com/ArchiveBox/abxpkg.git abxpkg - name: Clone abx-plugins run: git clone --depth=1 https://github.com/ArchiveBox/abx-plugins.git abx-plugins - name: Clone abx-dl run: git clone --depth=1 https://github.com/ArchiveBox/abx-dl.git abx-dl - name: Set up Python ${{ matrix.python }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} architecture: x64 - name: Install uv uses: astral-sh/setup-uv@v6 with: version: "0.10.6" - name: Set up Node JS uses: actions/setup-node@v4 with: node-version: 22 - name: Cache uv uses: actions/cache@v4 with: path: ~/.cache/uv key: ${{ runner.os }}-${{ matrix.python }}-uv-${{ hashFiles('pyproject.toml') }} restore-keys: | ${{ runner.os }}-${{ matrix.python }}-uv- - uses: awalsh128/cache-apt-pkgs-action@latest with: packages: git ripgrep build-essential python3-dev python3-setuptools libssl-dev libldap2-dev libsasl2-dev zlib1g-dev libatomic1 python3-minimal gnupg2 curl wget python3-ldap python3-msgpack python3-mutagen python3-regex python3-pycryptodome procps version: 1.1 - name: Install dependencies with uv run: | uv venv uv pip install --group dev -e ./abxpkg -e ./abx-plugins -e ./abx-dl -e ".[all]" - name: Install Chrome through ArchiveBox if: ${{ matrix.test.needs_chromium }} env: MIN_CHROMIUM_MAJOR: "149" LIB_DIR: ${{ runner.temp }}/abx-lib ABXPKG_LIB_DIR: ${{ runner.temp }}/abx-lib ABXPKG_INSTALL_TIMEOUT: "900" ABX_CI_DATA_DIR: ${{ runner.temp }}/archivebox-ci-data run: | set -euo pipefail mkdir -p "$LIB_DIR" "$ABX_CI_DATA_DIR" echo "LIB_DIR=$LIB_DIR" >> "$GITHUB_ENV" echo "ABXPKG_LIB_DIR=$ABXPKG_LIB_DIR" >> "$GITHUB_ENV" uv run --project "$GITHUB_WORKSPACE" --directory "$ABX_CI_DATA_DIR" --no-sync --no-sources archivebox install chrome candidate="$(uv run --project "$GITHUB_WORKSPACE" --directory "$ABX_CI_DATA_DIR" --no-sync --no-sources archivebox shell -c 'from archivebox.machine.models import Binary; binary = Binary.objects.filter(name="chromium", status="installed").order_by("-modified_at").first(); print(binary.abspath if binary else "")' | tail -n 1)" if [ ! -x "$candidate" ]; then echo "ArchiveBox did not install an executable Chromium binary: ${candidate:-not found}" >&2 exit 1 fi version="$("$candidate" --version || true)" major="$(printf '%s\n' "$version" | sed -E 's/.* ([0-9]+)\..*/\1/' | head -1)" case "$major" in ''|*[!0-9]*) major=0 ;; esac if [ "$major" -lt "$MIN_CHROMIUM_MAJOR" ]; then echo "ArchiveBox installed Chromium is too old: ${version:-unknown}" >&2 exit 1 fi echo "Using ArchiveBox-installed Chromium: $candidate ($version)" echo "CHROME_BINARY=$candidate" >> "$GITHUB_ENV" - name: Install Sonic through ArchiveBox if: ${{ matrix.test.needs_sonic }} env: LIB_DIR: ${{ runner.temp }}/abx-lib ABXPKG_LIB_DIR: ${{ runner.temp }}/abx-lib ABXPKG_INSTALL_TIMEOUT: "900" ABX_CI_DATA_DIR: ${{ runner.temp }}/archivebox-ci-data run: | set -euo pipefail mkdir -p "$LIB_DIR" "$ABX_CI_DATA_DIR" echo "LIB_DIR=$LIB_DIR" >> "$GITHUB_ENV" echo "ABXPKG_LIB_DIR=$ABXPKG_LIB_DIR" >> "$GITHUB_ENV" uv run --project "$GITHUB_WORKSPACE" --directory "$ABX_CI_DATA_DIR" --no-sync --no-sources archivebox install search_backend_sonic candidate="$(uv run --project "$GITHUB_WORKSPACE" --directory "$ABX_CI_DATA_DIR" --no-sync --no-sources archivebox shell -c 'from archivebox.machine.models import Binary; binary = Binary.objects.filter(name="sonic", status="installed").order_by("-modified_at").first(); print(binary.abspath if binary else "")' | tail -n 1)" if [ ! -x "$candidate" ]; then echo "ArchiveBox did not install an executable Sonic binary: ${candidate:-not found}" >&2 exit 1 fi version="$("$candidate" --version || true)" echo "Using ArchiveBox-installed Sonic: $candidate (${version:-version unknown})" echo "SONIC_BINARY=$candidate" >> "$GITHUB_ENV" echo "$(dirname "$candidate")" >> "$GITHUB_PATH" - name: Run test - ${{ matrix.test.name }} run: | mkdir -p tests/out set +e uv run --no-sync --no-sources pytest -xvs "${{ matrix.test.path }}" --basetemp=tests/out --ignore=archivebox/pkgs status=$? set -e if [ "$status" -eq 5 ]; then echo "No tests collected from ${{ matrix.test.path }}; treating as an empty test module." exit 0 fi exit "$status" plugin-tests: name: ${{ matrix.plugin.name }} runs-on: ubuntu-24.04 needs: discover-tests env: PYTHONPATH: ${{ github.workspace }}/abxpkg:${{ github.workspace }}/abx-plugins:${{ github.workspace }}/abx-dl CHROME_HEADLESS: "true" CHROME_SANDBOX: "false" PERSONAS_DIR: /tmp/abx-personas CHROME_USER_DATA_DIR: /tmp/abx-personas/Default/chrome_profile strategy: fail-fast: false matrix: plugin: ${{ fromJson(needs.discover-tests.outputs.plugin-tests) }} steps: - uses: actions/checkout@v4 with: submodules: true fetch-depth: 1 - name: Clone abxpkg run: git clone --depth=1 https://github.com/ArchiveBox/abxpkg.git abxpkg - name: Clone abx-plugins run: git clone --depth=1 https://github.com/ArchiveBox/abx-plugins.git abx-plugins - name: Clone abx-dl run: git clone --depth=1 https://github.com/ArchiveBox/abx-dl.git abx-dl - name: Set up Python 3.13 uses: actions/setup-python@v5 with: python-version: "3.13" architecture: x64 - name: Install uv uses: astral-sh/setup-uv@v6 with: version: "0.10.6" - name: Set up Node JS uses: actions/setup-node@v4 with: node-version: 22 - name: Cache uv uses: actions/cache@v4 with: path: ~/.cache/uv key: ${{ runner.os }}-3.13-uv-${{ hashFiles('pyproject.toml') }} restore-keys: | ${{ runner.os }}-3.13-uv- - uses: awalsh128/cache-apt-pkgs-action@latest with: packages: git ripgrep build-essential python3-dev python3-setuptools libssl-dev libldap2-dev libsasl2-dev zlib1g-dev libatomic1 python3-minimal gnupg2 curl wget python3-ldap python3-msgpack python3-mutagen python3-regex python3-pycryptodome procps version: 1.1 - name: Install dependencies with uv run: | uv venv uv pip install --group dev -e ./abxpkg -e ./abx-plugins -e ./abx-dl -e ".[all]" - name: Install Chrome through ArchiveBox if: ${{ matrix.plugin.needs_chromium }} env: MIN_CHROMIUM_MAJOR: "149" LIB_DIR: ${{ runner.temp }}/abx-lib ABXPKG_LIB_DIR: ${{ runner.temp }}/abx-lib ABXPKG_INSTALL_TIMEOUT: "900" ABX_CI_DATA_DIR: ${{ runner.temp }}/archivebox-ci-data run: | set -euo pipefail mkdir -p "$LIB_DIR" "$ABX_CI_DATA_DIR" echo "LIB_DIR=$LIB_DIR" >> "$GITHUB_ENV" echo "ABXPKG_LIB_DIR=$ABXPKG_LIB_DIR" >> "$GITHUB_ENV" uv run --project "$GITHUB_WORKSPACE" --directory "$ABX_CI_DATA_DIR" --no-sync --no-sources archivebox install chrome candidate="$(uv run --project "$GITHUB_WORKSPACE" --directory "$ABX_CI_DATA_DIR" --no-sync --no-sources archivebox shell -c 'from archivebox.machine.models import Binary; binary = Binary.objects.filter(name="chromium", status="installed").order_by("-modified_at").first(); print(binary.abspath if binary else "")' | tail -n 1)" if [ ! -x "$candidate" ]; then echo "ArchiveBox did not install an executable Chromium binary: ${candidate:-not found}" >&2 exit 1 fi version="$("$candidate" --version || true)" major="$(printf '%s\n' "$version" | sed -E 's/.* ([0-9]+)\..*/\1/' | head -1)" case "$major" in ''|*[!0-9]*) major=0 ;; esac if [ "$major" -lt "$MIN_CHROMIUM_MAJOR" ]; then echo "ArchiveBox installed Chromium is too old: ${version:-unknown}" >&2 exit 1 fi echo "Using ArchiveBox-installed Chromium: $candidate ($version)" echo "CHROME_BINARY=$candidate" >> "$GITHUB_ENV" - name: Install Sonic through ArchiveBox if: ${{ matrix.plugin.needs_sonic }} env: LIB_DIR: ${{ runner.temp }}/abx-lib ABXPKG_LIB_DIR: ${{ runner.temp }}/abx-lib ABXPKG_INSTALL_TIMEOUT: "900" ABX_CI_DATA_DIR: ${{ runner.temp }}/archivebox-ci-data run: | set -euo pipefail mkdir -p "$LIB_DIR" "$ABX_CI_DATA_DIR" echo "LIB_DIR=$LIB_DIR" >> "$GITHUB_ENV" echo "ABXPKG_LIB_DIR=$ABXPKG_LIB_DIR" >> "$GITHUB_ENV" uv run --project "$GITHUB_WORKSPACE" --directory "$ABX_CI_DATA_DIR" --no-sync --no-sources archivebox install search_backend_sonic candidate="$(uv run --project "$GITHUB_WORKSPACE" --directory "$ABX_CI_DATA_DIR" --no-sync --no-sources archivebox shell -c 'from archivebox.machine.models import Binary; binary = Binary.objects.filter(name="sonic", status="installed").order_by("-modified_at").first(); print(binary.abspath if binary else "")' | tail -n 1)" if [ ! -x "$candidate" ]; then echo "ArchiveBox did not install an executable Sonic binary: ${candidate:-not found}" >&2 exit 1 fi version="$("$candidate" --version || true)" echo "Using ArchiveBox-installed Sonic: $candidate (${version:-version unknown})" echo "SONIC_BINARY=$candidate" >> "$GITHUB_ENV" echo "$(dirname "$candidate")" >> "$GITHUB_PATH" - name: Run plugin tests - ${{ matrix.plugin.name }} env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} TWOCAPTCHA_API_KEY: ${{ secrets.TWOCAPTCHA_API_KEY }} API_KEY_2CAPTCHA: ${{ secrets.TWOCAPTCHA_API_KEY }} run: | uv run --no-sync --no-sources bash ./bin/test_plugins.sh "${{ matrix.plugin.plugin }}" --no-coverage