zig-mobile-runner 0.1.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/CHANGELOG.md +484 -0
- package/CONTRIBUTING.md +42 -0
- package/FEATURES.md +112 -0
- package/LICENSE +21 -0
- package/README.md +255 -0
- package/SECURITY.md +34 -0
- package/build.zig +38 -0
- package/build.zig.zon +7 -0
- package/clients/README.md +144 -0
- package/clients/go/README.md +24 -0
- package/clients/go/examples/fake-session/main.go +93 -0
- package/clients/go/go.mod +3 -0
- package/clients/go/zmr/client.go +432 -0
- package/clients/kotlin/README.md +35 -0
- package/clients/kotlin/build.gradle.kts +35 -0
- package/clients/kotlin/settings.gradle.kts +15 -0
- package/clients/kotlin/src/main/kotlin/dev/zmr/FakeSession.kt +86 -0
- package/clients/kotlin/src/main/kotlin/dev/zmr/ZmrClient.kt +67 -0
- package/clients/python/README.md +29 -0
- package/clients/python/examples/fake_session.py +48 -0
- package/clients/python/pyproject.toml +13 -0
- package/clients/python/zmr_client.py +202 -0
- package/clients/rust/Cargo.lock +107 -0
- package/clients/rust/Cargo.toml +10 -0
- package/clients/rust/README.md +19 -0
- package/clients/rust/examples/fake_session.rs +70 -0
- package/clients/rust/src/lib.rs +461 -0
- package/clients/swift/Package.swift +16 -0
- package/clients/swift/README.md +36 -0
- package/clients/swift/Sources/ZMRClient/ZMRClient.swift +114 -0
- package/clients/swift/Sources/ZMRFakeSession/main.swift +86 -0
- package/clients/typescript/README.md +34 -0
- package/clients/typescript/examples/fake-session.mjs +36 -0
- package/clients/typescript/index.d.ts +144 -0
- package/clients/typescript/index.mjs +192 -0
- package/clients/typescript/package.json +8 -0
- package/docs/adr/0001-agent-native-runner-boundary.md +31 -0
- package/docs/adr/0002-app-local-zmr-contract.md +39 -0
- package/docs/adr/0003-ios-simulator-xctest-shim.md +41 -0
- package/docs/adr/0004-benchmark-claims-and-baseline-collection.md +37 -0
- package/docs/adr/README.md +12 -0
- package/docs/ai-agents.md +156 -0
- package/docs/app-integration.md +316 -0
- package/docs/benchmarking.md +275 -0
- package/docs/client-installation.md +141 -0
- package/docs/clients.md +98 -0
- package/docs/config.md +175 -0
- package/docs/demo.md +259 -0
- package/docs/dsl.md +57 -0
- package/docs/install.md +233 -0
- package/docs/market-positioning.md +70 -0
- package/docs/npm.md +359 -0
- package/docs/protocol-fixtures/README.md +8 -0
- package/docs/protocol-fixtures/core-session.requests.jsonl +8 -0
- package/docs/protocol-fixtures/core-session.responses.jsonl +8 -0
- package/docs/protocol-versioning.md +65 -0
- package/docs/protocol.md +560 -0
- package/docs/publication.md +77 -0
- package/docs/release-audit.md +99 -0
- package/docs/release-candidate.md +111 -0
- package/docs/release-evidence.md +188 -0
- package/docs/release-notes-template.md +58 -0
- package/docs/roadmap.md +334 -0
- package/docs/scenario-authoring.md +88 -0
- package/docs/shipping.md +170 -0
- package/docs/trace-privacy.md +88 -0
- package/docs/troubleshooting.md +256 -0
- package/examples/android-app-auth-probe.json +89 -0
- package/examples/android-app-error-state.json +13 -0
- package/examples/android-app-login-smoke.json +192 -0
- package/examples/android-app-onboarding.json +12 -0
- package/examples/android-app-referral-deep-link.json +12 -0
- package/examples/android-shim-smoke.json +19 -0
- package/examples/demo-failure.json +12 -0
- package/examples/demo-fake.json +14 -0
- package/examples/ios-dev-client-open-link.json +26 -0
- package/examples/ios-dev-client-route-snapshot.json +24 -0
- package/examples/ios-shim-smoke.json +23 -0
- package/examples/ios-smoke.json +9 -0
- package/go.work +3 -0
- package/npm/agents.mjs +183 -0
- package/npm/app-config.mjs +95 -0
- package/npm/build-zmr.mjs +21 -0
- package/npm/commands.mjs +104 -0
- package/npm/generated-files.mjs +50 -0
- package/npm/index.mjs +75 -0
- package/npm/init-app.mjs +80 -0
- package/npm/package-scripts.mjs +72 -0
- package/npm/postinstall.mjs +21 -0
- package/npm/scaffold.mjs +179 -0
- package/npm/scenarios.mjs +93 -0
- package/npm/setup.mjs +69 -0
- package/npm/wizard.mjs +117 -0
- package/npm/zmr.mjs +23 -0
- package/package.json +114 -0
- package/prebuilds/darwin-arm64/zmr +0 -0
- package/prebuilds/darwin-x64/zmr +0 -0
- package/prebuilds/linux-arm64/zmr +0 -0
- package/prebuilds/linux-x64/zmr +0 -0
- package/schemas/README.md +26 -0
- package/schemas/action-result.schema.json +27 -0
- package/schemas/capabilities-output.schema.json +98 -0
- package/schemas/devices-output.schema.json +25 -0
- package/schemas/doctor-output.schema.json +51 -0
- package/schemas/explain-output.schema.json +51 -0
- package/schemas/import-output.schema.json +23 -0
- package/schemas/init-output.schema.json +71 -0
- package/schemas/json-rpc.schema.json +55 -0
- package/schemas/release-manifest.schema.json +43 -0
- package/schemas/release-readiness-output.schema.json +127 -0
- package/schemas/run-output.schema.json +43 -0
- package/schemas/scenario.schema.json +128 -0
- package/schemas/schemas-output.schema.json +26 -0
- package/schemas/semantic-snapshot.schema.json +116 -0
- package/schemas/snapshot.schema.json +60 -0
- package/schemas/trace-event.schema.json +14 -0
- package/schemas/trace-manifest.schema.json +59 -0
- package/schemas/validate-output.schema.json +42 -0
- package/schemas/version-output.schema.json +23 -0
- package/schemas/zmr-config.schema.json +75 -0
- package/scripts/android-emulator.sh +126 -0
- package/scripts/assert-ios-physical-ready.sh +213 -0
- package/scripts/benchmark-command.sh +307 -0
- package/scripts/benchmark.sh +359 -0
- package/scripts/benchmark_gate.py +117 -0
- package/scripts/benchmark_result_row.py +88 -0
- package/scripts/compare-benchmarks.py +288 -0
- package/scripts/create-android-demo-app.sh +342 -0
- package/scripts/create-ios-demo-app.sh +261 -0
- package/scripts/demo-android-real.sh +232 -0
- package/scripts/demo-ios-real.sh +270 -0
- package/scripts/demo.sh +464 -0
- package/scripts/device-matrix.sh +338 -0
- package/scripts/ensure-ios-shim-target.rb +237 -0
- package/scripts/install-android-shim.sh +281 -0
- package/scripts/install-ios-shim.sh +589 -0
- package/scripts/pilot-gate.sh +560 -0
- package/scripts/release-readiness.py +838 -0
- package/scripts/release-readiness.sh +91 -0
- package/scripts/run-android-pilot.sh +561 -0
- package/scripts/run-ios-pilot.sh +509 -0
- package/shims/android/README.md +21 -0
- package/shims/android/ZMRShimInstrumentedTest.java +152 -0
- package/shims/android/protocol.md +18 -0
- package/shims/ios/README.md +50 -0
- package/shims/ios/ZMRShim.swift +110 -0
- package/shims/ios/ZMRShimUITestCase.swift +475 -0
- package/shims/ios/protocol.md +74 -0
- package/skills/zmr-mobile-testing/SKILL.md +127 -0
- package/src/android.zig +344 -0
- package/src/android_device_info.zig +99 -0
- package/src/android_emulator.zig +154 -0
- package/src/android_screen_recording.zig +112 -0
- package/src/android_shell.zig +112 -0
- package/src/bundle.zig +124 -0
- package/src/bundle_redaction.zig +272 -0
- package/src/bundle_tar.zig +123 -0
- package/src/cli_devices.zig +97 -0
- package/src/cli_doctor.zig +114 -0
- package/src/cli_import.zig +70 -0
- package/src/cli_info.zig +39 -0
- package/src/cli_init.zig +72 -0
- package/src/cli_output.zig +467 -0
- package/src/cli_run.zig +259 -0
- package/src/cli_serve.zig +287 -0
- package/src/cli_trace.zig +111 -0
- package/src/cli_validate.zig +41 -0
- package/src/command.zig +211 -0
- package/src/config.zig +305 -0
- package/src/config_diagnostics.zig +212 -0
- package/src/config_paths.zig +49 -0
- package/src/device_registry.zig +37 -0
- package/src/doctor.zig +412 -0
- package/src/doctor_hints.zig +52 -0
- package/src/errors.zig +55 -0
- package/src/fake_device.zig +163 -0
- package/src/health.zig +28 -0
- package/src/importer.zig +343 -0
- package/src/importer_json.zig +100 -0
- package/src/importer_model.zig +103 -0
- package/src/ios.zig +399 -0
- package/src/ios_devices.zig +219 -0
- package/src/ios_lifecycle.zig +72 -0
- package/src/ios_shim.zig +242 -0
- package/src/ios_snapshot.zig +20 -0
- package/src/json_fields.zig +80 -0
- package/src/json_rpc.zig +150 -0
- package/src/json_rpc_methods.zig +318 -0
- package/src/json_rpc_observation.zig +31 -0
- package/src/json_rpc_params.zig +52 -0
- package/src/json_rpc_protocol.zig +110 -0
- package/src/json_rpc_trace.zig +73 -0
- package/src/main.zig +135 -0
- package/src/mcp.zig +234 -0
- package/src/mcp_protocol.zig +64 -0
- package/src/mcp_trace.zig +83 -0
- package/src/report.zig +346 -0
- package/src/report_html.zig +63 -0
- package/src/report_values.zig +27 -0
- package/src/run_options.zig +152 -0
- package/src/runner.zig +280 -0
- package/src/runner_actions.zig +109 -0
- package/src/runner_config.zig +6 -0
- package/src/runner_diagnostics.zig +268 -0
- package/src/runner_events.zig +170 -0
- package/src/runner_native.zig +88 -0
- package/src/runner_waits.zig +300 -0
- package/src/scaffold.zig +472 -0
- package/src/scenario.zig +346 -0
- package/src/scenario_fields.zig +50 -0
- package/src/schema_registry.zig +53 -0
- package/src/selector.zig +84 -0
- package/src/semantic.zig +171 -0
- package/src/trace.zig +315 -0
- package/src/trace_json.zig +340 -0
- package/src/trace_summary.zig +218 -0
- package/src/trace_summary_diagnostic.zig +202 -0
- package/src/types.zig +120 -0
- package/src/uiautomator.zig +164 -0
- package/src/validation.zig +187 -0
- package/src/version.zig +22 -0
- package/viewer/app.js +373 -0
- package/viewer/index.html +126 -0
- package/viewer/parser.js +233 -0
- package/viewer/styles.css +585 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
AVD="${AVD:-}"
|
|
5
|
+
DEVICE="${DEVICE:-emulator-5554}"
|
|
6
|
+
EMULATOR="${EMULATOR:-emulator}"
|
|
7
|
+
ADB="${ADB:-adb}"
|
|
8
|
+
SNAPSHOT="${SNAPSHOT:-zmr-clean}"
|
|
9
|
+
DRY_RUN=0
|
|
10
|
+
|
|
11
|
+
usage() {
|
|
12
|
+
cat <<'USAGE'
|
|
13
|
+
Usage:
|
|
14
|
+
scripts/android-emulator.sh boot --avd <name> [--device emulator-5554] [--dry-run]
|
|
15
|
+
scripts/android-emulator.sh wait-ready [--device emulator-5554] [--dry-run]
|
|
16
|
+
scripts/android-emulator.sh snapshot-save [--device emulator-5554] [--name zmr-clean] [--dry-run]
|
|
17
|
+
scripts/android-emulator.sh snapshot-load --avd <name> [--name zmr-clean] [--dry-run]
|
|
18
|
+
scripts/android-emulator.sh kill [--device emulator-5554] [--dry-run]
|
|
19
|
+
|
|
20
|
+
Environment:
|
|
21
|
+
EMULATOR emulator binary. Defaults to emulator.
|
|
22
|
+
ADB adb binary. Defaults to adb.
|
|
23
|
+
AVD default AVD name.
|
|
24
|
+
DEVICE default adb serial.
|
|
25
|
+
SNAPSHOT default snapshot name.
|
|
26
|
+
USAGE
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
quote_cmd() {
|
|
30
|
+
local quoted=()
|
|
31
|
+
local arg
|
|
32
|
+
for arg in "$@"; do
|
|
33
|
+
quoted+=("$(printf '%q' "$arg")")
|
|
34
|
+
done
|
|
35
|
+
printf '%s\n' "${quoted[*]}"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
run() {
|
|
39
|
+
echo "+ $(quote_cmd "$@")"
|
|
40
|
+
if [[ "$DRY_RUN" -eq 0 ]]; then
|
|
41
|
+
"$@"
|
|
42
|
+
fi
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
die() {
|
|
46
|
+
echo "error: $*" >&2
|
|
47
|
+
exit 2
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
require_value() {
|
|
51
|
+
local flag="$1"
|
|
52
|
+
local value="${2-}"
|
|
53
|
+
if [[ -z "$value" || "$value" == --* ]]; then
|
|
54
|
+
die "$flag requires a value"
|
|
55
|
+
fi
|
|
56
|
+
printf '%s\n' "$value"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
[[ $# -gt 0 ]] || {
|
|
60
|
+
usage >&2
|
|
61
|
+
exit 2
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
COMMAND="$1"
|
|
65
|
+
shift
|
|
66
|
+
|
|
67
|
+
while [[ $# -gt 0 ]]; do
|
|
68
|
+
case "$1" in
|
|
69
|
+
--avd)
|
|
70
|
+
AVD="$(require_value "$1" "${2-}")"
|
|
71
|
+
shift 2
|
|
72
|
+
;;
|
|
73
|
+
--device)
|
|
74
|
+
DEVICE="$(require_value "$1" "${2-}")"
|
|
75
|
+
shift 2
|
|
76
|
+
;;
|
|
77
|
+
--name)
|
|
78
|
+
SNAPSHOT="$(require_value "$1" "${2-}")"
|
|
79
|
+
shift 2
|
|
80
|
+
;;
|
|
81
|
+
--dry-run)
|
|
82
|
+
DRY_RUN=1
|
|
83
|
+
shift
|
|
84
|
+
;;
|
|
85
|
+
-h|--help)
|
|
86
|
+
usage
|
|
87
|
+
exit 0
|
|
88
|
+
;;
|
|
89
|
+
*)
|
|
90
|
+
die "unknown argument: $1"
|
|
91
|
+
;;
|
|
92
|
+
esac
|
|
93
|
+
done
|
|
94
|
+
|
|
95
|
+
case "$COMMAND" in
|
|
96
|
+
boot)
|
|
97
|
+
[[ -n "$AVD" ]] || die "--avd or AVD is required"
|
|
98
|
+
run "$EMULATOR" -avd "$AVD" -no-snapshot-load -netdelay none -netspeed full
|
|
99
|
+
;;
|
|
100
|
+
wait-ready)
|
|
101
|
+
run "$ADB" -s "$DEVICE" wait-for-device
|
|
102
|
+
if [[ "$DRY_RUN" -eq 1 ]]; then
|
|
103
|
+
echo "+ wait until sys.boot_completed is 1"
|
|
104
|
+
exit 0
|
|
105
|
+
fi
|
|
106
|
+
for _ in $(seq 1 120); do
|
|
107
|
+
value="$("$ADB" -s "$DEVICE" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')"
|
|
108
|
+
[[ "$value" == "1" ]] && exit 0
|
|
109
|
+
sleep 2
|
|
110
|
+
done
|
|
111
|
+
die "emulator did not finish booting"
|
|
112
|
+
;;
|
|
113
|
+
snapshot-save)
|
|
114
|
+
run "$ADB" -s "$DEVICE" emu avd snapshot save "$SNAPSHOT"
|
|
115
|
+
;;
|
|
116
|
+
snapshot-load)
|
|
117
|
+
[[ -n "$AVD" ]] || die "--avd or AVD is required"
|
|
118
|
+
run "$EMULATOR" -avd "$AVD" -snapshot "$SNAPSHOT" -netdelay none -netspeed full
|
|
119
|
+
;;
|
|
120
|
+
kill)
|
|
121
|
+
run "$ADB" -s "$DEVICE" emu kill
|
|
122
|
+
;;
|
|
123
|
+
*)
|
|
124
|
+
die "unknown command: $COMMAND"
|
|
125
|
+
;;
|
|
126
|
+
esac
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SOURCE="${BASH_SOURCE[0]}"
|
|
5
|
+
while [[ -h "$SOURCE" ]]; do
|
|
6
|
+
SOURCE_DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
|
|
7
|
+
SOURCE="$(readlink "$SOURCE")"
|
|
8
|
+
if [[ "$SOURCE" != /* ]]; then
|
|
9
|
+
SOURCE="$SOURCE_DIR/$SOURCE"
|
|
10
|
+
fi
|
|
11
|
+
done
|
|
12
|
+
|
|
13
|
+
ROOT="$(cd -P "$(dirname "$SOURCE")/.." && pwd)"
|
|
14
|
+
cd "$ROOT"
|
|
15
|
+
|
|
16
|
+
if [[ -n "${ZMR_BIN:-}" ]]; then
|
|
17
|
+
ZMR="$ZMR_BIN"
|
|
18
|
+
elif [[ -x "$ROOT/zig-out/bin/zmr" ]]; then
|
|
19
|
+
ZMR="$ROOT/zig-out/bin/zmr"
|
|
20
|
+
else
|
|
21
|
+
ZMR="zmr"
|
|
22
|
+
fi
|
|
23
|
+
DEVICE=""
|
|
24
|
+
XCRUN=""
|
|
25
|
+
XCRUN_PROVIDED=0
|
|
26
|
+
EVIDENCE_OUT=""
|
|
27
|
+
ATTEMPTS="${ZMR_IOS_READY_ATTEMPTS:-3}"
|
|
28
|
+
RETRY_DELAY_SECONDS="${ZMR_IOS_READY_RETRY_DELAY_SECONDS:-1}"
|
|
29
|
+
START_MS="$(python3 - <<'PY'
|
|
30
|
+
import time
|
|
31
|
+
print(int(time.time() * 1000))
|
|
32
|
+
PY
|
|
33
|
+
)"
|
|
34
|
+
|
|
35
|
+
usage() {
|
|
36
|
+
cat <<'USAGE'
|
|
37
|
+
Usage:
|
|
38
|
+
scripts/assert-ios-physical-ready.sh [--zmr <path>] [--xcrun <path>] [--device <identifier>] [--evidence-out <path>]
|
|
39
|
+
|
|
40
|
+
Fails unless zmr reports at least one ready physical iOS device. When --device
|
|
41
|
+
is supplied, that exact CoreDevice identifier from `zmr devices --json
|
|
42
|
+
--platform ios --ios-device-type physical` must be present and ready.
|
|
43
|
+
|
|
44
|
+
When --evidence-out is supplied, a successful check appends a JSONL row that
|
|
45
|
+
can be consumed by zmr-release-readiness.
|
|
46
|
+
USAGE
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
die() {
|
|
50
|
+
echo "error: $*" >&2
|
|
51
|
+
exit 2
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
require_value() {
|
|
55
|
+
local flag="$1"
|
|
56
|
+
local value="${2-}"
|
|
57
|
+
if [[ -z "$value" || "$value" == --* ]]; then
|
|
58
|
+
die "$flag requires a value"
|
|
59
|
+
fi
|
|
60
|
+
printf '%s\n' "$value"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
append_evidence() {
|
|
64
|
+
[[ -n "$EVIDENCE_OUT" ]] || return 0
|
|
65
|
+
|
|
66
|
+
local end_ms duration_ms command
|
|
67
|
+
end_ms="$(python3 - <<'PY'
|
|
68
|
+
import time
|
|
69
|
+
print(int(time.time() * 1000))
|
|
70
|
+
PY
|
|
71
|
+
)"
|
|
72
|
+
duration_ms="$((end_ms - START_MS))"
|
|
73
|
+
if [[ -n "$DEVICE" ]]; then
|
|
74
|
+
command="scripts/assert-ios-physical-ready.sh --device $DEVICE"
|
|
75
|
+
else
|
|
76
|
+
command="scripts/assert-ios-physical-ready.sh"
|
|
77
|
+
fi
|
|
78
|
+
if [[ -n "$XCRUN" ]]; then
|
|
79
|
+
command="$command --xcrun $XCRUN"
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
mkdir -p "$(dirname "$EVIDENCE_OUT")"
|
|
83
|
+
python3 - "$EVIDENCE_OUT" "physical iOS readiness" "ios-physical-ready" "passed" "$duration_ms" "$command" "$DEVICE" <<'PY'
|
|
84
|
+
import json
|
|
85
|
+
import sys
|
|
86
|
+
|
|
87
|
+
path, name, mode, status, duration_ms, command, device_id = sys.argv[1:]
|
|
88
|
+
row = {
|
|
89
|
+
"name": name,
|
|
90
|
+
"mode": mode,
|
|
91
|
+
"status": status,
|
|
92
|
+
"durationMs": int(duration_ms),
|
|
93
|
+
"command": command,
|
|
94
|
+
}
|
|
95
|
+
if device_id:
|
|
96
|
+
row["deviceId"] = device_id
|
|
97
|
+
with open(path, "a", encoding="utf-8") as fh:
|
|
98
|
+
fh.write(json.dumps(row, separators=(",", ":")) + "\n")
|
|
99
|
+
PY
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
while [[ $# -gt 0 ]]; do
|
|
103
|
+
case "$1" in
|
|
104
|
+
--zmr)
|
|
105
|
+
ZMR="$(require_value "$1" "${2-}")"
|
|
106
|
+
shift 2
|
|
107
|
+
;;
|
|
108
|
+
--xcrun)
|
|
109
|
+
XCRUN="$(require_value "$1" "${2-}")"
|
|
110
|
+
XCRUN_PROVIDED=1
|
|
111
|
+
shift 2
|
|
112
|
+
;;
|
|
113
|
+
--device)
|
|
114
|
+
DEVICE="$(require_value "$1" "${2-}")"
|
|
115
|
+
shift 2
|
|
116
|
+
;;
|
|
117
|
+
--evidence-out)
|
|
118
|
+
EVIDENCE_OUT="$(require_value "$1" "${2-}")"
|
|
119
|
+
shift 2
|
|
120
|
+
;;
|
|
121
|
+
-h|--help)
|
|
122
|
+
usage
|
|
123
|
+
exit 0
|
|
124
|
+
;;
|
|
125
|
+
*)
|
|
126
|
+
die "unknown argument: $1"
|
|
127
|
+
;;
|
|
128
|
+
esac
|
|
129
|
+
done
|
|
130
|
+
|
|
131
|
+
[[ -n "$ZMR" ]] || die "--zmr must be non-empty"
|
|
132
|
+
if [[ "$XCRUN_PROVIDED" -eq 1 && -z "$XCRUN" ]]; then
|
|
133
|
+
die "--xcrun must be non-empty"
|
|
134
|
+
fi
|
|
135
|
+
[[ "$ATTEMPTS" =~ ^[0-9]+$ && "$ATTEMPTS" -ge 1 ]] || die "ZMR_IOS_READY_ATTEMPTS must be a positive integer"
|
|
136
|
+
[[ "$RETRY_DELAY_SECONDS" =~ ^[0-9]+$ ]] || die "ZMR_IOS_READY_RETRY_DELAY_SECONDS must be a non-negative integer"
|
|
137
|
+
|
|
138
|
+
devices_json=""
|
|
139
|
+
last_error=""
|
|
140
|
+
attempt=1
|
|
141
|
+
zmr_devices_args=(devices --json --platform ios --ios-device-type physical)
|
|
142
|
+
if [[ -n "$XCRUN" ]]; then
|
|
143
|
+
zmr_devices_args+=(--xcrun "$XCRUN")
|
|
144
|
+
fi
|
|
145
|
+
while [[ "$attempt" -le "$ATTEMPTS" ]]; do
|
|
146
|
+
error_file="$(mktemp)"
|
|
147
|
+
if devices_json="$("$ZMR" "${zmr_devices_args[@]}" 2>"$error_file")"; then
|
|
148
|
+
rm -f "$error_file"
|
|
149
|
+
break
|
|
150
|
+
fi
|
|
151
|
+
last_error="$(cat "$error_file")"
|
|
152
|
+
rm -f "$error_file"
|
|
153
|
+
if [[ "$attempt" -lt "$ATTEMPTS" ]]; then
|
|
154
|
+
sleep "$RETRY_DELAY_SECONDS"
|
|
155
|
+
fi
|
|
156
|
+
attempt="$((attempt + 1))"
|
|
157
|
+
done
|
|
158
|
+
|
|
159
|
+
if [[ -z "$devices_json" ]]; then
|
|
160
|
+
if [[ -n "$last_error" ]]; then
|
|
161
|
+
printf '%s\n' "$last_error" >&2
|
|
162
|
+
fi
|
|
163
|
+
echo "error[setup.ios.physical_devices_unavailable]: unable to list physical iOS devices after $ATTEMPTS attempt(s)" >&2
|
|
164
|
+
exit 3
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
if ZMR_DEVICES_JSON="$devices_json" python3 - "$DEVICE" <<'PY'
|
|
168
|
+
import json
|
|
169
|
+
import os
|
|
170
|
+
import sys
|
|
171
|
+
|
|
172
|
+
target = sys.argv[1] or None
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
data = json.loads(os.environ["ZMR_DEVICES_JSON"])
|
|
176
|
+
except Exception as exc:
|
|
177
|
+
print(f"error[setup.ios.devices_json_invalid]: failed to parse zmr devices JSON: {exc}", file=sys.stderr)
|
|
178
|
+
sys.exit(3)
|
|
179
|
+
|
|
180
|
+
devices = data.get("devices")
|
|
181
|
+
if not isinstance(devices, list):
|
|
182
|
+
print("error[setup.ios.devices_json_invalid]: zmr devices JSON is missing devices[]", file=sys.stderr)
|
|
183
|
+
sys.exit(3)
|
|
184
|
+
|
|
185
|
+
if target:
|
|
186
|
+
for device in devices:
|
|
187
|
+
if device.get("serial") == target:
|
|
188
|
+
if device.get("ready") is True:
|
|
189
|
+
print(f"physical iOS device ready: {target}")
|
|
190
|
+
sys.exit(0)
|
|
191
|
+
state = device.get("state") or "unknown"
|
|
192
|
+
print(
|
|
193
|
+
f"error[setup.ios.physical_device_not_ready]: physical iOS device is not ready: {target} (state: {state})",
|
|
194
|
+
file=sys.stderr,
|
|
195
|
+
)
|
|
196
|
+
sys.exit(3)
|
|
197
|
+
print(f"error[setup.ios.physical_device_not_found]: physical iOS device was not found: {target}", file=sys.stderr)
|
|
198
|
+
sys.exit(3)
|
|
199
|
+
|
|
200
|
+
for device in devices:
|
|
201
|
+
if device.get("ready") is True:
|
|
202
|
+
print(f"physical iOS device ready: {device.get('serial', '<unknown>')}")
|
|
203
|
+
sys.exit(0)
|
|
204
|
+
|
|
205
|
+
states = ", ".join(f"{d.get('serial', '<unknown>')}:{d.get('state', 'unknown')}" for d in devices) or "none"
|
|
206
|
+
print(f"error[setup.ios.no_ready_physical_devices]: no ready physical iOS devices found ({states})", file=sys.stderr)
|
|
207
|
+
sys.exit(3)
|
|
208
|
+
PY
|
|
209
|
+
then
|
|
210
|
+
append_evidence
|
|
211
|
+
else
|
|
212
|
+
exit $?
|
|
213
|
+
fi
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SOURCE="${BASH_SOURCE[0]}"
|
|
5
|
+
while [[ -h "$SOURCE" ]]; do
|
|
6
|
+
SOURCE_DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
|
|
7
|
+
SOURCE="$(readlink "$SOURCE")"
|
|
8
|
+
if [[ "$SOURCE" != /* ]]; then
|
|
9
|
+
SOURCE="$SOURCE_DIR/$SOURCE"
|
|
10
|
+
fi
|
|
11
|
+
done
|
|
12
|
+
|
|
13
|
+
ROOT="$(cd -P "$(dirname "$SOURCE")/.." && pwd)"
|
|
14
|
+
CALLER_CWD="$(pwd -P)"
|
|
15
|
+
|
|
16
|
+
# Some sandboxed environments do not allow writing to the default temp directory
|
|
17
|
+
# (/var/folders, /tmp). Use a repo-local TMPDIR so adb/xcrun/mktemp/heredocs work.
|
|
18
|
+
if [[ -z "${TMPDIR:-}" || ! -w "${TMPDIR:-/nonexistent}" ]]; then
|
|
19
|
+
TMPDIR="$ROOT/traces/tmp"
|
|
20
|
+
mkdir -p "$TMPDIR"
|
|
21
|
+
export TMPDIR
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
TOOL="${TOOL:-baseline}"
|
|
25
|
+
RUNS="${RUNS:-5}"
|
|
26
|
+
TRACE_ROOT="${TRACE_ROOT:-$CALLER_CWD/traces/bench-command-$(date +%Y%m%d-%H%M%S)}"
|
|
27
|
+
RESULTS=""
|
|
28
|
+
CWD=""
|
|
29
|
+
REPLACE=0
|
|
30
|
+
PLATFORM="${PLATFORM:-}"
|
|
31
|
+
DEVICE="${DEVICE:-}"
|
|
32
|
+
APP_ID="${APP_ID:-}"
|
|
33
|
+
SCENARIO="${SCENARIO:-}"
|
|
34
|
+
APP_BUILD="${APP_BUILD:-}"
|
|
35
|
+
MIN_PASS_RATE="${MIN_PASS_RATE:-}"
|
|
36
|
+
MAX_FAILURES="${MAX_FAILURES:-}"
|
|
37
|
+
MAX_MEAN_MS="${MAX_MEAN_MS:-}"
|
|
38
|
+
MAX_P95_MS="${MAX_P95_MS:-}"
|
|
39
|
+
|
|
40
|
+
usage() {
|
|
41
|
+
cat <<'USAGE'
|
|
42
|
+
Usage:
|
|
43
|
+
scripts/benchmark-command.sh --tool <label> [options] -- <command> [args...]
|
|
44
|
+
|
|
45
|
+
Runs any local command repeatedly and appends normalized benchmark rows that can
|
|
46
|
+
be compared with ZMR rows through zmr-compare-benchmarks.
|
|
47
|
+
|
|
48
|
+
Options:
|
|
49
|
+
--tool <label> Baseline tool label, for example runner-a or runner-b.
|
|
50
|
+
--runs <n> Number of command runs. Default: 5.
|
|
51
|
+
--trace-root <dir> Directory for stdout/stderr logs. Default: traces/bench-command-<timestamp> in the caller directory.
|
|
52
|
+
--results <path> Results JSONL path. Defaults to <trace-root>/results.jsonl.
|
|
53
|
+
Explicit results paths are appended by default.
|
|
54
|
+
--replace Truncate --results before writing.
|
|
55
|
+
--cwd <dir> Run the command from this working directory.
|
|
56
|
+
--platform <name> Platform context, for example android or ios.
|
|
57
|
+
--device <id> Device context shared with candidate rows.
|
|
58
|
+
--app-id <id> App id/bundle id context shared with candidate rows.
|
|
59
|
+
--scenario <path> Scenario or flow identifier used by this command.
|
|
60
|
+
--app-build <id> App build fingerprint, artifact path, or CI build id.
|
|
61
|
+
--min-pass-rate <pct> Optional gate minimum.
|
|
62
|
+
--max-failures <n> Optional gate maximum.
|
|
63
|
+
--max-mean-ms <ms> Optional mean duration maximum.
|
|
64
|
+
--max-p95-ms <ms> Optional p95 duration maximum.
|
|
65
|
+
-h, --help Show this help.
|
|
66
|
+
|
|
67
|
+
Example:
|
|
68
|
+
zmr-benchmark-command \
|
|
69
|
+
--tool runner-a \
|
|
70
|
+
--runs 20 \
|
|
71
|
+
--trace-root traces/runner-a-login \
|
|
72
|
+
--results traces/comparison/results.jsonl \
|
|
73
|
+
-- runner-a test .runner-a/login.yaml
|
|
74
|
+
USAGE
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
die() {
|
|
78
|
+
echo "error: $*" >&2
|
|
79
|
+
exit 2
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
require_value() {
|
|
83
|
+
local flag="$1"
|
|
84
|
+
local value="${2-}"
|
|
85
|
+
if [[ -z "$value" || "$value" == --* ]]; then
|
|
86
|
+
die "$flag requires a value"
|
|
87
|
+
fi
|
|
88
|
+
printf '%s\n' "$value"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
quote_cmd() {
|
|
92
|
+
local quoted=()
|
|
93
|
+
local arg
|
|
94
|
+
for arg in "$@"; do
|
|
95
|
+
quoted+=("$(printf '%q' "$arg")")
|
|
96
|
+
done
|
|
97
|
+
printf '%s\n' "${quoted[*]}"
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
RESULTS_EXPLICIT=0
|
|
101
|
+
while [[ $# -gt 0 ]]; do
|
|
102
|
+
case "$1" in
|
|
103
|
+
--tool)
|
|
104
|
+
TOOL="$(require_value "$1" "${2-}")"
|
|
105
|
+
shift 2
|
|
106
|
+
;;
|
|
107
|
+
--runs)
|
|
108
|
+
RUNS="$(require_value "$1" "${2-}")"
|
|
109
|
+
shift 2
|
|
110
|
+
;;
|
|
111
|
+
--trace-root)
|
|
112
|
+
TRACE_ROOT="$(require_value "$1" "${2-}")"
|
|
113
|
+
shift 2
|
|
114
|
+
;;
|
|
115
|
+
--results)
|
|
116
|
+
RESULTS="$(require_value "$1" "${2-}")"
|
|
117
|
+
RESULTS_EXPLICIT=1
|
|
118
|
+
shift 2
|
|
119
|
+
;;
|
|
120
|
+
--replace)
|
|
121
|
+
REPLACE=1
|
|
122
|
+
shift
|
|
123
|
+
;;
|
|
124
|
+
--cwd)
|
|
125
|
+
CWD="$(require_value "$1" "${2-}")"
|
|
126
|
+
shift 2
|
|
127
|
+
;;
|
|
128
|
+
--platform)
|
|
129
|
+
PLATFORM="$(require_value "$1" "${2-}")"
|
|
130
|
+
shift 2
|
|
131
|
+
;;
|
|
132
|
+
--device)
|
|
133
|
+
DEVICE="$(require_value "$1" "${2-}")"
|
|
134
|
+
shift 2
|
|
135
|
+
;;
|
|
136
|
+
--app-id)
|
|
137
|
+
APP_ID="$(require_value "$1" "${2-}")"
|
|
138
|
+
shift 2
|
|
139
|
+
;;
|
|
140
|
+
--scenario)
|
|
141
|
+
SCENARIO="$(require_value "$1" "${2-}")"
|
|
142
|
+
shift 2
|
|
143
|
+
;;
|
|
144
|
+
--app-build)
|
|
145
|
+
APP_BUILD="$(require_value "$1" "${2-}")"
|
|
146
|
+
shift 2
|
|
147
|
+
;;
|
|
148
|
+
--min-pass-rate)
|
|
149
|
+
MIN_PASS_RATE="$(require_value "$1" "${2-}")"
|
|
150
|
+
shift 2
|
|
151
|
+
;;
|
|
152
|
+
--max-failures)
|
|
153
|
+
MAX_FAILURES="$(require_value "$1" "${2-}")"
|
|
154
|
+
shift 2
|
|
155
|
+
;;
|
|
156
|
+
--max-mean-ms)
|
|
157
|
+
MAX_MEAN_MS="$(require_value "$1" "${2-}")"
|
|
158
|
+
shift 2
|
|
159
|
+
;;
|
|
160
|
+
--max-p95-ms)
|
|
161
|
+
MAX_P95_MS="$(require_value "$1" "${2-}")"
|
|
162
|
+
shift 2
|
|
163
|
+
;;
|
|
164
|
+
--)
|
|
165
|
+
shift
|
|
166
|
+
break
|
|
167
|
+
;;
|
|
168
|
+
-h|--help)
|
|
169
|
+
usage
|
|
170
|
+
exit 0
|
|
171
|
+
;;
|
|
172
|
+
*)
|
|
173
|
+
die "unknown argument before --: $1"
|
|
174
|
+
;;
|
|
175
|
+
esac
|
|
176
|
+
done
|
|
177
|
+
|
|
178
|
+
[[ -n "$TOOL" ]] || die "--tool cannot be empty"
|
|
179
|
+
[[ "$RUNS" =~ ^[0-9]+$ && "$RUNS" -ge 1 ]] || die "--runs must be a positive integer"
|
|
180
|
+
[[ $# -gt 0 ]] || die "command is required after --"
|
|
181
|
+
if [[ -n "$CWD" && ! -d "$CWD" ]]; then
|
|
182
|
+
die "--cwd directory not found: $CWD"
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
validate_optional_number() {
|
|
186
|
+
local name="$1"
|
|
187
|
+
local value="$2"
|
|
188
|
+
if [[ -n "$value" && ! "$value" =~ ^[0-9]+([.][0-9]+)?$ ]]; then
|
|
189
|
+
die "$name must be a non-negative number"
|
|
190
|
+
fi
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
validate_optional_integer() {
|
|
194
|
+
local name="$1"
|
|
195
|
+
local value="$2"
|
|
196
|
+
if [[ -n "$value" && ! "$value" =~ ^[0-9]+$ ]]; then
|
|
197
|
+
die "$name must be a non-negative integer"
|
|
198
|
+
fi
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
validate_optional_number "--min-pass-rate" "$MIN_PASS_RATE"
|
|
202
|
+
validate_optional_integer "--max-failures" "$MAX_FAILURES"
|
|
203
|
+
validate_optional_integer "--max-mean-ms" "$MAX_MEAN_MS"
|
|
204
|
+
validate_optional_integer "--max-p95-ms" "$MAX_P95_MS"
|
|
205
|
+
|
|
206
|
+
mkdir -p "$TRACE_ROOT"
|
|
207
|
+
if [[ -z "$RESULTS" ]]; then
|
|
208
|
+
RESULTS="$TRACE_ROOT/results.jsonl"
|
|
209
|
+
fi
|
|
210
|
+
mkdir -p "$(dirname "$RESULTS")"
|
|
211
|
+
if [[ "$REPLACE" -eq 1 || "$RESULTS_EXPLICIT" -eq 0 ]]; then
|
|
212
|
+
: > "$RESULTS"
|
|
213
|
+
else
|
|
214
|
+
touch "$RESULTS"
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
COMMAND=("$@")
|
|
218
|
+
metadata_args=()
|
|
219
|
+
if [[ -n "$PLATFORM" ]]; then
|
|
220
|
+
metadata_args+=(--platform "$PLATFORM")
|
|
221
|
+
fi
|
|
222
|
+
if [[ -n "$DEVICE" ]]; then
|
|
223
|
+
metadata_args+=(--device "$DEVICE")
|
|
224
|
+
fi
|
|
225
|
+
if [[ -n "$APP_ID" ]]; then
|
|
226
|
+
metadata_args+=(--app-id "$APP_ID")
|
|
227
|
+
fi
|
|
228
|
+
if [[ -n "$SCENARIO" ]]; then
|
|
229
|
+
metadata_args+=(--scenario "$SCENARIO")
|
|
230
|
+
fi
|
|
231
|
+
if [[ -n "$APP_BUILD" ]]; then
|
|
232
|
+
metadata_args+=(--app-build "$APP_BUILD")
|
|
233
|
+
fi
|
|
234
|
+
echo "Benchmark command output: $TRACE_ROOT"
|
|
235
|
+
echo "Results: $RESULTS"
|
|
236
|
+
echo "Tool: $TOOL"
|
|
237
|
+
echo "+ $(quote_cmd "${COMMAND[@]}")"
|
|
238
|
+
|
|
239
|
+
for run in $(seq 1 "$RUNS"); do
|
|
240
|
+
run_dir="$TRACE_ROOT/$TOOL-$run"
|
|
241
|
+
mkdir -p "$run_dir"
|
|
242
|
+
printf '%s\n' "$(quote_cmd "${COMMAND[@]}")" > "$run_dir/command.txt"
|
|
243
|
+
|
|
244
|
+
command_status=0
|
|
245
|
+
start_ms="$(python3 -c 'import time; print(int(time.time() * 1000))')"
|
|
246
|
+
if [[ -n "$CWD" ]]; then
|
|
247
|
+
(cd "$CWD" && "${COMMAND[@]}") > "$run_dir/stdout.log" 2> "$run_dir/stderr.log" || command_status=$?
|
|
248
|
+
else
|
|
249
|
+
"${COMMAND[@]}" > "$run_dir/stdout.log" 2> "$run_dir/stderr.log" || command_status=$?
|
|
250
|
+
fi
|
|
251
|
+
end_ms="$(python3 -c 'import time; print(int(time.time() * 1000))')"
|
|
252
|
+
duration_ms=$((end_ms - start_ms))
|
|
253
|
+
|
|
254
|
+
if [[ "${#metadata_args[@]}" -gt 0 ]]; then
|
|
255
|
+
"$ROOT/scripts/benchmark_result_row.py" \
|
|
256
|
+
--tool "$TOOL" \
|
|
257
|
+
--run "$run" \
|
|
258
|
+
--command-status "$command_status" \
|
|
259
|
+
--duration-ms "$duration_ms" \
|
|
260
|
+
--trace-dir "$run_dir" \
|
|
261
|
+
"${metadata_args[@]}" >> "$RESULTS"
|
|
262
|
+
else
|
|
263
|
+
"$ROOT/scripts/benchmark_result_row.py" \
|
|
264
|
+
--tool "$TOOL" \
|
|
265
|
+
--run "$run" \
|
|
266
|
+
--command-status "$command_status" \
|
|
267
|
+
--duration-ms "$duration_ms" \
|
|
268
|
+
--trace-dir "$run_dir" >> "$RESULTS"
|
|
269
|
+
fi
|
|
270
|
+
done
|
|
271
|
+
|
|
272
|
+
python3 - "$RESULTS" "$TOOL" <<'PY'
|
|
273
|
+
import json
|
|
274
|
+
import math
|
|
275
|
+
import statistics
|
|
276
|
+
import sys
|
|
277
|
+
|
|
278
|
+
path, tool = sys.argv[1], sys.argv[2]
|
|
279
|
+
rows = [
|
|
280
|
+
json.loads(line)
|
|
281
|
+
for line in open(path, encoding="utf-8")
|
|
282
|
+
if line.strip() and json.loads(line).get("tool") == tool
|
|
283
|
+
]
|
|
284
|
+
durations = [int(row.get("durationMs", 0)) for row in rows]
|
|
285
|
+
failures = sum(1 for row in rows if row.get("status") != "ok")
|
|
286
|
+
mean = round(statistics.mean(durations)) if durations else 0
|
|
287
|
+
p95 = sorted(durations)[max(0, math.ceil(len(durations) * 0.95) - 1)] if durations else 0
|
|
288
|
+
print(f"{tool}: runs={len(rows)} failures={failures} meanMs={mean} p95Ms={p95}")
|
|
289
|
+
PY
|
|
290
|
+
|
|
291
|
+
gate_args=()
|
|
292
|
+
if [[ -n "$MIN_PASS_RATE" ]]; then
|
|
293
|
+
gate_args+=(--min-pass-rate "$MIN_PASS_RATE")
|
|
294
|
+
fi
|
|
295
|
+
if [[ -n "$MAX_FAILURES" ]]; then
|
|
296
|
+
gate_args+=(--max-failures "$MAX_FAILURES")
|
|
297
|
+
fi
|
|
298
|
+
if [[ -n "$MAX_MEAN_MS" ]]; then
|
|
299
|
+
gate_args+=(--max-mean-ms "$MAX_MEAN_MS")
|
|
300
|
+
fi
|
|
301
|
+
if [[ -n "$MAX_P95_MS" ]]; then
|
|
302
|
+
gate_args+=(--max-p95-ms "$MAX_P95_MS")
|
|
303
|
+
fi
|
|
304
|
+
|
|
305
|
+
if [[ "${#gate_args[@]}" -gt 0 ]]; then
|
|
306
|
+
"$ROOT/scripts/benchmark_gate.py" --results "$RESULTS" "${gate_args[@]}"
|
|
307
|
+
fi
|