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,338 @@
|
|
|
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
|
+
ZMR_BIN="${ZMR_BIN:-$(command -v zmr 2>/dev/null || printf '%s' "$ROOT/zig-out/bin/zmr")}"
|
|
16
|
+
MATRIX=""
|
|
17
|
+
TRACE_ROOT="${TRACE_ROOT:-$CALLER_CWD/traces/matrix-$(date +%Y%m%d-%H%M%S)}"
|
|
18
|
+
MIN_PASS_RATE="${MIN_PASS_RATE:-}"
|
|
19
|
+
MAX_FAILURES="${MAX_FAILURES:-}"
|
|
20
|
+
|
|
21
|
+
usage() {
|
|
22
|
+
cat <<'USAGE'
|
|
23
|
+
Usage:
|
|
24
|
+
scripts/device-matrix.sh --matrix <matrix.json> [--trace-root <dir>] [gate options]
|
|
25
|
+
|
|
26
|
+
Omit --trace-root to write under traces/matrix-<timestamp> in the caller directory.
|
|
27
|
+
|
|
28
|
+
Gate options:
|
|
29
|
+
--min-pass-rate <pct> Minimum total pass rate percentage.
|
|
30
|
+
--max-failures <n> Maximum total failed matrix runs.
|
|
31
|
+
|
|
32
|
+
Matrix format:
|
|
33
|
+
{
|
|
34
|
+
"runs": 2,
|
|
35
|
+
"appId": "com.example.mobiletest",
|
|
36
|
+
"devices": [
|
|
37
|
+
{
|
|
38
|
+
"name": "android-api-35",
|
|
39
|
+
"platform": "android",
|
|
40
|
+
"serial": "emulator-5554",
|
|
41
|
+
"scenario": ".zmr/android-smoke.json",
|
|
42
|
+
"adb": "adb",
|
|
43
|
+
"androidShim": ".zmr/android-shim"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "ios-18",
|
|
47
|
+
"platform": "ios",
|
|
48
|
+
"iosDeviceType": "simulator",
|
|
49
|
+
"serial": "booted",
|
|
50
|
+
"scenario": ".zmr/ios-smoke.json",
|
|
51
|
+
"xcrun": "xcrun",
|
|
52
|
+
"iosShim": ".zmr/ios-shim"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "ios-physical",
|
|
56
|
+
"platform": "ios",
|
|
57
|
+
"iosDeviceType": "physical",
|
|
58
|
+
"serial": "<physical-device-id>",
|
|
59
|
+
"scenario": ".zmr/ios-smoke.json",
|
|
60
|
+
"xcrun": "xcrun",
|
|
61
|
+
"iosShim": ".zmr/ios-shim"
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
USAGE
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
die() {
|
|
69
|
+
echo "error: $*" >&2
|
|
70
|
+
exit 2
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
require_value() {
|
|
74
|
+
local flag="$1"
|
|
75
|
+
local value="${2-}"
|
|
76
|
+
if [[ -z "$value" || "$value" == --* ]]; then
|
|
77
|
+
die "$flag requires a value"
|
|
78
|
+
fi
|
|
79
|
+
printf '%s\n' "$value"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
while [[ $# -gt 0 ]]; do
|
|
83
|
+
case "$1" in
|
|
84
|
+
--matrix)
|
|
85
|
+
MATRIX="$(require_value "$1" "${2-}")"
|
|
86
|
+
shift 2
|
|
87
|
+
;;
|
|
88
|
+
--trace-root)
|
|
89
|
+
TRACE_ROOT="$(require_value "$1" "${2-}")"
|
|
90
|
+
shift 2
|
|
91
|
+
;;
|
|
92
|
+
--min-pass-rate)
|
|
93
|
+
MIN_PASS_RATE="$(require_value "$1" "${2-}")"
|
|
94
|
+
shift 2
|
|
95
|
+
;;
|
|
96
|
+
--max-failures)
|
|
97
|
+
MAX_FAILURES="$(require_value "$1" "${2-}")"
|
|
98
|
+
shift 2
|
|
99
|
+
;;
|
|
100
|
+
-h|--help)
|
|
101
|
+
usage
|
|
102
|
+
exit 0
|
|
103
|
+
;;
|
|
104
|
+
*)
|
|
105
|
+
die "unknown argument: $1"
|
|
106
|
+
;;
|
|
107
|
+
esac
|
|
108
|
+
done
|
|
109
|
+
|
|
110
|
+
if [[ -z "$MATRIX" ]]; then
|
|
111
|
+
echo "error: --matrix is required" >&2
|
|
112
|
+
usage >&2
|
|
113
|
+
exit 2
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
if [[ ! -f "$MATRIX" ]]; then
|
|
117
|
+
die "matrix file not found: $MATRIX"
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
if [[ ! -x "$ZMR_BIN" ]]; then
|
|
121
|
+
die "zmr binary is not executable: $ZMR_BIN"
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
validate_optional_number() {
|
|
125
|
+
local name="$1"
|
|
126
|
+
local value="$2"
|
|
127
|
+
if [[ -n "$value" && ! "$value" =~ ^[0-9]+([.][0-9]+)?$ ]]; then
|
|
128
|
+
echo "$name must be a non-negative number" >&2
|
|
129
|
+
exit 2
|
|
130
|
+
fi
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
validate_optional_integer() {
|
|
134
|
+
local name="$1"
|
|
135
|
+
local value="$2"
|
|
136
|
+
if [[ -n "$value" && ! "$value" =~ ^[0-9]+$ ]]; then
|
|
137
|
+
echo "$name must be a non-negative integer" >&2
|
|
138
|
+
exit 2
|
|
139
|
+
fi
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
validate_optional_number "--min-pass-rate" "$MIN_PASS_RATE"
|
|
143
|
+
validate_optional_integer "--max-failures" "$MAX_FAILURES"
|
|
144
|
+
|
|
145
|
+
mkdir -p "$TRACE_ROOT"
|
|
146
|
+
ROWS="$TRACE_ROOT/matrix.rows.tsv"
|
|
147
|
+
RESULTS="$TRACE_ROOT/matrix.jsonl"
|
|
148
|
+
SUMMARY="$TRACE_ROOT/summary.json"
|
|
149
|
+
: > "$RESULTS"
|
|
150
|
+
|
|
151
|
+
python3 - "$MATRIX" > "$ROWS" <<'PY'
|
|
152
|
+
import json
|
|
153
|
+
import sys
|
|
154
|
+
|
|
155
|
+
path = sys.argv[1]
|
|
156
|
+
with open(path, "r", encoding="utf-8") as fh:
|
|
157
|
+
matrix = json.load(fh)
|
|
158
|
+
|
|
159
|
+
runs = int(matrix.get("runs", 1))
|
|
160
|
+
if runs < 1:
|
|
161
|
+
raise SystemExit("matrix.runs must be >= 1")
|
|
162
|
+
|
|
163
|
+
default_app_id = matrix.get("appId", "")
|
|
164
|
+
devices = matrix.get("devices")
|
|
165
|
+
if not isinstance(devices, list) or not devices:
|
|
166
|
+
raise SystemExit("matrix.devices must be a non-empty array")
|
|
167
|
+
|
|
168
|
+
fields = [
|
|
169
|
+
"name",
|
|
170
|
+
"platform",
|
|
171
|
+
"iosDeviceType",
|
|
172
|
+
"serial",
|
|
173
|
+
"scenario",
|
|
174
|
+
"appId",
|
|
175
|
+
"adb",
|
|
176
|
+
"androidShim",
|
|
177
|
+
"xcrun",
|
|
178
|
+
"iosShim",
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
for index, device in enumerate(devices):
|
|
182
|
+
if not isinstance(device, dict):
|
|
183
|
+
raise SystemExit(f"matrix.devices[{index}] must be an object")
|
|
184
|
+
row = {}
|
|
185
|
+
row["name"] = device.get("name") or device.get("serial") or f"device-{index + 1}"
|
|
186
|
+
row["platform"] = device.get("platform", "android")
|
|
187
|
+
row["iosDeviceType"] = device.get("iosDeviceType", "")
|
|
188
|
+
row["serial"] = device.get("serial", "")
|
|
189
|
+
row["scenario"] = device.get("scenario", "")
|
|
190
|
+
row["appId"] = device.get("appId", default_app_id)
|
|
191
|
+
row["adb"] = device.get("adb", "")
|
|
192
|
+
row["androidShim"] = device.get("androidShim", "")
|
|
193
|
+
row["xcrun"] = device.get("xcrun", "")
|
|
194
|
+
row["iosShim"] = device.get("iosShim", "")
|
|
195
|
+
if row["platform"] not in {"android", "ios"}:
|
|
196
|
+
raise SystemExit(f"matrix.devices[{index}].platform must be android or ios")
|
|
197
|
+
if row["iosDeviceType"] and row["iosDeviceType"] not in {"simulator", "physical"}:
|
|
198
|
+
raise SystemExit(f"matrix.devices[{index}].iosDeviceType must be simulator or physical")
|
|
199
|
+
if not row["serial"]:
|
|
200
|
+
raise SystemExit(f"matrix.devices[{index}].serial is required")
|
|
201
|
+
if not row["scenario"]:
|
|
202
|
+
raise SystemExit(f"matrix.devices[{index}].scenario is required")
|
|
203
|
+
for run in range(1, runs + 1):
|
|
204
|
+
values = [str(run)]
|
|
205
|
+
for field in fields:
|
|
206
|
+
value = str(row[field]).replace("\t", " ")
|
|
207
|
+
values.append(value if value else "__ZMR_EMPTY__")
|
|
208
|
+
print("\t".join(values))
|
|
209
|
+
PY
|
|
210
|
+
|
|
211
|
+
slugify() {
|
|
212
|
+
printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9._-]+/-/g; s/^-+//; s/-+$//'
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
decode_matrix_field() {
|
|
216
|
+
if [[ "$1" == "__ZMR_EMPTY__" ]]; then
|
|
217
|
+
printf ''
|
|
218
|
+
else
|
|
219
|
+
printf '%s' "$1"
|
|
220
|
+
fi
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
while IFS=$'\t' read -r run device_name platform ios_device_type serial scenario app_id adb android_shim xcrun ios_shim; do
|
|
224
|
+
device_name="$(decode_matrix_field "$device_name")"
|
|
225
|
+
platform="$(decode_matrix_field "$platform")"
|
|
226
|
+
ios_device_type="$(decode_matrix_field "$ios_device_type")"
|
|
227
|
+
serial="$(decode_matrix_field "$serial")"
|
|
228
|
+
scenario="$(decode_matrix_field "$scenario")"
|
|
229
|
+
app_id="$(decode_matrix_field "$app_id")"
|
|
230
|
+
adb="$(decode_matrix_field "$adb")"
|
|
231
|
+
android_shim="$(decode_matrix_field "$android_shim")"
|
|
232
|
+
xcrun="$(decode_matrix_field "$xcrun")"
|
|
233
|
+
ios_shim="$(decode_matrix_field "$ios_shim")"
|
|
234
|
+
|
|
235
|
+
safe_name="$(slugify "$device_name")"
|
|
236
|
+
if [[ -z "$safe_name" ]]; then
|
|
237
|
+
safe_name="device"
|
|
238
|
+
fi
|
|
239
|
+
trace_dir="$TRACE_ROOT/$safe_name-run-$run"
|
|
240
|
+
mkdir -p "$trace_dir"
|
|
241
|
+
|
|
242
|
+
zmr_args=(run "$scenario" --platform "$platform" --device "$serial" --trace-dir "$trace_dir")
|
|
243
|
+
if [[ -n "$ios_device_type" ]]; then
|
|
244
|
+
zmr_args+=(--ios-device-type "$ios_device_type")
|
|
245
|
+
fi
|
|
246
|
+
if [[ -n "$app_id" ]]; then
|
|
247
|
+
zmr_args+=(--app-id "$app_id")
|
|
248
|
+
fi
|
|
249
|
+
if [[ -n "$adb" ]]; then
|
|
250
|
+
zmr_args+=(--adb "$adb")
|
|
251
|
+
fi
|
|
252
|
+
if [[ -n "$android_shim" ]]; then
|
|
253
|
+
zmr_args+=(--android-shim "$android_shim")
|
|
254
|
+
fi
|
|
255
|
+
if [[ -n "$xcrun" ]]; then
|
|
256
|
+
zmr_args+=(--xcrun "$xcrun")
|
|
257
|
+
fi
|
|
258
|
+
if [[ -n "$ios_shim" ]]; then
|
|
259
|
+
zmr_args+=(--ios-shim "$ios_shim")
|
|
260
|
+
fi
|
|
261
|
+
|
|
262
|
+
command_status=0
|
|
263
|
+
start_ms="$(python3 -c 'import time; print(int(time.time() * 1000))')"
|
|
264
|
+
"$ZMR_BIN" "${zmr_args[@]}" || command_status=$?
|
|
265
|
+
end_ms="$(python3 -c 'import time; print(int(time.time() * 1000))')"
|
|
266
|
+
duration_ms=$((end_ms - start_ms))
|
|
267
|
+
|
|
268
|
+
row="$("$ROOT/scripts/benchmark_result_row.py" \
|
|
269
|
+
--tool zmr \
|
|
270
|
+
--run "$run" \
|
|
271
|
+
--command-status "$command_status" \
|
|
272
|
+
--duration-ms "$duration_ms" \
|
|
273
|
+
--trace-dir "$trace_dir")"
|
|
274
|
+
python3 - "$row" "$device_name" "$platform" "$serial" "$scenario" <<'PY' >> "$RESULTS"
|
|
275
|
+
import json
|
|
276
|
+
import sys
|
|
277
|
+
|
|
278
|
+
row = json.loads(sys.argv[1])
|
|
279
|
+
row["deviceName"] = sys.argv[2]
|
|
280
|
+
row["platform"] = sys.argv[3]
|
|
281
|
+
row["serial"] = sys.argv[4]
|
|
282
|
+
row["scenario"] = sys.argv[5]
|
|
283
|
+
print(json.dumps(row, separators=(",", ":")))
|
|
284
|
+
PY
|
|
285
|
+
done < "$ROWS"
|
|
286
|
+
|
|
287
|
+
python3 - "$RESULTS" "$SUMMARY" <<'PY'
|
|
288
|
+
import json
|
|
289
|
+
import statistics
|
|
290
|
+
import sys
|
|
291
|
+
|
|
292
|
+
results_path = sys.argv[1]
|
|
293
|
+
summary_path = sys.argv[2]
|
|
294
|
+
rows = []
|
|
295
|
+
with open(results_path, "r", encoding="utf-8") as fh:
|
|
296
|
+
rows = [json.loads(line) for line in fh if line.strip()]
|
|
297
|
+
|
|
298
|
+
total = len(rows)
|
|
299
|
+
failed = sum(1 for row in rows if row.get("status") != "ok" or row.get("traceStatus") == "failed")
|
|
300
|
+
passed = total - failed
|
|
301
|
+
durations = [int(row.get("durationMs", 0)) for row in rows]
|
|
302
|
+
pass_rate = (passed / total * 100.0) if total else 0.0
|
|
303
|
+
summary = {
|
|
304
|
+
"totalRuns": total,
|
|
305
|
+
"passed": passed,
|
|
306
|
+
"failed": failed,
|
|
307
|
+
"passRate": round(pass_rate, 2),
|
|
308
|
+
"meanMs": round(statistics.mean(durations), 2) if durations else 0,
|
|
309
|
+
"resultsPath": "matrix.jsonl",
|
|
310
|
+
}
|
|
311
|
+
with open(summary_path, "w", encoding="utf-8") as fh:
|
|
312
|
+
json.dump(summary, fh, separators=(",", ":"))
|
|
313
|
+
fh.write("\n")
|
|
314
|
+
print(f"matrix: runs={total} passRate={pass_rate:.2f}% failures={failed}")
|
|
315
|
+
PY
|
|
316
|
+
|
|
317
|
+
gate_failed=0
|
|
318
|
+
python3 - "$SUMMARY" "$MIN_PASS_RATE" "$MAX_FAILURES" <<'PY' || gate_failed=$?
|
|
319
|
+
import json
|
|
320
|
+
import sys
|
|
321
|
+
|
|
322
|
+
summary_path, min_pass_rate, max_failures = sys.argv[1:4]
|
|
323
|
+
with open(summary_path, "r", encoding="utf-8") as fh:
|
|
324
|
+
summary = json.load(fh)
|
|
325
|
+
|
|
326
|
+
failed = False
|
|
327
|
+
if min_pass_rate and summary["passRate"] < float(min_pass_rate):
|
|
328
|
+
print(f"matrix gate failed: passRate={summary['passRate']:.2f}% < {float(min_pass_rate):.2f}%")
|
|
329
|
+
failed = True
|
|
330
|
+
if max_failures and summary["failed"] > int(max_failures):
|
|
331
|
+
print(f"matrix gate failed: failures={summary['failed']} > {int(max_failures)}")
|
|
332
|
+
failed = True
|
|
333
|
+
raise SystemExit(1 if failed else 0)
|
|
334
|
+
PY
|
|
335
|
+
|
|
336
|
+
if [[ "$gate_failed" -ne 0 ]]; then
|
|
337
|
+
exit "$gate_failed"
|
|
338
|
+
fi
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'cgi'
|
|
5
|
+
require 'fileutils'
|
|
6
|
+
require 'optparse'
|
|
7
|
+
require 'pathname'
|
|
8
|
+
|
|
9
|
+
options = {
|
|
10
|
+
sources: [],
|
|
11
|
+
deployment_target: '15.0',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
OptionParser.new do |parser|
|
|
15
|
+
parser.banner = 'Usage: ensure-ios-shim-target.rb --project ios/App.xcodeproj --app-target App --test-target AppZMRUITests --scheme AppZMRUITests --bundle-id com.example.app [options]'
|
|
16
|
+
parser.on('--project PATH', 'Xcode project path') { |value| options[:project] = value }
|
|
17
|
+
parser.on('--app-target NAME', 'App target under test') { |value| options[:app_target] = value }
|
|
18
|
+
parser.on('--test-target NAME', 'UI test target to create/update') { |value| options[:test_target] = value }
|
|
19
|
+
parser.on('--scheme NAME', 'Shared UI test scheme to create/update') { |value| options[:scheme] = value }
|
|
20
|
+
parser.on('--bundle-id ID', 'App bundle id under test') { |value| options[:bundle_id] = value }
|
|
21
|
+
parser.on('--test-bundle-id ID', 'UI test bundle id') { |value| options[:test_bundle_id] = value }
|
|
22
|
+
parser.on('--deployment-target VERSION', 'iOS deployment target') { |value| options[:deployment_target] = value }
|
|
23
|
+
parser.on('--source PATH', 'Shim source path relative to app root; repeatable') { |value| options[:sources] << value }
|
|
24
|
+
parser.on('--info-plist PATH', 'Info.plist path relative to app root') { |value| options[:info_plist] = value }
|
|
25
|
+
parser.on('-h', '--help', 'Show help') do
|
|
26
|
+
puts parser
|
|
27
|
+
exit 0
|
|
28
|
+
end
|
|
29
|
+
end.parse!
|
|
30
|
+
|
|
31
|
+
required = %i[project app_target test_target scheme bundle_id test_bundle_id info_plist]
|
|
32
|
+
missing = required.select { |key| options[key].to_s.empty? }
|
|
33
|
+
missing << :source if options[:sources].empty?
|
|
34
|
+
unless missing.empty?
|
|
35
|
+
abort "error: missing required option(s): #{missing.map { |key| "--#{key.to_s.tr('_', '-')}" }.join(', ')}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
begin
|
|
39
|
+
require 'xcodeproj'
|
|
40
|
+
rescue LoadError
|
|
41
|
+
abort 'error: missing Ruby gem xcodeproj. Install with `gem install xcodeproj` or add it to the app Gemfile.'
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def app_root
|
|
45
|
+
Pathname.new(Dir.pwd)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def project_dir(project)
|
|
49
|
+
Pathname.new(File.dirname(File.expand_path(project.path)))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def source_root_relative(project, relative_path)
|
|
53
|
+
absolute = app_root.join(relative_path).expand_path
|
|
54
|
+
absolute.relative_path_from(project_dir(project)).to_s
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def ensure_file_reference(project, relative_path)
|
|
58
|
+
xcode_path = source_root_relative(project, relative_path)
|
|
59
|
+
existing = project.files.find { |file| file.path == xcode_path || file.path == relative_path }
|
|
60
|
+
if existing
|
|
61
|
+
existing.path = xcode_path
|
|
62
|
+
existing.source_tree = 'SOURCE_ROOT'
|
|
63
|
+
return existing
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
file_ref = project.main_group.new_file(xcode_path)
|
|
67
|
+
file_ref.source_tree = 'SOURCE_ROOT'
|
|
68
|
+
file_ref
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def ensure_source(target, file_ref)
|
|
72
|
+
return if target.source_build_phase.files_references.include?(file_ref)
|
|
73
|
+
|
|
74
|
+
target.source_build_phase.add_file_reference(file_ref, true)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def ensure_dependency(target, dependency)
|
|
78
|
+
return if target.dependencies.any? { |candidate| candidate.target == dependency }
|
|
79
|
+
|
|
80
|
+
target.add_dependency(dependency)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def product_name(target)
|
|
84
|
+
target.product_reference&.display_name || "#{target.name}.app"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def buildable_reference(target, container)
|
|
88
|
+
<<~XML
|
|
89
|
+
<BuildableReference
|
|
90
|
+
BuildableIdentifier = "primary"
|
|
91
|
+
BlueprintIdentifier = "#{CGI.escapeHTML(target.uuid)}"
|
|
92
|
+
BuildableName = "#{CGI.escapeHTML(product_name(target))}"
|
|
93
|
+
BlueprintName = "#{CGI.escapeHTML(target.name)}"
|
|
94
|
+
ReferencedContainer = "#{CGI.escapeHTML(container)}">
|
|
95
|
+
</BuildableReference>
|
|
96
|
+
XML
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def write_scheme(project, app_target, test_target, scheme_name)
|
|
100
|
+
scheme_dir = File.join(project.path, 'xcshareddata/xcschemes')
|
|
101
|
+
FileUtils.mkdir_p(scheme_dir)
|
|
102
|
+
scheme_path = File.join(scheme_dir, "#{scheme_name}.xcscheme")
|
|
103
|
+
container = "container:#{File.basename(project.path)}"
|
|
104
|
+
app_ref = buildable_reference(app_target, container)
|
|
105
|
+
test_ref = buildable_reference(test_target, container)
|
|
106
|
+
|
|
107
|
+
xml = <<~XML
|
|
108
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
109
|
+
<Scheme
|
|
110
|
+
LastUpgradeVersion = "1600"
|
|
111
|
+
version = "1.7">
|
|
112
|
+
<BuildAction
|
|
113
|
+
parallelizeBuildables = "YES"
|
|
114
|
+
buildImplicitDependencies = "YES">
|
|
115
|
+
<BuildActionEntries>
|
|
116
|
+
<BuildActionEntry
|
|
117
|
+
buildForTesting = "YES"
|
|
118
|
+
buildForRunning = "YES"
|
|
119
|
+
buildForProfiling = "YES"
|
|
120
|
+
buildForArchiving = "YES"
|
|
121
|
+
buildForAnalyzing = "YES">
|
|
122
|
+
#{app_ref.rstrip}
|
|
123
|
+
</BuildActionEntry>
|
|
124
|
+
<BuildActionEntry
|
|
125
|
+
buildForTesting = "YES"
|
|
126
|
+
buildForRunning = "NO"
|
|
127
|
+
buildForProfiling = "NO"
|
|
128
|
+
buildForArchiving = "NO"
|
|
129
|
+
buildForAnalyzing = "YES">
|
|
130
|
+
#{test_ref.rstrip}
|
|
131
|
+
</BuildActionEntry>
|
|
132
|
+
</BuildActionEntries>
|
|
133
|
+
</BuildAction>
|
|
134
|
+
<TestAction
|
|
135
|
+
buildConfiguration = "Debug"
|
|
136
|
+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
137
|
+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
138
|
+
shouldUseLaunchSchemeArgsEnv = "YES">
|
|
139
|
+
<Testables>
|
|
140
|
+
<TestableReference skipped = "NO">
|
|
141
|
+
#{test_ref.rstrip}
|
|
142
|
+
<SelectedTests>
|
|
143
|
+
<Test Identifier = "ZMRShimUITestCase/testRunZMRCommand">
|
|
144
|
+
</Test>
|
|
145
|
+
</SelectedTests>
|
|
146
|
+
</TestableReference>
|
|
147
|
+
</Testables>
|
|
148
|
+
<MacroExpansion>
|
|
149
|
+
#{app_ref.rstrip}
|
|
150
|
+
</MacroExpansion>
|
|
151
|
+
<EnvironmentVariables>
|
|
152
|
+
<EnvironmentVariable key = "ZMR_SHIM_REQUEST_FILE" value = "$(ZMR_SHIM_REQUEST_FILE)" isEnabled = "YES">
|
|
153
|
+
</EnvironmentVariable>
|
|
154
|
+
<EnvironmentVariable key = "ZMR_SHIM_RESPONSE_FILE" value = "$(ZMR_SHIM_RESPONSE_FILE)" isEnabled = "YES">
|
|
155
|
+
</EnvironmentVariable>
|
|
156
|
+
<EnvironmentVariable key = "ZMR_SHIM_MODE" value = "$(ZMR_SHIM_MODE)" isEnabled = "YES">
|
|
157
|
+
</EnvironmentVariable>
|
|
158
|
+
<EnvironmentVariable key = "ZMR_SHIM_SERVER_DIR" value = "$(ZMR_SHIM_SERVER_DIR)" isEnabled = "YES">
|
|
159
|
+
</EnvironmentVariable>
|
|
160
|
+
<EnvironmentVariable key = "ZMR_APP_BUNDLE_ID" value = "$(ZMR_APP_BUNDLE_ID)" isEnabled = "YES">
|
|
161
|
+
</EnvironmentVariable>
|
|
162
|
+
</EnvironmentVariables>
|
|
163
|
+
</TestAction>
|
|
164
|
+
<LaunchAction
|
|
165
|
+
buildConfiguration = "Debug"
|
|
166
|
+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
|
167
|
+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
|
168
|
+
launchStyle = "0"
|
|
169
|
+
useCustomWorkingDirectory = "NO"
|
|
170
|
+
ignoresPersistentStateOnLaunch = "NO"
|
|
171
|
+
debugDocumentVersioning = "YES"
|
|
172
|
+
debugServiceExtension = "internal"
|
|
173
|
+
allowLocationSimulation = "YES">
|
|
174
|
+
<BuildableProductRunnable runnableDebuggingMode = "0">
|
|
175
|
+
#{app_ref.rstrip}
|
|
176
|
+
</BuildableProductRunnable>
|
|
177
|
+
</LaunchAction>
|
|
178
|
+
<ProfileAction
|
|
179
|
+
buildConfiguration = "Release"
|
|
180
|
+
shouldUseLaunchSchemeArgsEnv = "YES"
|
|
181
|
+
savedToolIdentifier = ""
|
|
182
|
+
useCustomWorkingDirectory = "NO"
|
|
183
|
+
debugDocumentVersioning = "YES">
|
|
184
|
+
<BuildableProductRunnable runnableDebuggingMode = "0">
|
|
185
|
+
#{app_ref.rstrip}
|
|
186
|
+
</BuildableProductRunnable>
|
|
187
|
+
</ProfileAction>
|
|
188
|
+
<AnalyzeAction buildConfiguration = "Debug">
|
|
189
|
+
</AnalyzeAction>
|
|
190
|
+
<ArchiveAction
|
|
191
|
+
buildConfiguration = "Release"
|
|
192
|
+
revealArchiveInOrganizer = "YES">
|
|
193
|
+
</ArchiveAction>
|
|
194
|
+
</Scheme>
|
|
195
|
+
XML
|
|
196
|
+
|
|
197
|
+
File.write(scheme_path, xml)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
project_path = File.expand_path(options[:project], Dir.pwd)
|
|
201
|
+
project = Xcodeproj::Project.open(project_path)
|
|
202
|
+
app_target = project.targets.find { |target| target.name == options[:app_target] }
|
|
203
|
+
abort "error: missing app target #{options[:app_target]}" unless app_target
|
|
204
|
+
|
|
205
|
+
test_target = project.targets.find { |target| target.name == options[:test_target] }
|
|
206
|
+
test_target ||= project.new_target(:ui_test_bundle, options[:test_target], :ios, options[:deployment_target])
|
|
207
|
+
|
|
208
|
+
ensure_dependency(test_target, app_target)
|
|
209
|
+
|
|
210
|
+
options[:sources].each do |source_path|
|
|
211
|
+
abort "error: missing #{source_path}; run install-ios-shim first" unless File.exist?(app_root.join(source_path))
|
|
212
|
+
|
|
213
|
+
ensure_source(test_target, ensure_file_reference(project, source_path))
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
info_plist = source_root_relative(project, options[:info_plist])
|
|
217
|
+
team = app_target.build_configurations.map { |config| config.build_settings['DEVELOPMENT_TEAM'] }.find { |value| !value.to_s.empty? }
|
|
218
|
+
|
|
219
|
+
test_target.build_configurations.each do |configuration|
|
|
220
|
+
settings = configuration.build_settings
|
|
221
|
+
settings['CODE_SIGN_STYLE'] = 'Automatic'
|
|
222
|
+
settings['DEVELOPMENT_TEAM'] = team if team
|
|
223
|
+
settings['GENERATE_INFOPLIST_FILE'] = 'NO'
|
|
224
|
+
settings['INFOPLIST_FILE'] = info_plist
|
|
225
|
+
settings['IPHONEOS_DEPLOYMENT_TARGET'] = options[:deployment_target]
|
|
226
|
+
settings['PRODUCT_BUNDLE_IDENTIFIER'] = options[:test_bundle_id]
|
|
227
|
+
settings['PRODUCT_MODULE_NAME'] = '$(TARGET_NAME)'
|
|
228
|
+
settings['PRODUCT_NAME'] = '$(TARGET_NAME)'
|
|
229
|
+
settings['SWIFT_VERSION'] = '5.0'
|
|
230
|
+
settings['TARGETED_DEVICE_FAMILY'] = '1,2'
|
|
231
|
+
settings['TEST_TARGET_NAME'] = options[:app_target]
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
project.save
|
|
235
|
+
write_scheme(project, app_target, test_target, options[:scheme])
|
|
236
|
+
|
|
237
|
+
puts "ensured #{options[:test_target]} and #{options[:scheme]}.xcscheme"
|