|
| 1 | +#!/usr/bin/env bash |
| 2 | +# |
| 3 | +# Run the SDK integration test suite locally, mirroring the `integration-test` |
| 4 | +# job in .github/workflows/pull_request.yml. |
| 5 | +# |
| 6 | +# The AI/agentic integration tests are excluded on purpose: they target a |
| 7 | +# dedicated AI-enabled server (test_ai_task_types.py and test_ai_examples.py |
| 8 | +# hardcode http://localhost:7001/api, and test_agentic_workflows.py needs an |
| 9 | +# `openai` LLM provider configured on the server), so they don't run against |
| 10 | +# the standard test server this suite targets. The entire tests/integration/ai/ |
| 11 | +# suite is excluded for the same reason: it drives the agent runtime (POST |
| 12 | +# /api/agent/start plus an LLM provider), which the standard server doesn't |
| 13 | +# expose (it 404s /api/agent/start). |
| 14 | +# |
| 15 | +# The performance test (test_update_task_v2_perf.py) is also excluded by |
| 16 | +# default: it submits ~1000 workflows and takes several minutes. Pass |
| 17 | +# --with-perf to include it. |
| 18 | +# |
| 19 | +# Server connection is read from the environment (see |
| 20 | +# src/conductor/client/configuration/configuration.py): |
| 21 | +# CONDUCTOR_SERVER_URL (required; defaults to http://localhost:8080/api) |
| 22 | +# CONDUCTOR_AUTH_KEY (optional; needed for Orkes/authenticated servers) |
| 23 | +# CONDUCTOR_AUTH_SECRET (optional; needed for Orkes/authenticated servers) |
| 24 | +# |
| 25 | +# Usage: |
| 26 | +# export CONDUCTOR_SERVER_URL="http://localhost:8080/api" |
| 27 | +# ./scripts/run_integration_tests.sh |
| 28 | +# ./scripts/run_integration_tests.sh --with-perf # also run the perf test |
| 29 | +# |
| 30 | +# Buckets (--bucket=<name>): the slowest tests are split into their own buckets |
| 31 | +# so they can run as separate parallel CI jobs and are skipped by default |
| 32 | +# locally. Each slow bucket is path-scoped so it only imports its own module. |
| 33 | +# core (default) everything except the slow buckets below |
| 34 | +# long-sync sync lease-extension tests (test_lease_extension.py, ~90s) |
| 35 | +# long-async async lease-extension tests (test_async_lease_extension.py, ~90s) |
| 36 | +# test-all aggregate workflow-client test_all (test_workflow_client_intg.py, ~83s) |
| 37 | +# all the full suite (no bucket filtering) — for a complete local run |
| 38 | +# |
| 39 | +# The long-* buckets deselect the no-heartbeat test_02 cases (marker |
| 40 | +# server_timeout_unreliable): those assert a server-side task timeout that the |
| 41 | +# sdkdev server doesn't reliably fire on a CI-bounded timeline. Use --bucket=all |
| 42 | +# (or target the test directly) to run them anyway. |
| 43 | +# |
| 44 | +# ./scripts/run_integration_tests.sh # fast: skips slow buckets |
| 45 | +# ./scripts/run_integration_tests.sh --bucket=long-sync |
| 46 | +# ./scripts/run_integration_tests.sh --bucket=all # run everything |
| 47 | +# |
| 48 | +# Any other arguments are passed straight through to pytest, which is handy for |
| 49 | +# targeting a subset of tests or getting more detail on failures: |
| 50 | +# |
| 51 | +# # run a subset (by keyword or path) with live output |
| 52 | +# ./scripts/run_integration_tests.sh -s -k lease |
| 53 | +# ./scripts/run_integration_tests.sh tests/integration/test_lease_extension.py |
| 54 | +# |
| 55 | +# # short tracebacks + a one-line reason for every failure/skip, with live logs |
| 56 | +# ./scripts/run_integration_tests.sh -ra --tb=short --log-cli-level=INFO |
| 57 | +# |
| 58 | +# # watch a slow bucket live: -s streams print() and the worker/child-process |
| 59 | +# # task logs; --log-cli-level=INFO streams the main-process logger.info lines |
| 60 | +# ./scripts/run_integration_tests.sh --bucket=long-sync -s --log-cli-level=INFO |
| 61 | +# |
| 62 | +# # same, but with timestamped live logs (what CI uses) |
| 63 | +# ./scripts/run_integration_tests.sh --bucket=long-sync -s \ |
| 64 | +# --log-cli-level=INFO \ |
| 65 | +# --log-cli-format='%(asctime)s %(levelname)s %(name)s: %(message)s' |
| 66 | +# |
| 67 | +# # stop at the first failure instead of waiting for the whole suite |
| 68 | +# ./scripts/run_integration_tests.sh -x --tb=long |
| 69 | +# |
| 70 | +# # re-run just specific tests, e.g. the ones that failed |
| 71 | +# ./scripts/run_integration_tests.sh -s -k "upsert_group or create_role" |
| 72 | +# |
| 73 | +# # show the 15 slowest tests (setup/call/teardown timings) after the run |
| 74 | +# ./scripts/run_integration_tests.sh --durations=15 |
| 75 | + |
| 76 | +set -euo pipefail |
| 77 | + |
| 78 | +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" |
| 79 | +cd "$REPO_ROOT" |
| 80 | + |
| 81 | +# Slow-test files that get their own buckets / CI jobs (see --bucket above). |
| 82 | +LEASE_SYNC=tests/integration/test_lease_extension.py |
| 83 | +LEASE_ASYNC=tests/integration/test_async_lease_extension.py |
| 84 | +TEST_ALL_FILE=tests/integration/test_workflow_client_intg.py |
| 85 | + |
| 86 | +# The perf test is skipped by default; --with-perf opts back in. |
| 87 | +perf_ignore=(--ignore=tests/integration/test_update_task_v2_perf.py) |
| 88 | +bucket="core" |
| 89 | +pytest_args=() |
| 90 | +for arg in "$@"; do |
| 91 | + case "$arg" in |
| 92 | + --with-perf) perf_ignore=() ;; |
| 93 | + --bucket=*) bucket="${arg#*=}" ;; |
| 94 | + *) pytest_args+=("$arg") ;; |
| 95 | + esac |
| 96 | +done |
| 97 | + |
| 98 | +# The AI/agentic tests always target a dedicated server (see header) and are |
| 99 | +# never part of these buckets. The tests/integration/ai/ suite drives the agent |
| 100 | +# runtime (POST /api/agent/start, an LLM provider, SSE streaming): the standard |
| 101 | +# sdkdev server has no agent API and returns 404 for every one of them, so the |
| 102 | +# whole directory is excluded here rather than file-by-file. |
| 103 | +ai_ignore=( |
| 104 | + --ignore=tests/integration/test_ai_task_types.py |
| 105 | + --ignore=tests/integration/test_ai_examples.py |
| 106 | + --ignore=tests/integration/test_agentic_workflows.py |
| 107 | + --ignore=tests/integration/ai |
| 108 | +) |
| 109 | + |
| 110 | +# Build the target paths + selection for the chosen bucket. The slow buckets are |
| 111 | +# path-scoped so each job only imports its own module: this keeps |
| 112 | +# scan_for_annotated_workers from starting unrelated workers and lets the four |
| 113 | +# buckets run in parallel against one server without stealing each other's tasks. |
| 114 | +case "$bucket" in |
| 115 | + core) |
| 116 | + targets=(tests/integration) |
| 117 | + select=("${ai_ignore[@]}" ${perf_ignore[@]+"${perf_ignore[@]}"} \ |
| 118 | + --ignore="$LEASE_SYNC" --ignore="$LEASE_ASYNC" --ignore="$TEST_ALL_FILE") |
| 119 | + ;; |
| 120 | + all) |
| 121 | + targets=(tests/integration) |
| 122 | + select=("${ai_ignore[@]}" ${perf_ignore[@]+"${perf_ignore[@]}"}) |
| 123 | + ;; |
| 124 | + # The slow buckets below target a single specific file, so the perf test is |
| 125 | + # never in scope and perf_ignore is unnecessary here. This also means |
| 126 | + # --with-perf has no effect on these buckets; the perf test is only reachable |
| 127 | + # via the core/all buckets. |
| 128 | + # The no-heartbeat "server should time the task out" cases (test_02) are |
| 129 | + # deselected here: the sdkdev server does not reliably time a task out on a |
| 130 | + # CI-bounded timeline, so they flake for reasons unrelated to the SDK. They |
| 131 | + # carry the server_timeout_unreliable marker and still run under --bucket=all. |
| 132 | + long-sync) |
| 133 | + targets=("$LEASE_SYNC") |
| 134 | + select=(-m "slow_sync and not server_timeout_unreliable") |
| 135 | + ;; |
| 136 | + long-async) |
| 137 | + targets=("$LEASE_ASYNC") |
| 138 | + select=(-m "slow_async and not server_timeout_unreliable") |
| 139 | + ;; |
| 140 | + test-all) |
| 141 | + targets=("$TEST_ALL_FILE") |
| 142 | + select=(-m slow_test_all) |
| 143 | + ;; |
| 144 | + *) |
| 145 | + echo "Unknown --bucket='$bucket' (expected: core, long-sync, long-async, test-all, all)" >&2 |
| 146 | + exit 2 |
| 147 | + ;; |
| 148 | +esac |
| 149 | + |
| 150 | +# Note: the "${arr[@]+"${arr[@]}"}" form is required so empty arrays don't trip |
| 151 | +# "unbound variable" under `set -u` on bash 3.2 (the default macOS bash). |
| 152 | +exec python3 -m pytest -v \ |
| 153 | + "${targets[@]}" \ |
| 154 | + ${select[@]+"${select[@]}"} \ |
| 155 | + ${pytest_args[@]+"${pytest_args[@]}"} |
0 commit comments