# Conflicts: # .gitignore # Dockerfile # agent/onboarding.py # apps/desktop/electron/main.ts # apps/desktop/electron/pool-stop.ts # apps/desktop/src/components/model-picker.test.tsx # apps/desktop/src/store/updates.ts # apps/desktop/vite.config.ts # datagen-config-examples/run_browser_tasks.sh # docs/rca-ssl-cacert-post-git-pull.md # gateway/run.py # hermes_cli/backup.py # hermes_cli/credential_lifecycle.py # hermes_cli/dashboard_procs.py # hermes_cli/doctor_state.py # hermes_cli/env_loader.py # hermes_cli/gateway_windows.py # hermes_cli/local_runtime/endpoint.py # hermes_cli/psutil_android.py # hermes_cli/update_cmd.py # hermes_cli/update_cmd_windows.py # hermes_cli/web_routers/local_models.py # hermes_cli/web_server_config.py # hermes_cli/web_server_cron.py # plugins/memory/hindsight/__init__.py # plugins/memory/holographic/__init__.py # plugins/memory/honcho/cli.py # plugins/memory/mem0/__init__.py # plugins/platforms/google_chat/oauth.py # plugins/platforms/photon/adapter.py # scripts/ci/list_os_marked_tests.py # scripts/run_tests.sh # tests/agent/test_compression_stall_fallback.py # tests/agent/test_create_openai_client_ssl_verify.py # tests/gateway/test_google_chat_oauth_dependencies.py # tests/hermes_cli/conftest.py # tests/hermes_cli/test_cli_init.py # tests/hermes_cli/test_gateway_migrate_multiplex.py # tests/hermes_cli/test_psutil_android_extract.py # tests/hermes_cli/test_relaunch.py # tests/hermes_cli/test_update_check.py # tests/hermes_cli/test_update_handoff_desktop_rebuild.py # tests/hermes_cli/test_worktree_gc.py # tests/scripts/desktop_update/test_desktop_update_windows_python_handoff.py # tests/scripts/desktop_update/test_desktop_update_windows_retry_policy.py # tests/scripts/desktop_update/test_desktop_update_windows_timestamp.py # tests/scripts/install/test_install_autostash_conflict_recovery.py # tests/scripts/install/test_install_clone_throttle_fallback.py # tests/scripts/install/test_install_commit_pin_rollback.py # tests/scripts/install/test_install_diverged_update.py # tests/scripts/install/test_install_lockfile_churn.py # tests/scripts/install/test_install_macos_launcher.py # tests/scripts/install/test_install_no_initial_commit.py # tests/scripts/install/test_install_ps1_ascii_only.py # tests/scripts/install/test_install_ps1_browser_install.py # tests/scripts/install/test_install_ps1_managed_node_swap.py # tests/scripts/install/test_install_ps1_native_stderr_eap.py # tests/scripts/install/test_install_ps1_node_path_for_npm.py # tests/scripts/install/test_install_ps1_python_fallback_venv.py # tests/scripts/install/test_install_ps1_resolver_strictmode.py # tests/scripts/install/test_install_ps1_uv_install_fallback.py # tests/scripts/install/test_install_ps1_uv_powershell_host.py # tests/scripts/install/test_install_ps1_venv_process_tree.py # tests/scripts/install/test_install_ps1_venv_recreate_safety.py # tests/scripts/install/test_install_ps1_venv_rename_abort.py # tests/scripts/install/test_install_ps1_venv_transaction_boundary.py # tests/scripts/install/test_install_ps1_web_server_syntax_probe.py # tests/scripts/install/test_install_scripts_computer_use.py # tests/scripts/install/test_install_sh_acp_launcher.py # tests/scripts/install/test_install_sh_bootstrap_marker.py # tests/scripts/install/test_install_sh_browser_install.py # tests/scripts/install/test_install_sh_install_method_stamp.py # tests/scripts/install/test_install_sh_node_deps_failure.py # tests/scripts/install/test_install_sh_node_deps_workspaces.py # tests/scripts/install/test_install_sh_node_global_prefix.py # tests/scripts/install/test_install_sh_node_npm_check.py # tests/scripts/install/test_install_sh_node_prerelease.py # tests/scripts/install/test_install_sh_node_probe.py # tests/scripts/install/test_install_sh_node_tarball_without_xz.py # tests/scripts/install/test_install_sh_pythonpath_sanitization.py # tests/scripts/install/test_install_sh_reuse_supported_python.py # tests/scripts/install/test_install_sh_root_fhs_uv_python_path.py # tests/scripts/install/test_install_sh_setup_wizard_tty_probe.py # tests/scripts/install/test_install_sh_symlink_stomp.py # tests/scripts/install/test_install_sh_termux_network_prereqs.py # tests/scripts/install/test_install_sh_termux_python_bounds.py # tests/scripts/install/test_install_sh_uv_lock_config.py # tests/scripts/install/test_install_unmerged_index.py # tests/scripts/test_run_tests_parallel.py # tests/test_managed_runtime_resolution.py # tests/test_project_metadata.py # tests/tools/test_browser_use_cli.py # tests/tools/test_tts_pythonpath_fallback.py # tests/tui_gateway/test_hosted_room_driver_runtime.py # tests/tui_gateway/test_tui_gateway_server.py # tools/lazy_deps.py # tools/voice_mode.py # uv.lock # website/docs/developer-guide/macos-bundle-updates.md # website/docs/developer-guide/pm-audit-status.md # website/docs/developer-guide/shared-bundle-builds.md # website/docs/developer-guide/source-update-completion.md # website/docs/developer-guide/stable-releases.md
155 lines
7.0 KiB
YAML
155 lines
7.0 KiB
YAML
name: OS-specific tests
|
|
|
|
# Runs the tests that can only be trusted on their own host OS.
|
|
#
|
|
# The main Python suite (.github/workflows/tests.yml) runs on
|
|
# ubuntu-latest and covers everything that is either platform-agnostic or
|
|
# genuinely Linux-specific. Tests whose subject is macOS- or
|
|
# Windows-specific behaviour carry a marker (see the ``_OS_MARKS`` block
|
|
# comment in tests/conftest.py) and are SKIPPED on Linux, because faking
|
|
# ``sys.platform`` on a Linux runner selects the branch under test without
|
|
# reproducing any of the OS behaviour that branch exists for. This workflow
|
|
# is where those markers actually execute:
|
|
#
|
|
# macos → ``-m platforms`` on macos-latest
|
|
# windows → ``-m platforms`` on windows-latest-32-core
|
|
#
|
|
# (tests/conftest.py's ``pytest_collection_modifyitems`` hook skips
|
|
# foreign-OS ``platforms(...)`` markers on each host, so one shared ``-m
|
|
# platforms`` expression selects "this host's own marked tests" on either
|
|
# lane — the helper narrows WHICH FILES get imported first.)
|
|
#
|
|
# Deliberately NOT sliced. The marked set is small (tens of tests, not
|
|
# thousands), so one plain ``pytest`` process per OS is both faster and far
|
|
# less machinery than the per-file parallel runner the Linux lane uses.
|
|
# Each lane FAILS when it selects zero tests (pytest exit code 5) — a
|
|
# renamed marker or bad selector can never report a green job that ran
|
|
# nothing.
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
desktop_updater:
|
|
description: >-
|
|
Run the Windows desktop-update hand-off integration tests
|
|
(tests/scripts/desktop_update/test_desktop_update_windows_*.py). These spawn the real
|
|
scripts/desktop-update/windows.ps1 and poll its loopback server, so
|
|
they carry process-timing noise a shared runner amplifies; the
|
|
caller gates them on the classifier's desktop_updater lane so a PR
|
|
that never touched that surface cannot be failed by it. Push /
|
|
dispatch runs fail open (classifier sets every lane true).
|
|
type: boolean
|
|
required: false
|
|
default: true
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: tests-os-${{ github.ref_type == 'tag' && github.run_id || github.ref }}
|
|
cancel-in-progress: ${{ github.ref_type != 'tag' }}
|
|
|
|
jobs:
|
|
os-tests:
|
|
name: ${{ matrix.name }}
|
|
runs-on: ${{ matrix.runner }}
|
|
timeout-minutes: 30
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- name: macOS-only tests
|
|
runner: macos-latest
|
|
marker: macos
|
|
- name: Windows-only tests
|
|
runner: windows-latest-32-core
|
|
marker: windows
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Set up locked Python and test dependencies
|
|
uses: ./.github/actions/setup-pm
|
|
with:
|
|
extras: '["all", "dev", "anthropic", "mistral", "fal", "modal", "daytona", "hindsight", "parallel-web"]'
|
|
prune-python-cache: true
|
|
|
|
- name: Run ${{ matrix.marker }} tests
|
|
# scripts/run_tests.sh — the canonical runner, same as every other
|
|
# lane: per-file subprocess isolation (run_tests_parallel.py). It
|
|
# natively tolerates per-file empty collections (a platform-gated
|
|
# file collects nothing after -m filtering, exit 5, treated as a
|
|
# pass for that file) and fails the run itself when NOTHING was
|
|
# collected across all files (exit 2) — the zero-tests guard the
|
|
# bare-pytest variant implemented by hand with an exit-5 branch.
|
|
#
|
|
# Selection still narrows WHICH FILES run:
|
|
# scripts/ci/list_os_marked_tests.py emits the file list (it exits
|
|
# non-zero when the marker matches no file at all); the list rides
|
|
# to the runner via ``--files`` so an unrelated ImportError fails
|
|
# only ITS file, red and visible, without poisoning the lane's
|
|
# other files. ``-m`` stays authoritative for which TESTS run —
|
|
# passed after ``--`` so the runner routes it to every per-file
|
|
# pytest invocation (the flag REPLACES pyproject's addopts, hence
|
|
# repeating ``not integration``).
|
|
#
|
|
# ``--timeout-method`` needs no override: tests/conftest.py's
|
|
# pytest_configure already downgrades the signal-based timer on
|
|
# Windows, which has no SIGALRM.
|
|
shell: bash
|
|
run: |
|
|
set -uo pipefail
|
|
|
|
LIST="${RUNNER_TEMP:-.}/selected-tests.txt"
|
|
|
|
# Process substitution would hide the helper's exit status, so write
|
|
# to a file and check it explicitly.
|
|
if ! python scripts/ci/list_os_marked_tests.py \
|
|
"${{ matrix.marker }}" > "$LIST"; then
|
|
echo "::error::could not enumerate ${{ matrix.marker }} test files"
|
|
exit 1
|
|
fi
|
|
if [ ! -s "$LIST" ]; then
|
|
echo "::error::empty ${{ matrix.marker }} file list"
|
|
exit 1
|
|
fi
|
|
|
|
echo "selected file(s) for ${{ matrix.marker }}:"
|
|
cat "$LIST"
|
|
|
|
# Desktop-update hand-off integration tests spawn the real
|
|
# windows.ps1; deselect them unless the PR touched that surface
|
|
# (see the workflow_call input). ``--ignore-glob`` keeps the file
|
|
# list above intact, so a renamed test file still trips the
|
|
# zero-tests guard rather than silently vanishing.
|
|
# (bash 3.2 on the macOS runner: an empty array under ``set -u`` is
|
|
# an unbound-variable error, hence the ``${arr[@]+...}`` idiom.)
|
|
EXTRA_ARGS=()
|
|
if [ "${{ inputs.desktop_updater }}" != "true" ]; then
|
|
echo "desktop_updater lane off: skipping tests/scripts/desktop_update/test_desktop_update_windows_*.py"
|
|
EXTRA_ARGS+=(--ignore-glob='*test_desktop_update_windows_*.py')
|
|
fi
|
|
|
|
# ``tr -d '\r'``: on Windows the helper's redirected stdout gains
|
|
# CRLF line endings; a stray \r would corrupt the path. The list
|
|
# uses the native path-list separator consumed by --files.
|
|
#
|
|
# Any non-zero exit propagates red: real test failures, or the
|
|
# runner's own zero-run guard (every file filtered to empty by
|
|
# ``-m`` — "must never pass without running its OS's tests").
|
|
SEPARATOR="$(python -c 'import os, sys; sys.stdout.write(os.pathsep)')"
|
|
FILES="$(tr -d '\r' < "$LIST" | paste -sd "$SEPARATOR" -)"
|
|
scripts/run_tests.sh --files "$FILES" -- \
|
|
${EXTRA_ARGS[@]+"${EXTRA_ARGS[@]}"} \
|
|
-m "platforms and not integration" \
|
|
-v --tb=short
|
|
env:
|
|
# These files spawn nested PowerShell trees. CPU-count parallelism
|
|
# starves cold child startup before the fixture's idle deadline.
|
|
HERMES_TEST_WORKERS: ${{ matrix.marker == 'windows' && '8' || '' }}
|
|
# Belt-and-suspenders with tests/conftest.py's env blanking: no
|
|
# test may reach a real provider API.
|
|
OPENROUTER_API_KEY: ""
|
|
OPENAI_API_KEY: ""
|
|
NOUS_API_KEY: ""
|