cli: force UTF-8 stdio on startup + use platform-native fake path in … #423
This file contains hidden or 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: Run Tests | ||
| on: | ||
| push: | ||
| branches: [ "main" ] | ||
| pull_request: | ||
| branches: [ "main" ] | ||
| workflow_dispatch: | ||
| permissions: | ||
| contents: read | ||
| env: | ||
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true' | ||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | ||
| cancel-in-progress: true | ||
| jobs: | ||
| precheck: | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 20 | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
| - name: Setup Python | ||
| uses: actions/setup-python@v6 | ||
| with: | ||
| python-version: '3.12' | ||
| - name: Install uv | ||
| uses: astral-sh/setup-uv@v8.0.0 | ||
| with: | ||
| enable-cache: true | ||
| cache-dependency-glob: "uv.lock" | ||
| - name: Setup venv and install pip dependencies | ||
| run: | | ||
| uv venv --python "3.12" \ | ||
| && uv sync --all-extras --all-groups --no-cache \ | ||
| && uv pip install pip \ | ||
| && echo "/home/linuxbrew/.linuxbrew/bin" >> "$GITHUB_PATH" \ | ||
| && if [ -d /nix/var/nix/profiles/default/bin ]; then echo "/nix/var/nix/profiles/default/bin" >> "$GITHUB_PATH"; fi | ||
| - name: Run pre-commit checks | ||
| run: uv run prek run --all-files | ||
| discover-standard-tests: | ||
| needs: precheck | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 20 | ||
| outputs: | ||
| test-files: ${{ steps.set-matrix.outputs.test-files }} | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
| - name: Discover standard test files | ||
| id: set-matrix | ||
| run: | | ||
| json_array="[" | ||
| first=true | ||
| for test_file in $(find tests -maxdepth 1 -name 'test_*.py' -type f | sort); do | ||
| if [ "$first" = true ]; then | ||
| first=false | ||
| else | ||
| json_array+="," | ||
| fi | ||
| test_name=$(basename "$test_file" .py | sed 's/^test_//') | ||
| json_array+="{\"path\":\"$test_file\",\"name\":\"$test_name\"}" | ||
| done | ||
| json_array+="]" | ||
| echo "test-files=$json_array" >> "$GITHUB_OUTPUT" | ||
| echo "$json_array" | ||
| build: | ||
| name: ${{ matrix.target.os }} py${{ matrix.target.python_version }} ${{ matrix.test.name }} | ||
| needs: [precheck, discover-standard-tests] | ||
| runs-on: ${{ matrix.target.os }} | ||
| timeout-minutes: 20 | ||
| if: ${{ needs.discover-standard-tests.outputs.test-files != '[]' }} | ||
| # Windows support is best-effort: the core library (Binary / | ||
| # BinProvider / Scoop / pip / uv / etc.) works on Windows, but a | ||
| # handful of tests still carry POSIX-only assertions (hardcoded | ||
| # ``/tmp`` paths, ``.CMD`` vs no-suffix shim names, CRX extraction | ||
| # that needs a bundled ``unzipper`` npm dep, etc.). Mark the | ||
| # Windows leg as ``experimental`` so CI still surfaces its status | ||
| # without blocking merge on leftover per-test Windows fixups. | ||
| continue-on-error: ${{ matrix.target.experimental || false }} | ||
| # Use git-bash on Windows runners so the (mostly POSIX) setup scripts | ||
| # below keep working without duplicating them in PowerShell. | ||
| defaults: | ||
| run: | ||
| shell: bash | ||
| strategy: | ||
| fail-fast: false | ||
| max-parallel: 20 | ||
| matrix: | ||
| target: | ||
| - os: ubuntu-latest | ||
| python_version: '3.11' | ||
| - os: ubuntu-latest | ||
| python_version: '3.14' | ||
| - os: macOS-latest | ||
| python_version: '3.13' | ||
| - os: windows-latest | ||
| python_version: '3.13' | ||
| experimental: true | ||
| test: ${{ fromJson(needs.discover-standard-tests.outputs.test-files) }} | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
| - name: Setup Python | ||
| uses: actions/setup-python@v6 | ||
| with: | ||
| python-version: ${{ matrix.target.python_version }} | ||
| - name: Install uv | ||
| uses: astral-sh/setup-uv@v8.0.0 | ||
| with: | ||
| enable-cache: true | ||
| cache-dependency-glob: "uv.lock" | ||
| - name: Setup pnpm | ||
| uses: pnpm/action-setup@v5.0.0 | ||
| with: | ||
| version: 10.19.0 | ||
| - name: Setup Node | ||
| uses: actions/setup-node@v6 | ||
| with: | ||
| node-version: '22' | ||
| - name: Setup Yarn (classic + Berry) | ||
| # Uses ``ln -sf`` / Unix prefix dirs — not applicable to the Windows | ||
| # runner and not needed for the Windows test matrix anyway. | ||
| if: runner.os != 'Windows' | ||
| run: | | ||
| npm install -g yarn@1.22.22 | ||
| if [ "$(uname -s)" = "Darwin" ]; then | ||
| YARN_BERRY_PREFIX="/opt/homebrew/opt/yarn-berry" | ||
| YARN_BERRY_ALIAS="/opt/homebrew/bin/yarn-berry" | ||
| elif [ -d /home/linuxbrew/.linuxbrew/opt ]; then | ||
| YARN_BERRY_PREFIX="/home/linuxbrew/.linuxbrew/opt/yarn-berry" | ||
| YARN_BERRY_ALIAS="/home/linuxbrew/.linuxbrew/bin/yarn-berry" | ||
| else | ||
| YARN_BERRY_PREFIX="/usr/local/yarn-berry" | ||
| YARN_BERRY_ALIAS="/usr/local/bin/yarn-berry" | ||
| fi | ||
| YARN_BERRY_ALIAS_DIR="$(dirname "$YARN_BERRY_ALIAS")" | ||
| mkdir -p "$YARN_BERRY_ALIAS_DIR" | ||
| export PATH="$YARN_BERRY_ALIAS_DIR:$PATH" | ||
| echo "$YARN_BERRY_ALIAS_DIR" >> "$GITHUB_PATH" | ||
| npm install --prefix "$YARN_BERRY_PREFIX" @yarnpkg/cli-dist@4.13.0 | ||
| ln -sf "$YARN_BERRY_PREFIX/node_modules/.bin/yarn" "$YARN_BERRY_ALIAS" | ||
| "$YARN_BERRY_ALIAS" --version | grep -q '^4\.' | ||
| command -v yarn | ||
| yarn --version | ||
| command -v yarn-berry | ||
| yarn-berry --version | ||
| yarn --version | grep -q '^1\.' || { echo "ERROR: yarn is not 1.x"; exit 1; } | ||
| - name: Setup Yarn (classic + Berry, Windows) | ||
| # Windows equivalent of the Unix setup: classic yarn via ``npm -g``, | ||
| # then Berry via ``npm --prefix`` + a ``.cmd`` wrapper at a stable | ||
| # ``yarn-berry.cmd`` PATH entry (git-bash has no ``ln -sf``). | ||
| if: runner.os == 'Windows' | ||
| run: | | ||
| npm install -g yarn@1.22.22 | ||
| YARN_BERRY_PREFIX="$USERPROFILE/yarn-berry" | ||
| YARN_BERRY_ALIAS_DIR="$USERPROFILE/yarn-berry-bin" | ||
| mkdir -p "$YARN_BERRY_ALIAS_DIR" | ||
| # GITHUB_PATH is interpreted by runner as \-prefixed lines on Windows. | ||
| echo "$YARN_BERRY_ALIAS_DIR" >> "$GITHUB_PATH" | ||
| npm install --prefix "$YARN_BERRY_PREFIX" @yarnpkg/cli-dist@4.13.0 | ||
| # Emit a one-line ``yarn-berry.cmd`` that forwards to npm-installed | ||
| # ``yarn.cmd``. Use ``%*`` to pass through all args. | ||
| cat > "$YARN_BERRY_ALIAS_DIR/yarn-berry.cmd" <<EOF | ||
| @echo off | ||
| "$YARN_BERRY_PREFIX\\node_modules\\.bin\\yarn.cmd" %* | ||
| EOF | ||
| export PATH="$YARN_BERRY_ALIAS_DIR:$PATH" | ||
| command -v yarn-berry | ||
| yarn-berry --version | grep -q '^4\.' | ||
| yarn --version | grep -q '^1\.' | ||
| - name: Setup Bun | ||
| uses: oven-sh/setup-bun@v2 | ||
| with: | ||
| bun-version: latest | ||
| - name: Setup Deno | ||
| uses: denoland/setup-deno@v2 | ||
| with: | ||
| deno-version: v2.x | ||
| - name: Setup Go | ||
| uses: actions/setup-go@v6 | ||
| with: | ||
| go-version: '1.25' | ||
| - name: Install Nix | ||
| # Nix has no Windows build. | ||
| if: runner.os != 'Windows' | ||
| uses: DeterminateSystems/nix-installer-action@v22 | ||
| - name: Setup venv and install pip dependencies | ||
| run: | | ||
| export PNPM_HOME="${RUNNER_TEMP}/pnpm" | ||
| uv venv --python "${{ matrix.target.python_version }}" | ||
| # ``ansible`` has no Windows build, and ``pyinfra`` pulls it in | ||
| # transitively, so on Windows we install the base extras only. | ||
| if [ "${{ runner.os }}" = "Windows" ]; then | ||
| uv sync | ||
| else | ||
| uv sync --all-extras | ||
| fi | ||
| uv pip install pip | ||
| mkdir -p "$PNPM_HOME" | ||
| echo "$PNPM_HOME" >> "$GITHUB_PATH" | ||
| if [ "${{ runner.os }}" != "Windows" ]; then | ||
| echo "/home/linuxbrew/.linuxbrew/bin" >> "$GITHUB_PATH" | ||
| if [ -d /nix/var/nix/profiles/default/bin ]; then echo "/nix/var/nix/profiles/default/bin" >> "$GITHUB_PATH"; fi | ||
| fi | ||
| - name: Environment diagnostic | ||
| run: | | ||
| echo "=== OS ==="; uname -a | ||
| # Activate the uv-managed venv so ``.venv/bin`` (where | ||
| # ``uv sync --all-extras`` installs pyinfra / ansible / pip / | ||
| # etc.) is on PATH, matching what ``uv run pytest`` sees. On | ||
| # Windows the executable scripts go into ``.venv/Scripts`` instead. | ||
| if [ -d .venv/bin ]; then | ||
| export PATH="$PWD/.venv/bin:$PATH" | ||
| echo "=== venv === $PWD/.venv" | ||
| fi | ||
| if [ -d .venv/Scripts ]; then | ||
| export PATH="$PWD/.venv/Scripts:$PATH" | ||
| echo "=== venv === $PWD/.venv (Windows layout)" | ||
| fi | ||
| echo "=== PATH ==="; echo "$PATH" | tr ':' '\n' | ||
| for bin in python pip uv node npm pnpm yarn bun deno go gem cargo rustc brew apt-get dpkg docker nix nix-env ansible ansible-playbook pyinfra scoop sh bash; do | ||
| path=$(command -v "$bin" 2>/dev/null || true) | ||
| if [ -z "$path" ]; then | ||
| echo "=== $bin === (not installed)" | ||
| continue | ||
| fi | ||
| case "$bin" in | ||
| # go uses the ``version`` subcommand, not a ``--version`` flag | ||
| go) ver=$("$bin" version 2>/dev/null | head -1 || true) ;; | ||
| # some tools print corepack/network noise to stderr during the | ||
| # first version probe; capture stdout only. | ||
| pnpm|yarn|bun|deno|brew) | ||
| ver=$("$bin" --version 2>/dev/null | head -1 || true) ;; | ||
| *) ver=$("$bin" --version 2>&1 | head -1 || true) ;; | ||
| esac | ||
| echo "=== $bin === $path :: $ver" | ||
| done | ||
| - name: Run standard test file | ||
| run: | | ||
| export PNPM_HOME="${RUNNER_TEMP}/pnpm" | ||
| export PATH="$PNPM_HOME:$PATH" | ||
| set +e | ||
| uv run pytest -vv --tb=long -m "not root_required and not docker_required" "${{ matrix.test.path }}" | ||
| status=$? | ||
| set -e | ||
| if [ "$status" -eq 0 ] || [ "$status" -eq 5 ]; then | ||
| exit 0 | ||
| fi | ||
| exit "$status" | ||
| discover-live-tests: | ||
| needs: precheck | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 20 | ||
| outputs: | ||
| live-tests: ${{ steps.set-matrix.outputs.live-tests }} | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
| - name: Discover live integration test files | ||
| id: set-matrix | ||
| run: | | ||
| json_array="[" | ||
| first=true | ||
| for test_file in $(rg -l "def test_provider_direct_methods_exercise_real_lifecycle" tests/test_*.py | sort); do | ||
| test_name=$(basename "$test_file" .py | sed 's/^test_//') | ||
| needs_docker=false | ||
| if grep -q "@pytest.mark.docker_required" "$test_file"; then | ||
| needs_docker=true | ||
| fi | ||
| os_targets="ubuntu-latest macOS-latest" | ||
| if grep -q "@pytest.mark.docker_required" "$test_file" || grep -q "@pytest.mark.root_required" "$test_file" || grep -q 'skipif("darwin"' "$test_file"; then | ||
| os_targets="ubuntu-latest" | ||
| elif grep -q 'require_tool("brew")' "$test_file" && ! grep -q 'require_tool("apt-get")' "$test_file" && ! grep -q "operations.apt.packages" "$test_file" && ! grep -q "ansible.builtin.apt" "$test_file"; then | ||
| os_targets="macOS-latest" | ||
| fi | ||
| for os_target in $os_targets; do | ||
| os_name=$(printf '%s' "$os_target" | tr '[:upper:]' '[:lower:]' | sed 's/-latest//') | ||
| entry="{\"name\":\"${test_name}-${os_name}\",\"path\":\"$test_file\",\"os\":\"$os_target\",\"needs_docker\":$needs_docker}" | ||
| if [ "$first" = true ]; then first=false; else json_array+=","; fi | ||
| json_array+="$entry" | ||
| done | ||
| done | ||
| json_array+="]" | ||
| echo "live-tests=$json_array" >> "$GITHUB_OUTPUT" | ||
| echo "$json_array" | ||
| live-integration: | ||
| name: ${{ matrix.live.name }} | ||
| needs: [precheck, discover-live-tests] | ||
| runs-on: ${{ matrix.live.os }} | ||
| timeout-minutes: 20 | ||
| if: ${{ needs.discover-live-tests.outputs.live-tests != '[]' }} | ||
| strategy: | ||
| fail-fast: false | ||
| max-parallel: 20 | ||
| matrix: | ||
| live: ${{ fromJson(needs.discover-live-tests.outputs.live-tests) }} | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
| - name: Setup Python | ||
| uses: actions/setup-python@v6 | ||
| with: | ||
| python-version: '3.12' | ||
| - name: Install uv | ||
| uses: astral-sh/setup-uv@v8.0.0 | ||
| with: | ||
| enable-cache: true | ||
| cache-dependency-glob: "uv.lock" | ||
| - name: Setup pnpm | ||
| uses: pnpm/action-setup@v5.0.0 | ||
| with: | ||
| version: 10.19.0 | ||
| - name: Setup Node | ||
| uses: actions/setup-node@v6 | ||
| with: | ||
| node-version: '22' | ||
| - name: Setup Yarn (classic + Berry) | ||
| run: | | ||
| npm install -g yarn@1.22.22 | ||
| if [ "$(uname -s)" = "Darwin" ]; then | ||
| YARN_BERRY_PREFIX="/opt/homebrew/opt/yarn-berry" | ||
| YARN_BERRY_ALIAS="/opt/homebrew/bin/yarn-berry" | ||
| elif [ -d /home/linuxbrew/.linuxbrew/opt ]; then | ||
| YARN_BERRY_PREFIX="/home/linuxbrew/.linuxbrew/opt/yarn-berry" | ||
| YARN_BERRY_ALIAS="/home/linuxbrew/.linuxbrew/bin/yarn-berry" | ||
| else | ||
| YARN_BERRY_PREFIX="/usr/local/yarn-berry" | ||
| YARN_BERRY_ALIAS="/usr/local/bin/yarn-berry" | ||
| fi | ||
| YARN_BERRY_ALIAS_DIR="$(dirname "$YARN_BERRY_ALIAS")" | ||
| mkdir -p "$YARN_BERRY_ALIAS_DIR" | ||
| export PATH="$YARN_BERRY_ALIAS_DIR:$PATH" | ||
| echo "$YARN_BERRY_ALIAS_DIR" >> "$GITHUB_PATH" | ||
| npm install --prefix "$YARN_BERRY_PREFIX" @yarnpkg/cli-dist@4.13.0 | ||
| ln -sf "$YARN_BERRY_PREFIX/node_modules/.bin/yarn" "$YARN_BERRY_ALIAS" | ||
| "$YARN_BERRY_ALIAS" --version | grep -q '^4\.' | ||
| command -v yarn | ||
| yarn --version | ||
| command -v yarn-berry | ||
| yarn-berry --version | ||
| yarn --version | grep -q '^1\.' || { echo "ERROR: yarn is not 1.x"; exit 1; } | ||
| - name: Setup Bun | ||
| uses: oven-sh/setup-bun@v2 | ||
| with: | ||
| bun-version: latest | ||
| - name: Setup Deno | ||
| uses: denoland/setup-deno@v2 | ||
| with: | ||
| deno-version: v2.x | ||
| - name: Setup Go | ||
| uses: actions/setup-go@v6 | ||
| with: | ||
| go-version: '1.25' | ||
| - name: Install Nix | ||
| uses: DeterminateSystems/nix-installer-action@v22 | ||
| - name: Setup Docker | ||
| if: ${{ matrix.live.needs_docker }} | ||
| uses: docker/setup-docker-action@v5.0.0 | ||
| env: | ||
| LIMA_START_ARGS: --vm-type=vz | ||
| with: | ||
| daemon-config: | | ||
| { | ||
| "debug": true | ||
| } | ||
| - name: Setup venv and install pip dependencies | ||
| run: | | ||
| export PNPM_HOME="${RUNNER_TEMP}/pnpm" | ||
| uv venv --python "3.12" | ||
| uv sync --all-extras | ||
| uv pip install pip | ||
| mkdir -p "$PNPM_HOME" | ||
| echo "$PNPM_HOME" >> "$GITHUB_PATH" | ||
| echo "/home/linuxbrew/.linuxbrew/bin" >> "$GITHUB_PATH" | ||
| echo "/nix/var/nix/profiles/default/bin" >> "$GITHUB_PATH" | ||
| - name: Inspect Docker | ||
| if: ${{ matrix.live.needs_docker }} | ||
| run: | | ||
| docker version \ | ||
| && docker info | ||
| - name: Run live package lifecycle test | ||
| run: | | ||
| export PNPM_HOME="${RUNNER_TEMP}/pnpm" | ||
| export PATH="$PNPM_HOME:$PATH:/nix/var/nix/profiles/default/bin:$PATH" | ||
| uv run pytest "${{ matrix.live.path }}" -k test_provider_direct_methods_exercise_real_lifecycle | ||