shipwright-cli 1.10.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -36
- package/completions/_shipwright +212 -32
- package/completions/shipwright.bash +97 -25
- package/docs/strategy/01-market-research.md +619 -0
- package/docs/strategy/02-mission-and-brand.md +587 -0
- package/docs/strategy/03-gtm-and-roadmap.md +759 -0
- package/docs/strategy/QUICK-START.txt +289 -0
- package/docs/strategy/README.md +172 -0
- package/package.json +4 -2
- package/scripts/sw +208 -1
- package/scripts/sw-activity.sh +500 -0
- package/scripts/sw-adaptive.sh +925 -0
- package/scripts/sw-adversarial.sh +1 -1
- package/scripts/sw-architecture-enforcer.sh +1 -1
- package/scripts/sw-auth.sh +613 -0
- package/scripts/sw-autonomous.sh +664 -0
- package/scripts/sw-changelog.sh +704 -0
- package/scripts/sw-checkpoint.sh +1 -1
- package/scripts/sw-ci.sh +602 -0
- package/scripts/sw-cleanup.sh +1 -1
- package/scripts/sw-code-review.sh +637 -0
- package/scripts/sw-connect.sh +1 -1
- package/scripts/sw-context.sh +605 -0
- package/scripts/sw-cost.sh +1 -1
- package/scripts/sw-daemon.sh +432 -130
- package/scripts/sw-dashboard.sh +1 -1
- package/scripts/sw-db.sh +540 -0
- package/scripts/sw-decompose.sh +539 -0
- package/scripts/sw-deps.sh +551 -0
- package/scripts/sw-developer-simulation.sh +1 -1
- package/scripts/sw-discovery.sh +412 -0
- package/scripts/sw-docs-agent.sh +539 -0
- package/scripts/sw-docs.sh +1 -1
- package/scripts/sw-doctor.sh +59 -1
- package/scripts/sw-dora.sh +615 -0
- package/scripts/sw-durable.sh +710 -0
- package/scripts/sw-e2e-orchestrator.sh +535 -0
- package/scripts/sw-eventbus.sh +393 -0
- package/scripts/sw-feedback.sh +471 -0
- package/scripts/sw-fix.sh +1 -1
- package/scripts/sw-fleet-discover.sh +567 -0
- package/scripts/sw-fleet-viz.sh +404 -0
- package/scripts/sw-fleet.sh +8 -1
- package/scripts/sw-github-app.sh +596 -0
- package/scripts/sw-github-checks.sh +1 -1
- package/scripts/sw-github-deploy.sh +1 -1
- package/scripts/sw-github-graphql.sh +1 -1
- package/scripts/sw-guild.sh +569 -0
- package/scripts/sw-heartbeat.sh +1 -1
- package/scripts/sw-hygiene.sh +559 -0
- package/scripts/sw-incident.sh +617 -0
- package/scripts/sw-init.sh +88 -1
- package/scripts/sw-instrument.sh +699 -0
- package/scripts/sw-intelligence.sh +1 -1
- package/scripts/sw-jira.sh +1 -1
- package/scripts/sw-launchd.sh +363 -28
- package/scripts/sw-linear.sh +1 -1
- package/scripts/sw-logs.sh +1 -1
- package/scripts/sw-loop.sh +64 -3
- package/scripts/sw-memory.sh +1 -1
- package/scripts/sw-mission-control.sh +487 -0
- package/scripts/sw-model-router.sh +545 -0
- package/scripts/sw-otel.sh +596 -0
- package/scripts/sw-oversight.sh +689 -0
- package/scripts/sw-pipeline-composer.sh +1 -1
- package/scripts/sw-pipeline-vitals.sh +1 -1
- package/scripts/sw-pipeline.sh +687 -24
- package/scripts/sw-pm.sh +693 -0
- package/scripts/sw-pr-lifecycle.sh +522 -0
- package/scripts/sw-predictive.sh +1 -1
- package/scripts/sw-prep.sh +1 -1
- package/scripts/sw-ps.sh +1 -1
- package/scripts/sw-public-dashboard.sh +798 -0
- package/scripts/sw-quality.sh +595 -0
- package/scripts/sw-reaper.sh +1 -1
- package/scripts/sw-recruit.sh +573 -0
- package/scripts/sw-regression.sh +642 -0
- package/scripts/sw-release-manager.sh +736 -0
- package/scripts/sw-release.sh +706 -0
- package/scripts/sw-remote.sh +1 -1
- package/scripts/sw-replay.sh +520 -0
- package/scripts/sw-retro.sh +691 -0
- package/scripts/sw-scale.sh +444 -0
- package/scripts/sw-security-audit.sh +505 -0
- package/scripts/sw-self-optimize.sh +1 -1
- package/scripts/sw-session.sh +1 -1
- package/scripts/sw-setup.sh +1 -1
- package/scripts/sw-standup.sh +712 -0
- package/scripts/sw-status.sh +1 -1
- package/scripts/sw-strategic.sh +658 -0
- package/scripts/sw-stream.sh +450 -0
- package/scripts/sw-swarm.sh +583 -0
- package/scripts/sw-team-stages.sh +511 -0
- package/scripts/sw-templates.sh +1 -1
- package/scripts/sw-testgen.sh +515 -0
- package/scripts/sw-tmux-pipeline.sh +554 -0
- package/scripts/sw-tmux.sh +1 -1
- package/scripts/sw-trace.sh +485 -0
- package/scripts/sw-tracker-github.sh +188 -0
- package/scripts/sw-tracker-jira.sh +172 -0
- package/scripts/sw-tracker-linear.sh +251 -0
- package/scripts/sw-tracker.sh +117 -2
- package/scripts/sw-triage.sh +603 -0
- package/scripts/sw-upgrade.sh +1 -1
- package/scripts/sw-ux.sh +677 -0
- package/scripts/sw-webhook.sh +627 -0
- package/scripts/sw-widgets.sh +530 -0
- package/scripts/sw-worktree.sh +1 -1
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ╔═══════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
# ║ shipwright e2e orchestrator — Test suite registry & execution ║
|
|
4
|
+
# ║ Smoke tests, integration tests, regression management, parallel exec ║
|
|
5
|
+
# ╚═══════════════════════════════════════════════════════════════════════════╝
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
VERSION="2.0.0"
|
|
9
|
+
|
|
10
|
+
# ─── Script directory resolution ────────────────────────────────────────────
|
|
11
|
+
SOURCE="${BASH_SOURCE[0]}"
|
|
12
|
+
while [[ -L "$SOURCE" ]]; do
|
|
13
|
+
DIR="$(cd "$(dirname "$SOURCE")" && pwd)"
|
|
14
|
+
SOURCE="$(readlink "$SOURCE")"
|
|
15
|
+
[[ "$SOURCE" != /* ]] && SOURCE="$DIR/$SOURCE"
|
|
16
|
+
done
|
|
17
|
+
SCRIPT_DIR="$(cd "$(dirname "$SOURCE")" && pwd)"
|
|
18
|
+
|
|
19
|
+
# ─── Colors (matches Seth's tmux theme) ─────────────────────────────────────
|
|
20
|
+
CYAN='\033[38;2;0;212;255m' # #00d4ff — primary accent
|
|
21
|
+
PURPLE='\033[38;2;124;58;237m' # #7c3aed — secondary
|
|
22
|
+
BLUE='\033[38;2;0;102;255m' # #0066ff — tertiary
|
|
23
|
+
GREEN='\033[38;2;74;222;128m' # success
|
|
24
|
+
YELLOW='\033[38;2;250;204;21m' # warning
|
|
25
|
+
RED='\033[38;2;248;113;113m' # error
|
|
26
|
+
DIM='\033[2m'
|
|
27
|
+
BOLD='\033[1m'
|
|
28
|
+
RESET='\033[0m'
|
|
29
|
+
|
|
30
|
+
# ─── State directories ──────────────────────────────────────────────────────
|
|
31
|
+
E2E_DIR="${HOME}/.shipwright/e2e"
|
|
32
|
+
SUITE_REGISTRY="$E2E_DIR/suite-registry.json"
|
|
33
|
+
FLAKY_CACHE="$E2E_DIR/flaky-cache.json"
|
|
34
|
+
RESULTS_LOG="$E2E_DIR/results.jsonl"
|
|
35
|
+
LATEST_REPORT="$E2E_DIR/latest-report.json"
|
|
36
|
+
|
|
37
|
+
# ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
38
|
+
info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
|
|
39
|
+
success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
|
|
40
|
+
warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
|
|
41
|
+
error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
|
|
42
|
+
|
|
43
|
+
emit_event() {
|
|
44
|
+
local event_type="$1"
|
|
45
|
+
shift
|
|
46
|
+
local data=""
|
|
47
|
+
for pair in "$@"; do
|
|
48
|
+
[[ -n "$data" ]] && data="$data "
|
|
49
|
+
data="$data$pair"
|
|
50
|
+
done
|
|
51
|
+
local timestamp=$(date -u +%s)
|
|
52
|
+
echo "{\"timestamp\":$timestamp,\"type\":\"$event_type\",$data}" >> "$RESULTS_LOG"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
ensure_state_dir() {
|
|
56
|
+
mkdir -p "$E2E_DIR"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# ─── Initialize suite registry ──────────────────────────────────────────────
|
|
60
|
+
init_registry() {
|
|
61
|
+
ensure_state_dir
|
|
62
|
+
if [[ ! -f "$SUITE_REGISTRY" ]]; then
|
|
63
|
+
cat > "$SUITE_REGISTRY" <<'EOF'
|
|
64
|
+
{
|
|
65
|
+
"suites": [
|
|
66
|
+
{
|
|
67
|
+
"id": "smoke",
|
|
68
|
+
"name": "Smoke Tests",
|
|
69
|
+
"category": "smoke",
|
|
70
|
+
"description": "Quick validation (<30s): CLI routing, help text, basic commands",
|
|
71
|
+
"script": "sw-e2e-smoke-test.sh",
|
|
72
|
+
"features": ["cli-routing", "help-text", "basic-commands"],
|
|
73
|
+
"timeout_seconds": 30,
|
|
74
|
+
"enabled": true
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"id": "integration",
|
|
78
|
+
"name": "Integration Tests",
|
|
79
|
+
"category": "integration",
|
|
80
|
+
"description": "Cross-component tests: pipeline→daemon, memory→pipeline, tracker→daemon",
|
|
81
|
+
"script": "sw-e2e-integration-test.sh",
|
|
82
|
+
"features": ["pipeline", "daemon", "memory", "tracker"],
|
|
83
|
+
"timeout_seconds": 600,
|
|
84
|
+
"enabled": true
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"id": "regression",
|
|
88
|
+
"name": "Regression Suite",
|
|
89
|
+
"category": "regression",
|
|
90
|
+
"description": "Full regression suite: all known failures, edge cases",
|
|
91
|
+
"script": "sw-daemon-test.sh",
|
|
92
|
+
"features": ["daemon", "metrics", "health"],
|
|
93
|
+
"timeout_seconds": 300,
|
|
94
|
+
"enabled": true
|
|
95
|
+
}
|
|
96
|
+
],
|
|
97
|
+
"flaky_tests": [],
|
|
98
|
+
"quarantine": [],
|
|
99
|
+
"last_updated": 0
|
|
100
|
+
}
|
|
101
|
+
EOF
|
|
102
|
+
success "Initialized suite registry at $SUITE_REGISTRY"
|
|
103
|
+
fi
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
# ─── Load and validate registry ─────────────────────────────────────────────
|
|
107
|
+
load_registry() {
|
|
108
|
+
ensure_state_dir
|
|
109
|
+
if [[ ! -f "$SUITE_REGISTRY" ]]; then
|
|
110
|
+
init_registry
|
|
111
|
+
fi
|
|
112
|
+
cat "$SUITE_REGISTRY"
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# ─── Register a new test suite ──────────────────────────────────────────────
|
|
116
|
+
cmd_register() {
|
|
117
|
+
local suite_id="$1"
|
|
118
|
+
local suite_name="${2:-$suite_id}"
|
|
119
|
+
local category="${3:-custom}"
|
|
120
|
+
local features_str="${4:-}"
|
|
121
|
+
|
|
122
|
+
ensure_state_dir
|
|
123
|
+
|
|
124
|
+
if [[ ! -f "$SUITE_REGISTRY" ]]; then
|
|
125
|
+
init_registry
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
# Parse existing registry
|
|
129
|
+
local registry=$(load_registry)
|
|
130
|
+
local new_suite
|
|
131
|
+
|
|
132
|
+
# Create feature array
|
|
133
|
+
local features="[]"
|
|
134
|
+
if [[ -n "$features_str" ]]; then
|
|
135
|
+
features=$(echo "$features_str" | jq -R 'split(",") | map(select(length > 0))')
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
# Build new suite entry
|
|
139
|
+
new_suite=$(jq -n \
|
|
140
|
+
--arg id "$suite_id" \
|
|
141
|
+
--arg name "$suite_name" \
|
|
142
|
+
--arg cat "$category" \
|
|
143
|
+
--argjson feats "$features" \
|
|
144
|
+
'{
|
|
145
|
+
id: $id,
|
|
146
|
+
name: $name,
|
|
147
|
+
category: $cat,
|
|
148
|
+
description: "",
|
|
149
|
+
script: "sw-\($id)-test.sh",
|
|
150
|
+
features: $feats,
|
|
151
|
+
timeout_seconds: 300,
|
|
152
|
+
enabled: true
|
|
153
|
+
}')
|
|
154
|
+
|
|
155
|
+
# Check if suite already exists
|
|
156
|
+
if echo "$registry" | jq -e ".suites[] | select(.id == \"$suite_id\")" > /dev/null 2>&1; then
|
|
157
|
+
error "Suite '$suite_id' already registered"
|
|
158
|
+
return 1
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
# Add to registry
|
|
162
|
+
registry=$(echo "$registry" | jq ".suites += [$new_suite] | .last_updated = $(date +%s)")
|
|
163
|
+
|
|
164
|
+
# Atomic write
|
|
165
|
+
local tmp_file
|
|
166
|
+
tmp_file=$(mktemp)
|
|
167
|
+
echo "$registry" | jq '.' > "$tmp_file"
|
|
168
|
+
mv "$tmp_file" "$SUITE_REGISTRY"
|
|
169
|
+
|
|
170
|
+
success "Registered suite: $suite_id"
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
# ─── Quarantine a flaky test ───────────────────────────────────────────────
|
|
174
|
+
cmd_quarantine() {
|
|
175
|
+
local test_name="$1"
|
|
176
|
+
local reason="${2:-Intermittent failures}"
|
|
177
|
+
local action="${3:-quarantine}" # quarantine or unquarantine
|
|
178
|
+
|
|
179
|
+
ensure_state_dir
|
|
180
|
+
|
|
181
|
+
if [[ ! -f "$SUITE_REGISTRY" ]]; then
|
|
182
|
+
init_registry
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
local registry=$(load_registry)
|
|
186
|
+
|
|
187
|
+
if [[ "$action" == "quarantine" ]]; then
|
|
188
|
+
# Add to quarantine list if not already present
|
|
189
|
+
if ! echo "$registry" | jq -e ".quarantine[] | select(. == \"$test_name\")" > /dev/null 2>&1; then
|
|
190
|
+
registry=$(echo "$registry" | jq ".quarantine += [\"$test_name\"] | .last_updated = $(date +%s)")
|
|
191
|
+
local tmp_file
|
|
192
|
+
tmp_file=$(mktemp)
|
|
193
|
+
echo "$registry" | jq '.' > "$tmp_file"
|
|
194
|
+
mv "$tmp_file" "$SUITE_REGISTRY"
|
|
195
|
+
success "Quarantined: $test_name — $reason"
|
|
196
|
+
else
|
|
197
|
+
warn "$test_name already quarantined"
|
|
198
|
+
fi
|
|
199
|
+
else
|
|
200
|
+
# Remove from quarantine
|
|
201
|
+
registry=$(echo "$registry" | jq ".quarantine |= map(select(. != \"$test_name\")) | .last_updated = $(date +%s)")
|
|
202
|
+
local tmp_file
|
|
203
|
+
tmp_file=$(mktemp)
|
|
204
|
+
echo "$registry" | jq '.' > "$tmp_file"
|
|
205
|
+
mv "$tmp_file" "$SUITE_REGISTRY"
|
|
206
|
+
success "Unquarantined: $test_name"
|
|
207
|
+
fi
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
# ─── Run a single test suite ────────────────────────────────────────────────
|
|
211
|
+
run_suite() {
|
|
212
|
+
local suite_id="$1"
|
|
213
|
+
local registry=$(load_registry)
|
|
214
|
+
|
|
215
|
+
# Find suite
|
|
216
|
+
local suite=$(echo "$registry" | jq ".suites[] | select(.id == \"$suite_id\")")
|
|
217
|
+
|
|
218
|
+
if [[ -z "$suite" ]]; then
|
|
219
|
+
error "Suite not found: $suite_id"
|
|
220
|
+
return 1
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
local suite_name=$(echo "$suite" | jq -r '.name')
|
|
224
|
+
local script=$(echo "$suite" | jq -r '.script')
|
|
225
|
+
local timeout=$(echo "$suite" | jq -r '.timeout_seconds')
|
|
226
|
+
local features=$(echo "$suite" | jq -r '.features | join(", ")')
|
|
227
|
+
|
|
228
|
+
local test_script="$SCRIPT_DIR/$script"
|
|
229
|
+
|
|
230
|
+
if [[ ! -f "$test_script" ]]; then
|
|
231
|
+
error "Test script not found: $test_script"
|
|
232
|
+
return 1
|
|
233
|
+
fi
|
|
234
|
+
|
|
235
|
+
info "Running: $suite_name ($features)"
|
|
236
|
+
local start_time=$(date +%s)
|
|
237
|
+
|
|
238
|
+
# Run with timeout
|
|
239
|
+
local exit_code=0
|
|
240
|
+
timeout "$timeout" bash "$test_script" || exit_code=$?
|
|
241
|
+
|
|
242
|
+
local end_time=$(date +%s)
|
|
243
|
+
local duration=$((end_time - start_time))
|
|
244
|
+
|
|
245
|
+
# Log result
|
|
246
|
+
emit_event "suite_complete" \
|
|
247
|
+
"\"suite_id\":\"$suite_id\"" \
|
|
248
|
+
"\"suite_name\":\"$suite_name\"" \
|
|
249
|
+
"\"exit_code\":$exit_code" \
|
|
250
|
+
"\"duration_seconds\":$duration"
|
|
251
|
+
|
|
252
|
+
return $exit_code
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
# ─── Run suites in parallel ─────────────────────────────────────────────────
|
|
256
|
+
run_parallel() {
|
|
257
|
+
local category="${1:-}"
|
|
258
|
+
local max_parallel=${2:-3}
|
|
259
|
+
|
|
260
|
+
ensure_state_dir
|
|
261
|
+
> "$RESULTS_LOG" # Clear results log
|
|
262
|
+
|
|
263
|
+
local registry=$(load_registry)
|
|
264
|
+
|
|
265
|
+
# Filter suites by category and enabled status
|
|
266
|
+
local suites
|
|
267
|
+
if [[ -n "$category" ]]; then
|
|
268
|
+
suites=$(echo "$registry" | jq -r ".suites[] | select(.category == \"$category\" and .enabled) | .id")
|
|
269
|
+
else
|
|
270
|
+
suites=$(echo "$registry" | jq -r ".suites[] | select(.enabled) | .id")
|
|
271
|
+
fi
|
|
272
|
+
|
|
273
|
+
local suite_array=()
|
|
274
|
+
while IFS= read -r suite; do
|
|
275
|
+
[[ -n "$suite" ]] && suite_array+=("$suite")
|
|
276
|
+
done <<< "$suites"
|
|
277
|
+
|
|
278
|
+
if [[ ${#suite_array[@]} -eq 0 ]]; then
|
|
279
|
+
warn "No suites to run"
|
|
280
|
+
return 0
|
|
281
|
+
fi
|
|
282
|
+
|
|
283
|
+
info "Running ${#suite_array[@]} test suite(s) in parallel (max $max_parallel workers)"
|
|
284
|
+
|
|
285
|
+
local pids=()
|
|
286
|
+
local running=0
|
|
287
|
+
local idx=0
|
|
288
|
+
local failed_suites=()
|
|
289
|
+
|
|
290
|
+
# Spawn initial batch
|
|
291
|
+
for (( i = 0; i < max_parallel && i < ${#suite_array[@]}; i++ )); do
|
|
292
|
+
run_suite "${suite_array[$i]}" &
|
|
293
|
+
pids+=($!)
|
|
294
|
+
((running++))
|
|
295
|
+
((idx++))
|
|
296
|
+
done
|
|
297
|
+
|
|
298
|
+
# Process remaining suites as workers finish
|
|
299
|
+
while [[ $running -gt 0 ]]; do
|
|
300
|
+
for i in "${!pids[@]}"; do
|
|
301
|
+
if ! kill -0 "${pids[$i]}" 2>/dev/null; then
|
|
302
|
+
# Process finished
|
|
303
|
+
wait "${pids[$i]}" || failed_suites+=("${suite_array[$((i + max_parallel - running))]}")
|
|
304
|
+
|
|
305
|
+
# Spawn next if available
|
|
306
|
+
if [[ $idx -lt ${#suite_array[@]} ]]; then
|
|
307
|
+
run_suite "${suite_array[$idx]}" &
|
|
308
|
+
pids[$i]=$!
|
|
309
|
+
((idx++))
|
|
310
|
+
else
|
|
311
|
+
unset 'pids[$i]'
|
|
312
|
+
fi
|
|
313
|
+
((running--))
|
|
314
|
+
fi
|
|
315
|
+
done
|
|
316
|
+
sleep 0.1
|
|
317
|
+
done
|
|
318
|
+
|
|
319
|
+
# Wait for all to finish
|
|
320
|
+
local exit_code=0
|
|
321
|
+
for pid in "${pids[@]}"; do
|
|
322
|
+
wait "$pid" || exit_code=1
|
|
323
|
+
done
|
|
324
|
+
|
|
325
|
+
return $exit_code
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
# ─── Generate test report ──────────────────────────────────────────────────
|
|
329
|
+
cmd_report() {
|
|
330
|
+
ensure_state_dir
|
|
331
|
+
|
|
332
|
+
if [[ ! -f "$RESULTS_LOG" ]]; then
|
|
333
|
+
warn "No test results found"
|
|
334
|
+
return 0
|
|
335
|
+
fi
|
|
336
|
+
|
|
337
|
+
info "Generating test report..."
|
|
338
|
+
|
|
339
|
+
local pass=0
|
|
340
|
+
local fail=0
|
|
341
|
+
local timeout=0
|
|
342
|
+
local skip=0
|
|
343
|
+
|
|
344
|
+
while IFS= read -r line; do
|
|
345
|
+
local exit_code=$(echo "$line" | jq -r '.exit_code // 0')
|
|
346
|
+
if [[ $exit_code -eq 0 ]]; then
|
|
347
|
+
((pass++))
|
|
348
|
+
elif [[ $exit_code -eq 124 ]]; then
|
|
349
|
+
((timeout++))
|
|
350
|
+
else
|
|
351
|
+
((fail++))
|
|
352
|
+
fi
|
|
353
|
+
done < "$RESULTS_LOG"
|
|
354
|
+
|
|
355
|
+
local total=$((pass + fail + timeout + skip))
|
|
356
|
+
|
|
357
|
+
# Create report
|
|
358
|
+
local report=$(jq -n \
|
|
359
|
+
--arg ts "$(date -Iseconds)" \
|
|
360
|
+
--arg version "$VERSION" \
|
|
361
|
+
--argjson p "$pass" \
|
|
362
|
+
--argjson f "$fail" \
|
|
363
|
+
--argjson t "$timeout" \
|
|
364
|
+
--argjson s "$skip" \
|
|
365
|
+
--argjson total "$total" \
|
|
366
|
+
'{
|
|
367
|
+
timestamp: $ts,
|
|
368
|
+
version: $version,
|
|
369
|
+
summary: {
|
|
370
|
+
total: $total,
|
|
371
|
+
passed: $p,
|
|
372
|
+
failed: $f,
|
|
373
|
+
timeout: $t,
|
|
374
|
+
skipped: $s,
|
|
375
|
+
pass_rate: ($p / $total * 100 | round | tostring + "%")
|
|
376
|
+
},
|
|
377
|
+
details: input
|
|
378
|
+
}' < <(jq -s '.' "$RESULTS_LOG"))
|
|
379
|
+
|
|
380
|
+
# Atomic write
|
|
381
|
+
local tmp_file
|
|
382
|
+
tmp_file=$(mktemp)
|
|
383
|
+
echo "$report" | jq '.' > "$tmp_file"
|
|
384
|
+
mv "$tmp_file" "$LATEST_REPORT"
|
|
385
|
+
|
|
386
|
+
# Display
|
|
387
|
+
echo ""
|
|
388
|
+
echo -e "${CYAN}${BOLD}━━━ Test Report ━━━${RESET}"
|
|
389
|
+
echo "$report" | jq '.summary'
|
|
390
|
+
echo ""
|
|
391
|
+
|
|
392
|
+
if [[ $fail -gt 0 ]]; then
|
|
393
|
+
error "Tests failed: $fail/$total"
|
|
394
|
+
return 1
|
|
395
|
+
else
|
|
396
|
+
success "All tests passed: $total/$total"
|
|
397
|
+
return 0
|
|
398
|
+
fi
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
# ─── Show flaky test analysis ───────────────────────────────────────────────
|
|
402
|
+
cmd_flaky() {
|
|
403
|
+
ensure_state_dir
|
|
404
|
+
|
|
405
|
+
if [[ ! -f "$RESULTS_LOG" ]]; then
|
|
406
|
+
warn "No test history found"
|
|
407
|
+
return 0
|
|
408
|
+
fi
|
|
409
|
+
|
|
410
|
+
info "Analyzing flaky tests..."
|
|
411
|
+
|
|
412
|
+
# Group by test name, count passes/fails
|
|
413
|
+
local flaky_analysis=$(jq -s 'group_by(.suite_id) | map({
|
|
414
|
+
test: .[0].suite_id,
|
|
415
|
+
runs: length,
|
|
416
|
+
passes: (map(select(.exit_code == 0)) | length),
|
|
417
|
+
failures: (map(select(.exit_code != 0)) | length)
|
|
418
|
+
}) | map(select(.failures > 0 and .passes > 0))' "$RESULTS_LOG" 2>/dev/null || echo '[]')
|
|
419
|
+
|
|
420
|
+
echo ""
|
|
421
|
+
echo -e "${CYAN}${BOLD}━━━ Flaky Tests ━━━${RESET}"
|
|
422
|
+
echo "$flaky_analysis" | jq '.'
|
|
423
|
+
|
|
424
|
+
if [[ $(echo "$flaky_analysis" | jq 'length') -gt 0 ]]; then
|
|
425
|
+
warn "Found intermittent failures — consider quarantine"
|
|
426
|
+
else
|
|
427
|
+
success "No flaky tests detected"
|
|
428
|
+
fi
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
# ─── Main command routing ───────────────────────────────────────────────────
|
|
432
|
+
show_help() {
|
|
433
|
+
cat <<EOF
|
|
434
|
+
${CYAN}${BOLD}shipwright e2e${RESET} — End-to-end test orchestrator
|
|
435
|
+
|
|
436
|
+
${BOLD}USAGE${RESET}
|
|
437
|
+
shipwright e2e <command> [options]
|
|
438
|
+
|
|
439
|
+
${BOLD}COMMANDS${RESET}
|
|
440
|
+
${CYAN}run${RESET} [category] Run all test suites (or filtered by category)
|
|
441
|
+
${CYAN}smoke${RESET} Run quick smoke test suite
|
|
442
|
+
${CYAN}integration${RESET} Run integration test suite
|
|
443
|
+
${CYAN}regression${RESET} Run full regression test suite
|
|
444
|
+
${CYAN}register${RESET} <id> [...] Register a new test suite
|
|
445
|
+
${CYAN}quarantine${RESET} <name> Quarantine a flaky test
|
|
446
|
+
${CYAN}unquarantine${RESET} <name> Unquarantine a test
|
|
447
|
+
${CYAN}report${RESET} Generate test result report
|
|
448
|
+
${CYAN}flaky${RESET} Show flaky test analysis
|
|
449
|
+
${CYAN}help${RESET} Show this help message
|
|
450
|
+
|
|
451
|
+
${BOLD}EXAMPLES${RESET}
|
|
452
|
+
${DIM}shipwright e2e run${RESET} # Run all suites
|
|
453
|
+
${DIM}shipwright e2e run smoke${RESET} # Run only smoke tests
|
|
454
|
+
${DIM}shipwright e2e smoke${RESET} # Quick validation
|
|
455
|
+
${DIM}shipwright e2e register custom-test Custom${RESET}
|
|
456
|
+
${DIM}shipwright e2e quarantine flaky_test${RESET}
|
|
457
|
+
${DIM}shipwright e2e report${RESET} # Show latest results
|
|
458
|
+
${DIM}shipwright e2e flaky${RESET} # Analyze intermittent failures
|
|
459
|
+
|
|
460
|
+
${BOLD}ENVIRONMENT${RESET}
|
|
461
|
+
State directory: $E2E_DIR
|
|
462
|
+
Registry: $SUITE_REGISTRY
|
|
463
|
+
Results log: $RESULTS_LOG
|
|
464
|
+
Report: $LATEST_REPORT
|
|
465
|
+
|
|
466
|
+
${DIM}Version: $VERSION${RESET}
|
|
467
|
+
EOF
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
main() {
|
|
471
|
+
local cmd="${1:-help}"
|
|
472
|
+
shift 2>/dev/null || true
|
|
473
|
+
|
|
474
|
+
ensure_state_dir
|
|
475
|
+
|
|
476
|
+
case "$cmd" in
|
|
477
|
+
run)
|
|
478
|
+
init_registry
|
|
479
|
+
run_parallel "$@"
|
|
480
|
+
cmd_report
|
|
481
|
+
;;
|
|
482
|
+
smoke)
|
|
483
|
+
init_registry
|
|
484
|
+
run_suite "smoke"
|
|
485
|
+
;;
|
|
486
|
+
integration)
|
|
487
|
+
init_registry
|
|
488
|
+
run_suite "integration"
|
|
489
|
+
;;
|
|
490
|
+
regression)
|
|
491
|
+
init_registry
|
|
492
|
+
run_suite "regression"
|
|
493
|
+
;;
|
|
494
|
+
register)
|
|
495
|
+
init_registry
|
|
496
|
+
cmd_register "$@"
|
|
497
|
+
;;
|
|
498
|
+
quarantine)
|
|
499
|
+
quarantine_test="${1:-}"
|
|
500
|
+
if [[ -z "$quarantine_test" ]]; then
|
|
501
|
+
error "Usage: e2e quarantine <test-name>"
|
|
502
|
+
return 1
|
|
503
|
+
fi
|
|
504
|
+
cmd_quarantine "$quarantine_test" "$2" "quarantine"
|
|
505
|
+
;;
|
|
506
|
+
unquarantine)
|
|
507
|
+
test_name="${1:-}"
|
|
508
|
+
if [[ -z "$test_name" ]]; then
|
|
509
|
+
error "Usage: e2e unquarantine <test-name>"
|
|
510
|
+
return 1
|
|
511
|
+
fi
|
|
512
|
+
cmd_quarantine "$test_name" "" "unquarantine"
|
|
513
|
+
;;
|
|
514
|
+
report)
|
|
515
|
+
cmd_report
|
|
516
|
+
;;
|
|
517
|
+
flaky)
|
|
518
|
+
cmd_flaky
|
|
519
|
+
;;
|
|
520
|
+
help|--help|-h)
|
|
521
|
+
show_help
|
|
522
|
+
;;
|
|
523
|
+
*)
|
|
524
|
+
error "Unknown command: $cmd"
|
|
525
|
+
echo ""
|
|
526
|
+
show_help
|
|
527
|
+
return 1
|
|
528
|
+
;;
|
|
529
|
+
esac
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
# Source guard: allow sourcing this script
|
|
533
|
+
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
|
|
534
|
+
main "$@"
|
|
535
|
+
fi
|