forked from CopilotKit/CopilotKit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcmd-test.sh
More file actions
executable file
·229 lines (212 loc) · 9.59 KB
/
Copy pathcmd-test.sh
File metadata and controls
executable file
·229 lines (212 loc) · 9.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
#!/usr/bin/env bash
# showcase test — run probe tests against a showcase service
# Sourced by the main dispatcher; do not execute directly.
CMD_TEST_DESC="Run probe tests against a service"
usage_test() {
cat <<'HELP'
Usage: showcase test <slug> [options]
Run probe tests against a showcase service (via Docker containers).
Options:
--d6 Run D6 (e2e-full) probes only (via fleet control-plane)
--d5 Run D5 (e2e-deep) probes only (via fleet control-plane)
--d4 Run D4 probes only
--direct Legacy/debug: run d5/d6 via the in-process driver
instead of the fleet control-plane (producer->queue->worker)
--smoke Run smoke probes only
--verbose Verbose test output
--headed Run Playwright in headed (visible) mode
--repeat <n> Run N times
--keep Don't stop auto-started packages after test; with --isolate,
also leaves the isolated stack standing (teardown command
printed at exit). A kept stack left running with no owner is
auto-reaped after its keep TTL (default 4h); run
'showcase reap' to tear it down sooner.
--live Write results to PocketBase for dashboard
--rebuild Force Docker rebuild before running
--cycle On failure, auto-dump aimock logs from the test window
--isolate [name] Run in an isolated compose project with offset ports
(default name: showcase-iso<slot>). Allows parallel test runs.
The optional name may appear before OR after the <slug>.
--isolate=<N> Sugar form: pin the isolation slot to N (equivalent to
prefixing SHOWCASE_ISO_SLOT=<N>). 1≤N≤ISOLATE_MAX_SLOT.
--isolate=<name> Sugar form: explicit isolate name (non-numeric), equivalent
to '--isolate <name>'. A bare '--isolate=' is rejected.
Examples:
showcase test mastra --d6 --verbose # D6 probes (full matrix) with verbose output
showcase test mastra --d5 --verbose # D5 probes with verbose output
showcase test mastra --d5 --cycle # D5 + aimock logs on failure
showcase test langgraph-python # all tests for a slug
showcase test mastra --d5 --headed # watch the browser
showcase test agno --d5 --isolate # isolated run (auto-named)
showcase test agno --d5 --isolate d5verify # isolated with explicit name
showcase test agno --d5 --isolate=9 # pin to slot 9 (equiv: SHOWCASE_ISO_SLOT=9 ... --isolate)
HELP
}
cmd_test() {
local slug=""
local cycle=""
local isolate_name=""
local use_isolate=false
local harness_args=()
# Pending `--isolate <token>` name candidate. The space-separated
# `--isolate <name>` form is order-ambiguous when the slug is not yet known:
# `--isolate mastra` could mean "isolate the slug 'mastra' (auto-named)" OR be
# the start of "--isolate <name> <slug>". We defer the decision: stash the
# token here, and resolve it once parsing finishes. If a positional slug also
# appears, the stash was the explicit isolate NAME; if no slug appears, the
# stash WAS the slug (auto-named isolation). See the post-loop resolution.
local pending_iso_name=""
local have_pending_iso_name=false
# Parse arguments — pass most through to the harness CLI
while [[ $# -gt 0 ]]; do
case "$1" in
--d6) harness_args+=(--d6); shift ;;
--d5) harness_args+=(--d5); shift ;;
--d4) harness_args+=(--d4); shift ;;
--smoke) harness_args+=(--smoke); shift ;;
--verbose) harness_args+=(--verbose); shift ;;
--headed) harness_args+=(--headed); shift ;;
--keep) ISOLATE_KEEP=true; harness_args+=(--keep); shift ;;
--live) harness_args+=(--live); shift ;;
--rebuild) harness_args+=(--rebuild); shift ;;
--direct) harness_args+=(--direct); shift ;;
--cycle) cycle=1; shift ;;
--isolate)
use_isolate=true
shift
# Optional name argument: `--isolate [name]`. Consume the next token as
# the isolate name candidate when it is a plain word (not a flag).
# - slug already set → this token is unambiguously the NAME.
# - slug NOT set yet → AMBIGUOUS (could be the name with a slug still
# to come, or the slug itself for an auto-named run). Defer via the
# pending-name stash; the post-loop resolution decides based on
# whether a positional slug also turns up.
# A following flag (or nothing) means no explicit name → auto-named.
if [[ $# -gt 0 ]] && [[ "$1" != --* ]]; then
if [[ -n "$slug" ]]; then
isolate_name="$1"
else
pending_iso_name="$1"
have_pending_iso_name=true
fi
shift
fi
;;
--isolate=*)
# Sugar form. Two shapes share this branch:
# --isolate=<N> pins the slot by exporting SHOWCASE_ISO_SLOT; the
# picker (_claim_isolate_slot in _common.sh) owns ALL
# validation (positive int, 1≤N≤ISOLATE_MAX_SLOT,
# slot 0 reserved, port probe).
# --isolate=<name> an explicit isolate name (non-numeric), bound here.
# A bare `--isolate=` (empty value) is rejected LOUDLY: left unguarded it
# exports an empty SHOWCASE_ISO_SLOT that fails the picker's `-n` test and
# silently falls through to auto-pick, bypassing the pinned-path checks.
use_isolate=true
local iso_val="${1#--isolate=}"
if [[ -z "$iso_val" ]]; then
die "--isolate= requires a value (slot number or name); got an empty value (see 'showcase test --help')"
elif [[ "$iso_val" =~ ^[0-9]+$ ]]; then
SHOWCASE_ISO_SLOT="$iso_val"
export SHOWCASE_ISO_SLOT
else
isolate_name="$iso_val"
fi
shift
;;
--repeat)
shift
harness_args+=(--repeat "${1:?--repeat requires a value}")
shift
;;
-h|--help)
usage_test
return 0
;;
-*)
die "Unknown option: $1 (see 'showcase test --help')"
;;
*)
if [[ -z "$slug" ]]; then
slug="$1"
else
die "Unexpected argument: $1"
fi
shift
;;
esac
done
# Resolve the deferred `--isolate <token>` name candidate now that all
# positionals are known (see pending_iso_name above):
# - slug present → the pending token was the explicit isolate NAME.
# - slug absent → the pending token WAS the slug (auto-named isolation).
# This is what lets BOTH `--isolate <name> <slug>` (name first) and the
# auto-named `--isolate <slug>` parse correctly from the same ambiguous token.
if $have_pending_iso_name; then
if [[ -n "$slug" ]]; then
isolate_name="$pending_iso_name"
else
slug="$pending_iso_name"
fi
fi
need_slug "$slug"
# Apply isolation if requested (must happen before any compose commands).
# Register the trap BEFORE apply_isolation so cleanup runs even if the
# function itself crashes partway through. restore_isolation reads the
# ISOLATE_KEEP global (set above when --keep is parsed). It MUST be a global,
# not a local: on the normal path cmd_test returns and its locals unwind
# before the EXIT trap fires at top-level script exit, so a function-local
# flag would silently read as false there (a local is only visible to the
# trap when `die` exits from inside cmd_test itself). Under --keep,
# restore_isolation leaves the stack standing and prints a survival notice
# instead of tearing down, so the slot's live containers keep it from being
# reaped.
if $use_isolate; then
trap restore_isolation EXIT
apply_isolation "${isolate_name:-}" "$slug"
if $ISOLATE_KEEP; then
info "--keep set: isolated stack will be left standing after the run (teardown command printed at exit)"
fi
fi
# Build the filter description for the info line
local filter_desc=""
for arg in ${harness_args[@]+"${harness_args[@]}"}; do
case "$arg" in
--d6|--d5|--d4|--smoke) filter_desc="${filter_desc:+$filter_desc,}$arg" ;;
esac
done
# If --cycle, record aimock log position before the test
local pre_test_ts=""
local aimock_container
if $use_isolate && [[ -n "$ISOLATE_NAME" ]]; then
aimock_container="${ISOLATE_NAME}-aimock"
else
aimock_container="showcase-aimock"
fi
if [[ -n "$cycle" ]]; then
if docker ps --format '{{.Names}}' 2>/dev/null | grep -q "^${aimock_container}$"; then
pre_test_ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
else
warn "aimock container '$aimock_container' not running; --cycle log capture disabled"
fi
fi
info "Testing $slug${filter_desc:+ ($filter_desc)}..."
date -u +%Y-%m-%dT%H:%M:%SZ > "$SHOWCASE_ROOT/.last-test-ts"
local test_exit=0
npx tsx "$SHOWCASE_ROOT/harness/src/cli.ts" test "$slug" ${harness_args[@]+"${harness_args[@]}"} \
|| test_exit=$?
# --cycle: dump aimock log delta on failure
if [[ $test_exit -ne 0 ]] && [[ -n "$cycle" ]] && [[ -n "$pre_test_ts" ]]; then
echo ""
echo "═══ aimock logs since test start ($pre_test_ts) ═══"
docker logs --since "$pre_test_ts" "$aimock_container" 2>&1
echo "═══════════════════════════════════════════════════"
fi
# Report result
if [[ $test_exit -eq 0 ]]; then
success "Tests passed for $slug"
else
warn "Tests failed for $slug (exit $test_exit)"
fi
return $test_exit
}