mobile-debug-mcp 0.34.0 → 0.36.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/.agents/skills/mcp-builder/SKILL.md +44 -64
- package/.agents/skills/mcp-builder/references/build-flags.md +44 -43
- package/.agents/skills/mcp-builder/references/diagnostics-schema.md +4 -1
- package/.agents/skills/mcp-builder/references/toolchain-details.md +49 -62
- package/.agents/skills/spec-review/references/spec-review-checklist.md +7 -0
- package/.agents/skills/test-authoring/SKILL.md +7 -6
- package/.agents/skills/test-authoring/references/repo-test-layout.md +3 -1
- package/.agents/skills/test-authoring/references/test-authoring-workflow.md +16 -7
- package/.agents/skills/test-authoring/references/test-quality-checklist.md +2 -1
- package/.cursor-plugin/marketplace.json +1 -1
- package/.github/copilot-instructions.md +14 -36
- package/.github/workflows/ci.yml +4 -4
- package/AGENTS.md +12 -3
- package/CONTRIBUTING.md +79 -0
- package/README.md +218 -129
- package/agent-plugin/.antigravity-plugin/README.md +48 -0
- package/agent-plugin/.antigravity-plugin/plugin.json +18 -0
- package/agent-plugin/.claude-plugin/plugin.json +2 -2
- package/agent-plugin/.codex-plugin/plugin.json +1 -1
- package/agent-plugin/.cursor-plugin/plugin.json +2 -2
- package/agent-plugin/.mcp.json +1 -1
- package/agent-plugin/README.md +50 -11
- package/agent-plugin/cursor/.cursor-plugin/plugin.json +23 -0
- package/agent-plugin/cursor/LICENSE +201 -0
- package/agent-plugin/cursor/README.md +9 -0
- package/agent-plugin/cursor/skills/mobile-debug-mcp/SKILL.md +253 -0
- package/agent-plugin/mcp.json +1 -1
- package/agent-plugin/mcp_config.json +13 -0
- package/agent-plugin/plugin.json +1 -1
- package/agent-plugin/skills/mobile-debug-mcp/SKILL.md +62 -17
- package/dist/contracts/execution-controls.js +24 -0
- package/dist/contracts/selector.js +8 -0
- package/dist/contracts/target.js +15 -0
- package/dist/contracts/ui.js +1 -0
- package/dist/debugger/android.js +2 -1
- package/dist/debugger/diagnostics.js +54 -0
- package/dist/debugger/guard.js +4 -4
- package/dist/debugger/ios-worker.js +62 -0
- package/dist/debugger/ios.js +57 -0
- package/dist/debugger/protocol.js +33 -0
- package/dist/debugger/sessions.js +133 -74
- package/dist/debugger/worker.js +59 -46
- package/dist/interact/adjustment/adjustment-context.js +41 -0
- package/dist/interact/adjustment/adjustment-gestures.js +55 -0
- package/dist/interact/adjustment/adjustment-plan.js +18 -0
- package/dist/interact/adjustment/adjustment-resolution.js +127 -0
- package/dist/interact/adjustment/adjustment-result.js +19 -0
- package/dist/interact/adjustment/adjustment-semantic.js +64 -0
- package/dist/interact/adjustment/adjustment-verification.js +35 -0
- package/dist/interact/adjustment/adjustment.js +52 -0
- package/dist/interact/adjustment/control-geometry.js +87 -0
- package/dist/interact/adjustment/control-values.js +136 -0
- package/dist/interact/adjustment/slider-labels.js +84 -0
- package/dist/interact/assertions/screen-assertion.js +37 -0
- package/dist/interact/assertions/state-assertion-context.js +35 -0
- package/dist/interact/assertions/state-assertion.js +76 -0
- package/dist/interact/assertions/state-comparison.js +141 -0
- package/dist/interact/assertions/visibility-assertion.js +34 -0
- package/dist/interact/conditional-action.js +12 -7
- package/dist/interact/index.js +77 -3092
- package/dist/interact/input-service.js +1 -0
- package/dist/interact/interaction-trace.js +21 -0
- package/dist/interact/model.js +1 -0
- package/dist/interact/observation.js +1 -0
- package/dist/interact/targeting/candidate-ancestor.js +58 -0
- package/dist/interact/targeting/candidate-result.js +23 -0
- package/dist/interact/targeting/candidate-scoring.js +50 -0
- package/dist/interact/targeting/element-helpers.js +216 -0
- package/dist/interact/targeting/find-element.js +65 -0
- package/dist/interact/targeting/nearby-control.js +56 -0
- package/dist/interact/targeting/resolved-elements.js +21 -0
- package/dist/interact/targeting/tap-target.js +17 -0
- package/dist/interact/targeting/target-scope.js +104 -0
- package/dist/interact/waits/legacy-log-wait.js +38 -0
- package/dist/interact/waits/legacy-wait-observations.js +103 -0
- package/dist/interact/waits/legacy-wait-state.js +34 -0
- package/dist/interact/waits/legacy-wait.js +48 -0
- package/dist/interact/waits/screen-wait.js +49 -0
- package/dist/interact/waits/ui-change-helpers.js +157 -0
- package/dist/interact/waits/ui-change-state.js +18 -0
- package/dist/interact/waits/ui-change-wait.js +77 -0
- package/dist/interact/waits/ui-wait-condition.js +56 -0
- package/dist/interact/waits/ui-wait-state.js +38 -0
- package/dist/interact/waits/ui-wait.js +106 -0
- package/dist/manage/android.js +55 -51
- package/dist/manage/index.js +38 -75
- package/dist/manage/ios/build-types.js +1 -0
- package/dist/manage/ios/build.js +210 -0
- package/dist/manage/ios/project-discovery.js +29 -0
- package/dist/manage/ios/scheme.js +18 -0
- package/dist/manage/ios.js +44 -256
- package/dist/observe/android/context.js +1 -0
- package/dist/observe/android/current-screen.js +55 -0
- package/dist/observe/android/fingerprint.js +13 -0
- package/dist/observe/android/log-stream.js +132 -0
- package/dist/observe/android/logs.js +107 -0
- package/dist/observe/android/screenshot.js +70 -0
- package/dist/observe/android/ui-tree.js +107 -0
- package/dist/observe/android.js +16 -520
- package/dist/observe/evidence.js +114 -75
- package/dist/observe/index.js +16 -263
- package/dist/observe/ios/context.js +16 -0
- package/dist/observe/ios/fingerprint.js +12 -0
- package/dist/observe/ios/log-stream.js +105 -0
- package/dist/observe/ios/logs.js +168 -0
- package/dist/observe/ios/screenshot.js +22 -0
- package/dist/observe/ios/ui-elements.js +241 -0
- package/dist/observe/ios/ui-tree.js +137 -0
- package/dist/observe/ios-trace/artifact.js +63 -0
- package/dist/observe/ios-trace/capabilities.js +86 -0
- package/dist/observe/ios-trace/native.js +93 -0
- package/dist/observe/ios-trace/notification.js +35 -0
- package/dist/observe/ios-trace/process.js +98 -0
- package/dist/observe/ios-trace/sessions.js +244 -0
- package/dist/observe/ios.js +17 -715
- package/dist/observe/logs/model.js +1 -0
- package/dist/{network → observe/network}/index.js +2 -2
- package/dist/observe/readiness-service.js +5 -3
- package/dist/observe/readiness.js +33 -14
- package/dist/observe/screenshot-encoding.js +41 -0
- package/dist/observe/snapshot-collection.js +79 -0
- package/dist/observe/snapshot-logs.js +30 -0
- package/dist/observe/snapshot-metadata.js +43 -31
- package/dist/observe/snapshot-semantic.js +115 -0
- package/dist/observe/system-trace-error.js +7 -0
- package/dist/observe/system-trace-router.js +81 -0
- package/dist/observe/system-trace.js +26 -21
- package/dist/runtime/action-results.js +270 -0
- package/dist/runtime/clock.js +4 -0
- package/dist/server/common.js +1 -256
- package/dist/server/definitions/assert.js +162 -0
- package/dist/server/definitions/diagnostic.js +118 -0
- package/dist/server/definitions/input.js +274 -0
- package/dist/server/definitions/manage.js +194 -0
- package/dist/server/definitions/observe.js +149 -0
- package/dist/server/definitions/target.js +138 -0
- package/dist/server/definitions/wait.js +105 -0
- package/dist/server/handlers/action-execution.js +163 -0
- package/dist/server/handlers/correlation.js +23 -0
- package/dist/server/handlers/debugger-handlers.js +67 -0
- package/dist/server/handlers/debugger-response.js +9 -0
- package/dist/server/handlers/diagnostic.js +77 -0
- package/dist/server/handlers/handler-types.js +1 -0
- package/dist/server/handlers/input.js +154 -0
- package/dist/server/handlers/interact.js +113 -0
- package/dist/server/handlers/journey-assertions.js +35 -0
- package/dist/server/handlers/journey.js +147 -0
- package/dist/server/handlers/manage.js +119 -0
- package/dist/server/handlers/observe.js +95 -0
- package/dist/server/handlers/payload.js +4 -0
- package/dist/server/handlers/tool-handlers.js +170 -0
- package/dist/server/schemas/condition-schema.js +2 -0
- package/dist/server/{debugger-schema.js → schemas/debugger-schema.js} +20 -12
- package/dist/server/{journey-schema.js → schemas/journey-schema.js} +7 -22
- package/dist/server/schemas/system-trace-schema.js +22 -0
- package/dist/server/tool-definitions.js +58 -1135
- package/dist/server-core.js +2 -2
- package/dist/server.js +4 -4
- package/dist/system/android-status.js +101 -0
- package/dist/system/android.js +37 -27
- package/dist/system/index.js +13 -439
- package/dist/system/ios-status.js +132 -0
- package/dist/system/ios.js +11 -8
- package/dist/system/model.js +1 -0
- package/dist/system/probes.js +105 -0
- package/dist/system/status-summary.js +43 -0
- package/dist/system/unsupported-status.js +52 -0
- package/dist/utils/android/commands.js +89 -0
- package/dist/utils/android/gradle.js +139 -0
- package/dist/utils/android/logs.js +103 -0
- package/dist/utils/android/ui-tree.js +233 -0
- package/dist/utils/android/utils.js +6 -564
- package/dist/utils/cli/idb/idb-helper.js +4 -6
- package/dist/utils/cli/idb/install-idb.js +13 -9
- package/dist/utils/cli/ios/preflight-ios.js +42 -38
- package/dist/utils/ios/utils.js +48 -46
- package/dist/utils/java.js +65 -49
- package/dist/utils/resolve-device.js +4 -2
- package/dist/utils/ui/index.js +26 -26
- package/docs/CHANGELOG.md +67 -1
- package/docs/README.md +38 -0
- package/docs/agent-plugin-installation-verification.md +39 -0
- package/docs/benchmarks/README.md +18 -0
- package/docs/benchmarks/interaction-latency/README.md +14 -0
- package/docs/benchmarks/interaction-latency/final.md +4 -1
- package/docs/benchmarks/interaction-latency/ios-validation.md +2 -0
- package/docs/benchmarks/ui-tree-performance/README.md +24 -16
- package/docs/benchmarks/ui-tree-performance/comparison.md +11 -10
- package/docs/code-structure.md +120 -0
- package/docs/specs/001-state-verification/spec.md +16 -14
- package/docs/specs/002-richer-element-identity/spec.md +12 -10
- package/docs/specs/003-wait-and-synchronization-reliability/spec.md +12 -10
- package/docs/specs/004-action-verification-routing/spec.md +3 -3
- package/docs/specs/005-unified-action-execution-and-verification-model/spec.md +2 -0
- package/docs/specs/006-runtime-action-instrumentation-and-binding-layer/spec.md +2 -0
- package/docs/specs/007-actionability-resolution-and-executable-target-selection/spec.md +3 -1
- package/docs/specs/008-adjustable-control-support-and-semantic-value-manipulation/spec.md +3 -4
- package/docs/specs/009-semantic-control-modeling-for-custom-and-composite-controls/spec.md +2 -0
- package/docs/specs/010-verification-stabilization-and-temporal-convergence/spec.md +2 -2
- package/docs/specs/011-recovery-and-replanning-for-failed-or-ambiguous-interaction-flows/spec.md +2 -2
- package/docs/specs/011.1-recovery-contract-types-and-runtime-wiring-spec/spec.md +2 -0
- package/docs/specs/012-action-trace-and-xecution-observability/spec.md +2 -3
- package/docs/specs/013-wait-and-synchronization-reliability/spec.md +19 -19
- package/docs/specs/014-actionability-resolution/spec.md +20 -18
- package/docs/specs/015-adjustable-control-support/spec.md +17 -15
- package/docs/specs/016-environment-tools/spec.md +20 -20
- package/docs/specs/018-interaction-latency-and-batched-journeys/spec.md +2 -0
- package/docs/specs/019-ui-tree-observation-performance/spec.md +3 -1
- package/docs/specs/020-conditional-actions/intent.md +5 -3
- package/docs/specs/020-conditional-actions/plan.md +8 -9
- package/docs/specs/020-conditional-actions/spec.md +4 -2
- package/docs/specs/021-app-readiness-signals/intent.md +4 -2
- package/docs/specs/021-app-readiness-signals/plan.md +7 -6
- package/docs/specs/021-app-readiness-signals/spec.md +3 -1
- package/docs/specs/022-correlated-evidence/intent.md +4 -4
- package/docs/specs/022-correlated-evidence/plan.md +10 -11
- package/docs/specs/022-correlated-evidence/spec.md +3 -1
- package/docs/specs/023-android-system-traces/intent.md +3 -2
- package/docs/specs/023-android-system-traces/plan.md +5 -7
- package/docs/specs/023-android-system-traces/spec.md +3 -1
- package/docs/specs/024-android-heap-dump-capture/intent.md +3 -1
- package/docs/specs/025-android-runtime-debugger/intent.md +4 -3
- package/docs/specs/025-android-runtime-debugger/plan.md +3 -1
- package/docs/specs/025-android-runtime-debugger/spec.md +4 -3
- package/docs/specs/026-code-structure-and-quality-gates/health-plan.md +88 -0
- package/docs/specs/026-code-structure-and-quality-gates/intent.md +47 -0
- package/docs/specs/026-code-structure-and-quality-gates/plan.md +191 -0
- package/docs/specs/026-code-structure-and-quality-gates/spec.md +138 -0
- package/docs/specs/026-code-structure-and-quality-gates/verification-final.json +200 -0
- package/docs/specs/026-code-structure-and-quality-gates/verification-thin-facade.json +154 -0
- package/docs/specs/026-code-structure-and-quality-gates/verification.json +136 -0
- package/docs/specs/027-debugger-failure-diagnostics/intent.md +43 -0
- package/docs/specs/027-debugger-failure-diagnostics/plan.md +86 -0
- package/docs/specs/027-debugger-failure-diagnostics/spec.md +133 -0
- package/docs/specs/028-compact-debugger-responses/intent.md +41 -0
- package/docs/specs/028-compact-debugger-responses/plan.md +112 -0
- package/docs/specs/028-compact-debugger-responses/spec.md +108 -0
- package/docs/specs/029-kotlin-locals-validation/report.md +119 -0
- package/docs/specs/030-android-coroutine-validation/evidence.json +475 -0
- package/docs/specs/030-android-coroutine-validation/intent.md +47 -0
- package/docs/specs/030-android-coroutine-validation/plan.md +147 -0
- package/docs/specs/030-android-coroutine-validation/report.md +240 -0
- package/docs/specs/030-android-coroutine-validation/spec.md +107 -0
- package/docs/specs/031-ios-system-traces/intent.md +38 -0
- package/docs/specs/031-ios-system-traces/investigation.md +92 -0
- package/docs/specs/031-ios-system-traces/late-run-failure-evidence.json +32 -0
- package/docs/specs/031-ios-system-traces/plan.md +138 -0
- package/docs/specs/031-ios-system-traces/pr102-monitor-evidence.json +22 -0
- package/docs/specs/031-ios-system-traces/simulator-evidence.json +47 -0
- package/docs/specs/031-ios-system-traces/spec.md +135 -0
- package/docs/specs/032-ios-runtime-debugger/evidence.json +176 -0
- package/docs/specs/032-ios-runtime-debugger/intent.md +38 -0
- package/docs/specs/032-ios-runtime-debugger/plan.md +95 -0
- package/docs/specs/032-ios-runtime-debugger/report.md +205 -0
- package/docs/specs/032-ios-runtime-debugger/spec.md +153 -0
- package/docs/tools/TOOLS.md +31 -18
- package/docs/tools/{evidence.md → correlated-evidence.md} +9 -1
- package/docs/tools/debugger.md +120 -4
- package/docs/tools/interact.md +42 -20
- package/docs/tools/manage.md +25 -44
- package/docs/tools/observe.md +55 -23
- package/docs/tools/system-traces.md +89 -14
- package/docs/tools/system.md +12 -5
- package/package.json +12 -8
- package/scripts/agent-plugin.mjs +55 -5
- package/scripts/architecture-graph.mjs +69 -0
- package/scripts/check-architecture.mjs +47 -0
- package/scripts/code-quality-baseline.json +1 -0
- package/scripts/code-quality.mjs +161 -0
- package/src/contracts/execution-controls.ts +26 -0
- package/src/contracts/selector.ts +17 -0
- package/src/contracts/target.ts +19 -0
- package/src/contracts/ui.ts +94 -0
- package/src/debugger/android.ts +3 -2
- package/src/debugger/diagnostics.ts +43 -0
- package/src/debugger/guard.ts +10 -5
- package/src/debugger/ios-worker.ts +49 -0
- package/src/debugger/ios.ts +45 -0
- package/{worker → src/debugger/java}/DebuggerWorker.java +9 -3
- package/src/debugger/lldb/breakpoints.py +51 -0
- package/src/debugger/lldb/inspection.py +109 -0
- package/src/debugger/lldb/worker.py +203 -0
- package/src/debugger/protocol.ts +37 -0
- package/src/debugger/sessions.ts +326 -155
- package/src/debugger/worker.ts +57 -41
- package/src/interact/adjustment/adjustment-context.ts +83 -0
- package/src/interact/adjustment/adjustment-gestures.ts +48 -0
- package/src/interact/adjustment/adjustment-plan.ts +23 -0
- package/src/interact/adjustment/adjustment-resolution.ts +104 -0
- package/src/interact/adjustment/adjustment-result.ts +34 -0
- package/src/interact/adjustment/adjustment-semantic.ts +53 -0
- package/src/interact/adjustment/adjustment-verification.ts +60 -0
- package/src/interact/adjustment/adjustment.ts +49 -0
- package/src/interact/adjustment/control-geometry.ts +120 -0
- package/src/interact/adjustment/control-values.ts +141 -0
- package/src/interact/adjustment/slider-labels.ts +84 -0
- package/src/interact/assertions/screen-assertion.ts +39 -0
- package/src/interact/assertions/state-assertion-context.ts +48 -0
- package/src/interact/assertions/state-assertion.ts +64 -0
- package/src/interact/assertions/state-comparison.ts +168 -0
- package/src/interact/assertions/visibility-assertion.ts +41 -0
- package/src/interact/conditional-action.ts +18 -12
- package/src/interact/index.ts +89 -3494
- package/src/interact/input-service.ts +11 -0
- package/src/interact/interaction-trace.ts +36 -0
- package/src/interact/model.ts +109 -0
- package/src/interact/observation.ts +7 -0
- package/src/interact/targeting/candidate-ancestor.ts +49 -0
- package/src/interact/targeting/candidate-result.ts +24 -0
- package/src/interact/targeting/candidate-scoring.ts +42 -0
- package/src/interact/targeting/element-helpers.ts +243 -0
- package/src/interact/targeting/find-element.ts +47 -0
- package/src/interact/targeting/nearby-control.ts +46 -0
- package/src/interact/targeting/resolved-elements.ts +20 -0
- package/src/interact/targeting/tap-target.ts +22 -0
- package/src/interact/targeting/target-scope.ts +121 -0
- package/src/interact/waits/legacy-log-wait.ts +30 -0
- package/src/interact/waits/legacy-wait-observations.ts +55 -0
- package/src/interact/waits/legacy-wait-state.ts +48 -0
- package/src/interact/waits/legacy-wait.ts +34 -0
- package/src/interact/waits/screen-wait.ts +37 -0
- package/src/interact/waits/ui-change-helpers.ts +184 -0
- package/src/interact/waits/ui-change-state.ts +26 -0
- package/src/interact/waits/ui-change-wait.ts +59 -0
- package/src/interact/waits/ui-wait-condition.ts +52 -0
- package/src/interact/waits/ui-wait-state.ts +55 -0
- package/src/interact/waits/ui-wait.ts +84 -0
- package/src/manage/android.ts +56 -50
- package/src/manage/index.ts +63 -93
- package/src/manage/ios/build-types.ts +14 -0
- package/src/manage/ios/build.ts +230 -0
- package/src/manage/ios/project-discovery.ts +26 -0
- package/src/manage/ios/scheme.ts +16 -0
- package/src/manage/ios.ts +67 -298
- package/src/observe/android/context.ts +1 -0
- package/src/observe/android/current-screen.ts +63 -0
- package/src/observe/android/fingerprint.ts +13 -0
- package/src/observe/android/log-stream.ts +127 -0
- package/src/observe/android/logs.ts +110 -0
- package/src/observe/android/screenshot.ts +79 -0
- package/src/observe/android/ui-tree.ts +113 -0
- package/src/observe/android.ts +17 -531
- package/src/observe/evidence.ts +122 -70
- package/src/observe/index.ts +24 -286
- package/src/observe/ios/context.ts +22 -0
- package/src/observe/ios/fingerprint.ts +12 -0
- package/src/observe/ios/log-stream.ts +103 -0
- package/src/observe/ios/logs.ts +179 -0
- package/src/observe/ios/screenshot.ts +26 -0
- package/src/observe/ios/ui-elements.ts +266 -0
- package/src/observe/ios/ui-tree.ts +143 -0
- package/src/observe/ios-trace/artifact.ts +55 -0
- package/src/observe/ios-trace/capabilities.ts +74 -0
- package/src/observe/ios-trace/native.ts +72 -0
- package/src/observe/ios-trace/notification.ts +30 -0
- package/src/observe/ios-trace/process.ts +82 -0
- package/src/observe/ios-trace/sessions.ts +204 -0
- package/src/observe/ios.ts +18 -749
- package/src/observe/logs/model.ts +11 -0
- package/src/{network → observe/network}/index.ts +2 -2
- package/src/observe/readiness-service.ts +6 -4
- package/src/observe/readiness.ts +42 -21
- package/src/observe/screenshot-encoding.ts +32 -0
- package/src/observe/snapshot-collection.ts +69 -0
- package/src/observe/snapshot-logs.ts +26 -0
- package/src/observe/snapshot-metadata.ts +51 -31
- package/src/observe/snapshot-semantic.ts +153 -0
- package/src/observe/system-trace-error.ts +3 -0
- package/src/observe/system-trace-router.ts +61 -0
- package/src/observe/system-trace.ts +28 -19
- package/src/runtime/action-results.ts +376 -0
- package/src/runtime/clock.ts +9 -0
- package/src/server/common.ts +9 -359
- package/src/server/definitions/assert.ts +164 -0
- package/src/server/definitions/diagnostic.ts +120 -0
- package/src/server/definitions/input.ts +276 -0
- package/src/server/definitions/manage.ts +196 -0
- package/src/server/definitions/observe.ts +151 -0
- package/src/server/definitions/target.ts +140 -0
- package/src/server/definitions/wait.ts +107 -0
- package/src/server/handlers/action-execution.ts +164 -0
- package/src/server/handlers/correlation.ts +20 -0
- package/src/server/handlers/debugger-handlers.ts +58 -0
- package/src/server/handlers/debugger-response.ts +7 -0
- package/src/server/handlers/diagnostic.ts +86 -0
- package/src/server/handlers/handler-types.ts +7 -0
- package/src/server/handlers/input.ts +173 -0
- package/src/server/handlers/interact.ts +135 -0
- package/src/server/handlers/journey-assertions.ts +39 -0
- package/src/server/handlers/journey.ts +132 -0
- package/src/server/handlers/manage.ts +141 -0
- package/src/server/handlers/observe.ts +116 -0
- package/src/server/handlers/payload.ts +36 -0
- package/src/server/handlers/tool-handlers.ts +170 -0
- package/src/server/schemas/condition-schema.ts +2 -0
- package/src/server/schemas/debugger-schema.ts +46 -0
- package/src/server/{journey-schema.ts → schemas/journey-schema.ts} +7 -22
- package/src/server/schemas/system-trace-schema.ts +23 -0
- package/src/server/tool-definitions.ts +67 -1143
- package/src/server-core.ts +2 -2
- package/src/server.ts +4 -4
- package/src/system/android-status.ts +124 -0
- package/src/system/android.ts +33 -21
- package/src/system/index.ts +21 -525
- package/src/system/ios-status.ts +147 -0
- package/src/system/ios.ts +14 -10
- package/src/system/model.ts +41 -0
- package/src/system/probes.ts +114 -0
- package/src/system/status-summary.ts +47 -0
- package/src/system/unsupported-status.ts +55 -0
- package/src/types.ts +16 -106
- package/src/utils/android/commands.ts +81 -0
- package/src/utils/android/gradle.ts +132 -0
- package/src/utils/android/logs.ts +103 -0
- package/src/utils/android/ui-tree.ts +239 -0
- package/src/utils/android/utils.ts +16 -556
- package/src/utils/cli/idb/check-idb.ts +4 -4
- package/src/utils/cli/idb/idb-helper.ts +6 -8
- package/src/utils/cli/idb/install-idb.ts +15 -9
- package/src/utils/cli/ios/preflight-ios.ts +39 -31
- package/src/utils/cli/ios/run-ios-ui-tree-tap.ts +4 -4
- package/src/utils/ios/utils.ts +60 -61
- package/src/utils/java.ts +62 -43
- package/src/utils/resolve-device.ts +9 -6
- package/src/utils/ui/index.ts +28 -28
- package/test/device/automated/debugger/coroutine-lifecycle.ts +27 -0
- package/test/device/automated/debugger/coroutine-support.ts +140 -0
- package/test/device/automated/debugger/coroutine.android.test.ts +169 -0
- package/test/device/automated/debugger/kotlin.android.test.ts +127 -0
- package/test/device/automated/debugger/runtime.android.test.ts +19 -3
- package/test/device/automated/debugger/runtime.ios.test.ts +221 -0
- package/test/device/automated/interact/interaction.android.test.ts +56 -0
- package/test/device/automated/observe/ios-system-trace.smoke.ts +119 -0
- package/test/device/automated/observe/system-trace.smoke.ts +1 -1
- package/test/device/manual/interact/modul8_selector_tap.manual.ts +1 -1
- package/test/fixtures/contracts/tool-definitions.json +3696 -0
- package/test/fixtures/coroutine-app/MainActivity.kt +100 -0
- package/test/fixtures/coroutine-app/README.md +48 -0
- package/test/fixtures/coroutine-app/build.ts +35 -0
- package/test/fixtures/debugger-app/MainActivity.java +21 -0
- package/test/fixtures/debugger-app/MainActivity.kt +32 -0
- package/test/fixtures/debugger-app/README.md +52 -0
- package/test/fixtures/debugger-app/build.ts +25 -15
- package/test/fixtures/ios-debugger-app/Main.swift +95 -0
- package/test/fixtures/ios-debugger-app/README.md +27 -0
- package/test/fixtures/ios-debugger-app/build.ts +21 -0
- package/test/fixtures/latency-journey-app/README.md +2 -0
- package/test/fixtures/readiness-app/README.md +2 -0
- package/test/helpers/ios-trace-shutdown.ts +29 -0
- package/test/unit/debugger/coroutine-lifecycle.test.ts +43 -0
- package/test/unit/debugger/diagnostic-sessions.test.ts +67 -0
- package/test/unit/debugger/diagnostics.test.ts +80 -0
- package/test/unit/debugger/guards.test.ts +4 -0
- package/test/unit/debugger/handle-guards.test.ts +88 -0
- package/test/unit/debugger/ios-preparation.test.ts +57 -0
- package/test/unit/debugger/ios.test.ts +104 -0
- package/test/unit/debugger/lldb-worker.py +166 -0
- package/test/unit/debugger/lldb-worker.test.ts +10 -0
- package/test/unit/debugger/preparation.test.ts +32 -0
- package/test/unit/debugger/protocol.test.ts +15 -0
- package/test/unit/debugger/sessions.test.ts +1 -1
- package/test/unit/interact/adjustment_service.test.ts +42 -0
- package/test/unit/interact/adjustment_verification.test.ts +35 -0
- package/test/unit/interact/clock_dependencies.test.ts +63 -0
- package/test/unit/interact/element_helpers.test.ts +19 -0
- package/test/unit/interact/legacy_wait_service.test.ts +17 -0
- package/test/unit/interact/state_comparison.test.ts +20 -0
- package/test/unit/interact/target_scope.test.ts +35 -0
- package/test/unit/network/get_network_activity.test.ts +1 -1
- package/test/unit/observe/ios-trace-native.test.ts +90 -0
- package/test/unit/observe/ios-trace-sessions.test.ts +131 -0
- package/test/unit/observe/readiness.test.ts +1 -1
- package/test/unit/observe/screenshot-encoding.test.ts +30 -0
- package/test/unit/observe/snapshot-logs.test.ts +14 -0
- package/test/unit/observe/system-trace-router.test.ts +49 -0
- package/test/unit/quality/agent-plugin-export.test.ts +38 -0
- package/test/unit/quality/architecture.test.ts +60 -0
- package/test/unit/quality/code-quality.test.ts +58 -0
- package/test/unit/quality/interaction_boundaries.test.ts +24 -0
- package/test/unit/server/conditional_actions.test.ts +1 -1
- package/test/unit/server/conditional_schema.test.ts +2 -2
- package/test/unit/server/debugger-response.test.ts +63 -0
- package/test/unit/server/system-trace.test.ts +12 -3
- package/test/unit/server/tool_definitions_parity.test.ts +8 -0
- package/dist/server/debugger-handlers.js +0 -38
- package/dist/server/system-trace-schema.js +0 -17
- package/dist/server/tool-handlers.js +0 -1000
- package/docs/ROADMAP.md +0 -646
- package/docs/benchmarks/ui-tree-performance/bridge-probe.json +0 -29
- package/docs/benchmarks/ui-tree-performance/initial-baseline.json +0 -59560
- package/docs/specs/baseline-spec-v0.md +0 -312
- package/docs/specs/mcp-tooling-spec-v1.md +0 -446
- package/src/server/debugger-handlers.ts +0 -29
- package/src/server/debugger-schema.ts +0 -37
- package/src/server/system-trace-schema.ts +0 -17
- package/src/server/tool-handlers.ts +0 -1008
- /package/dist/{server/condition-schema.js → contracts/condition.js} +0 -0
- /package/dist/server/{readiness-schema.js → schemas/readiness-schema.js} +0 -0
- /package/src/{server/condition-schema.ts → contracts/condition.ts} +0 -0
- /package/src/server/{readiness-schema.ts → schemas/readiness-schema.ts} +0 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
package dev.mobiledebugmcp.coroutinefixture
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.os.Bundle
|
|
5
|
+
import android.widget.Button
|
|
6
|
+
import android.widget.LinearLayout
|
|
7
|
+
import android.widget.TextView
|
|
8
|
+
import java.util.concurrent.Executors
|
|
9
|
+
import kotlinx.coroutines.*
|
|
10
|
+
|
|
11
|
+
class MainActivity : Activity() {
|
|
12
|
+
private val first = Executors.newSingleThreadExecutor { Thread(it, "fixture-first") }.asCoroutineDispatcher()
|
|
13
|
+
private val second = Executors.newSingleThreadExecutor { Thread(it, "fixture-second") }.asCoroutineDispatcher()
|
|
14
|
+
private val scope = CoroutineScope(SupervisorJob() + first)
|
|
15
|
+
private var gate = CompletableDeferred<Unit>()
|
|
16
|
+
private var job: Job? = null
|
|
17
|
+
private lateinit var status: TextView
|
|
18
|
+
|
|
19
|
+
override fun onCreate(saved: Bundle?) {
|
|
20
|
+
super.onCreate(saved)
|
|
21
|
+
val layout = LinearLayout(this)
|
|
22
|
+
layout.orientation = LinearLayout.VERTICAL
|
|
23
|
+
layout.setPadding(0, 160, 0, 0)
|
|
24
|
+
status = TextView(this)
|
|
25
|
+
status.text = "Coroutine ready"
|
|
26
|
+
layout.addView(status)
|
|
27
|
+
fun button(label: String, action: () -> Unit) {
|
|
28
|
+
val button = Button(this)
|
|
29
|
+
button.text = label
|
|
30
|
+
button.setOnClickListener { action() }
|
|
31
|
+
layout.addView(button)
|
|
32
|
+
}
|
|
33
|
+
button("Complete run") { start(false) }
|
|
34
|
+
button("Cancellation run") { start(true) }
|
|
35
|
+
button("Release gate") { gate.complete(Unit) }
|
|
36
|
+
button("Cancel job") { job?.cancel() }
|
|
37
|
+
setContentView(layout)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private fun show(text: String) { runOnUiThread { status.text = text } }
|
|
41
|
+
|
|
42
|
+
private fun start(cancel: Boolean) {
|
|
43
|
+
if (job?.isActive == true) { show("Coroutine busy"); return }
|
|
44
|
+
gate = CompletableDeferred()
|
|
45
|
+
job = scope.launch {
|
|
46
|
+
if (cancel) cancelWork() else completeWork()
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private suspend fun completeWork() {
|
|
51
|
+
val firstThread = Thread.currentThread().name
|
|
52
|
+
val known = 42
|
|
53
|
+
check(firstThread == "fixture-first") // BREAK_BEFORE
|
|
54
|
+
// Queued on the same single-thread executor: runs only after gate.await yields.
|
|
55
|
+
first.executor.execute { show("Coroutine waiting") }
|
|
56
|
+
gate.await() // BREAK_SUSPEND
|
|
57
|
+
val after = known + 1 // BREAK_AFTER
|
|
58
|
+
withContext(second) {
|
|
59
|
+
val dispatcherThread = Thread.currentThread().name
|
|
60
|
+
val nested = async { helper(after) } // BREAK_DISPATCH
|
|
61
|
+
check(dispatcherThread == "fixture-second")
|
|
62
|
+
val answer = nested.await()
|
|
63
|
+
show("Coroutine result $answer") // BREAK_RESULT
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private fun helper(value: Int): Int {
|
|
68
|
+
val result = increment(value) // BREAK_HELPER
|
|
69
|
+
return result // BREAK_HELPER_RETURN
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private fun increment(value: Int): Int {
|
|
73
|
+
val result = value + 1 // BREAK_INCREMENT
|
|
74
|
+
return result // BREAK_INCREMENT_RETURN
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private suspend fun cancelWork() = coroutineScope {
|
|
78
|
+
val child = launch(second) {
|
|
79
|
+
try {
|
|
80
|
+
// The child must yield at gate.await before this queued work can run.
|
|
81
|
+
second.executor.execute { show("Cancellation waiting") }
|
|
82
|
+
gate.await()
|
|
83
|
+
error("Cancelled child must not cross gate")
|
|
84
|
+
} finally {
|
|
85
|
+
show("Cancellation finally") // BREAK_CANCEL
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
try { child.join() } finally {
|
|
89
|
+
withContext(NonCancellable) { child.cancelAndJoin() }
|
|
90
|
+
show("Coroutine cancelled")
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
override fun onDestroy() {
|
|
95
|
+
scope.cancel()
|
|
96
|
+
first.close()
|
|
97
|
+
second.close()
|
|
98
|
+
super.onDestroy()
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Isolated Android coroutine debugger fixture
|
|
2
|
+
|
|
3
|
+
Separate disposable package `dev.mobiledebugmcp.coroutinefixture`; the runner
|
|
4
|
+
refuses to replace an existing installation. No server dependency, debug agent,
|
|
5
|
+
automatic download or production app instrumentation is introduced.
|
|
6
|
+
|
|
7
|
+
Requires Node 22+, JDK 17, Android SDK platform/build-tools 36, Kotlin JVM compiler
|
|
8
|
+
and matching stdlib 2.0.21, and kotlinx-coroutines-core-jvm 1.9.0. Provide local
|
|
9
|
+
paths explicitly:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
export JAVA_HOME=/path/to/jdk-17
|
|
13
|
+
export MCP_KOTLINC=/path/to/kotlin/bin/kotlinc
|
|
14
|
+
export MCP_KOTLIN_STDLIB=/path/to/kotlin/lib/kotlin-stdlib.jar
|
|
15
|
+
export MCP_COROUTINES_CORE=/path/to/kotlinx-coroutines-core-jvm-1.9.0.jar
|
|
16
|
+
npm run build
|
|
17
|
+
MCP_COROUTINE_DEVICE=emulator-5554 npx tsx test/device/automated/debugger/coroutine.android.test.ts
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`npm run test:device` discovers the runner and reports **UNRUN** unless
|
|
21
|
+
`MCP_COROUTINE_DEVICE` is set. A configured run with missing prerequisites fails;
|
|
22
|
+
it cannot silently pass. Do not run other fixture journeys concurrently on the
|
|
23
|
+
same serial. A physical device must be selected explicitly and reported separately.
|
|
24
|
+
|
|
25
|
+
The source owns two named single-thread dispatcher executors, an explicit gate,
|
|
26
|
+
nested async work and a cancellation/finally path. Readiness is posted by work
|
|
27
|
+
queued on the same single-thread executor behind the coroutine, so the runner
|
|
28
|
+
releases or cancels only after the gate wait has yielded. UI controls release the
|
|
29
|
+
gate only after debugger resume. Compiler line/variable tables are captured for generated
|
|
30
|
+
continuation classes as well as ordinary methods. The assessment compares existing
|
|
31
|
+
MCP stops, thread stacks and shallow locals; none represents a reconstructed
|
|
32
|
+
logical coroutine stack. A bounded step attempt is recorded as an observation,
|
|
33
|
+
not a promise to follow work across suspension. Source breakpoints are tested
|
|
34
|
+
independently as a workaround.
|
|
35
|
+
|
|
36
|
+
The runner checks pause guards, old frame rejection, event-wait cancellation,
|
|
37
|
+
completion/cancellation UI recovery and owned ADB forward cleanup. Finally it
|
|
38
|
+
removes its package and temporary build/signing output. The overall JSON PASS is
|
|
39
|
+
emitted only after cleanup; cleanup failures emit JSON FAIL and reject the run.
|
|
40
|
+
Forward comparisons filter the selected serial and sort entries, so another
|
|
41
|
+
device changing its forwards does not fail this journey. SIGKILL/host loss cannot
|
|
42
|
+
execute finally; inspect outstanding sessions/packages manually after such loss.
|
|
43
|
+
Local output includes synthetic fixture values, source/APK/server/dependency hashes
|
|
44
|
+
and compiler metadata. Redirect stdout/stderr to an intentional local evidence
|
|
45
|
+
file; no data is uploaded automatically.
|
|
46
|
+
|
|
47
|
+
See the [accepted specification](../../../docs/specs/030-android-coroutine-validation/spec.md)
|
|
48
|
+
and [implementation/verification record](../../../docs/specs/030-android-coroutine-validation/plan.md).
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process'
|
|
2
|
+
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
export const coroutineAppId = 'dev.mobiledebugmcp.coroutinefixture'
|
|
7
|
+
export function buildCoroutineFixture() {
|
|
8
|
+
if (!process.env.MCP_COROUTINES_CORE) throw new Error('MCP_COROUTINES_CORE must identify kotlinx-coroutines-core-jvm.jar')
|
|
9
|
+
if (!process.env.MCP_KOTLIN_STDLIB) throw new Error('MCP_KOTLIN_STDLIB must identify the matching Kotlin standard-library jar')
|
|
10
|
+
const source = path.dirname(fileURLToPath(import.meta.url))
|
|
11
|
+
const directory = mkdtempSync(path.join(tmpdir(), 'mcp-coroutine-fixture-'))
|
|
12
|
+
const java = process.env.JAVA_HOME ?? (existsSync('/Applications/Android Studio.app/Contents/jbr/Contents/Home') ? '/Applications/Android Studio.app/Contents/jbr/Contents/Home' : undefined)
|
|
13
|
+
const run = (cmd: string, args: string[]) => execFileSync(cmd, args, { cwd: directory, encoding: 'utf8', stdio: 'pipe', timeout: 120000 })
|
|
14
|
+
const sdk = process.env.ANDROID_HOME ?? path.join(process.env.HOME!, 'Library/Android/sdk')
|
|
15
|
+
const tools = path.join(sdk, 'build-tools/36.0.0')
|
|
16
|
+
const androidJar = path.join(sdk, 'platforms/android-36/android.jar')
|
|
17
|
+
const jdk = (name: string) => java ? path.join(java, 'bin', name) : name
|
|
18
|
+
try {
|
|
19
|
+
mkdirSync(path.join(directory, 'classes'))
|
|
20
|
+
writeFileSync(path.join(directory, 'AndroidManifest.xml'), `<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="${coroutineAppId}" android:versionCode="1" android:versionName="1.0"><uses-sdk android:minSdkVersion="26" android:targetSdkVersion="36"/><application android:debuggable="true" android:label="Debugger fixture" android:theme="@android:style/Theme.Material.Light.NoActionBar"><activity android:name=".MainActivity" android:exported="true"><intent-filter><action android:name="android.intent.action.MAIN"/><category android:name="android.intent.category.LAUNCHER"/></intent-filter></activity></application></manifest>`)
|
|
21
|
+
run(process.env.MCP_KOTLINC ?? 'kotlinc', ['-no-stdlib', '-no-reflect', '-jvm-target', '1.8', '-classpath', [androidJar, process.env.MCP_KOTLIN_STDLIB!, process.env.MCP_COROUTINES_CORE!].join(path.delimiter), '-d', 'classes', path.join(source, 'MainActivity.kt')])
|
|
22
|
+
run(jdk('jar'), ['cf', 'classes.jar', '-C', 'classes', '.'])
|
|
23
|
+
run(path.join(tools, 'd8'), ['--min-api', '26', '--lib', androidJar, '--output', directory, 'classes.jar', process.env.MCP_KOTLIN_STDLIB!, process.env.MCP_COROUTINES_CORE!])
|
|
24
|
+
run(path.join(tools, 'aapt2'), ['link', '-I', androidJar, '--manifest', 'AndroidManifest.xml', '-o', 'unsigned.apk'])
|
|
25
|
+
run('zip', ['-q', 'unsigned.apk', 'classes.dex'])
|
|
26
|
+
run(path.join(tools, 'zipalign'), ['-f', '4', 'unsigned.apk', 'aligned.apk'])
|
|
27
|
+
run(jdk('keytool'), ['-genkeypair', '-keystore', 'fixture.jks', '-storepass', 'android', '-keypass', 'android', '-alias', 'fixture', '-dname', 'CN=Readiness Fixture', '-keyalg', 'RSA', '-validity', '365'])
|
|
28
|
+
const artifact = path.join(directory, 'Debugger.apk')
|
|
29
|
+
run(path.join(tools, 'apksigner'), ['sign', '--ks', 'fixture.jks', '--ks-pass', 'pass:android', '--out', artifact, 'aligned.apk'])
|
|
30
|
+
return { artifact, directory }
|
|
31
|
+
} catch (error) {
|
|
32
|
+
rmSync(directory, { recursive: true, force: true })
|
|
33
|
+
throw error
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -23,8 +23,29 @@ public class MainActivity extends Activity {
|
|
|
23
23
|
try { Class.forName("dev.mobiledebugmcp.debugfixture.NoLocals").getDeclaredMethod("work").invoke(null); }
|
|
24
24
|
catch (Exception e) { status.setText("Metadata failed"); }
|
|
25
25
|
}); layout.addView(noLocals);
|
|
26
|
+
addLevelControl(layout);
|
|
26
27
|
setContentView(layout);
|
|
27
28
|
}
|
|
29
|
+
void addLevelControl(LinearLayout layout) {
|
|
30
|
+
TextView level = new TextView(this);
|
|
31
|
+
level.setGravity(android.view.Gravity.CENTER);
|
|
32
|
+
level.setText("25 %"); layout.addView(level);
|
|
33
|
+
android.widget.SeekBar slider = new android.widget.SeekBar(this);
|
|
34
|
+
slider.setMax(100); slider.setProgress(25);
|
|
35
|
+
slider.setContentDescription("Fixture level");
|
|
36
|
+
slider.setOnSeekBarChangeListener(new android.widget.SeekBar.OnSeekBarChangeListener() {
|
|
37
|
+
public void onProgressChanged(android.widget.SeekBar view, int progress, boolean fromUser) { level.setText(progress + " %"); }
|
|
38
|
+
public void onStartTrackingTouch(android.widget.SeekBar view) {}
|
|
39
|
+
public void onStopTrackingTouch(android.widget.SeekBar view) {}
|
|
40
|
+
});
|
|
41
|
+
layout.addView(slider);
|
|
42
|
+
LinearLayout range = new LinearLayout(this);
|
|
43
|
+
TextView minimum = new TextView(this); minimum.setText("0 %");
|
|
44
|
+
TextView maximum = new TextView(this); maximum.setText("100 %"); maximum.setGravity(android.view.Gravity.RIGHT);
|
|
45
|
+
range.addView(minimum, new LinearLayout.LayoutParams(0, 80, 1));
|
|
46
|
+
range.addView(maximum, new LinearLayout.LayoutParams(0, 80, 1));
|
|
47
|
+
layout.addView(range);
|
|
48
|
+
}
|
|
28
49
|
void calculate(int seed) {
|
|
29
50
|
int known = seed + 35;
|
|
30
51
|
String longText = new String(new char[300]).replace('\0', 'x');
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
package dev.mobiledebugmcp.debugfixture
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.os.Bundle
|
|
5
|
+
import android.widget.Button
|
|
6
|
+
import android.widget.LinearLayout
|
|
7
|
+
import android.widget.TextView
|
|
8
|
+
|
|
9
|
+
class MainActivity : Activity() {
|
|
10
|
+
private lateinit var status: TextView
|
|
11
|
+
|
|
12
|
+
override fun onCreate(saved: Bundle?) {
|
|
13
|
+
super.onCreate(saved)
|
|
14
|
+
val layout = LinearLayout(this)
|
|
15
|
+
layout.orientation = LinearLayout.VERTICAL
|
|
16
|
+
layout.setPadding(0, 160, 0, 0)
|
|
17
|
+
status = TextView(this)
|
|
18
|
+
status.text = "Kotlin ready"
|
|
19
|
+
layout.addView(status)
|
|
20
|
+
val button = Button(this)
|
|
21
|
+
button.text = "Kotlin calculate"
|
|
22
|
+
button.setOnClickListener { calculate(7) }
|
|
23
|
+
layout.addView(button)
|
|
24
|
+
setContentView(layout)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private fun calculate(seed: Int) {
|
|
28
|
+
val known = seed + 35 // BREAK_KOTLIN_BEFORE
|
|
29
|
+
val answer = known + 1 // BREAK_KOTLIN_KNOWN
|
|
30
|
+
status.text = "Kotlin result $answer" // BREAK_KOTLIN_RESULT
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Isolated Android debugger fixture
|
|
2
2
|
|
|
3
|
+
[Contributing](../../../CONTRIBUTING.md) · [Documentation](../../../docs/README.md)
|
|
4
|
+
|
|
3
5
|
Package `dev.mobiledebugmcp.debugfixture`, built with `android:debuggable=true`
|
|
4
6
|
and Java source/line/local metadata. Calculate exposes `known=42`, an increment
|
|
5
7
|
call for into/over/out stepping, and a visible `Result 43` after resume. Late class
|
|
@@ -11,3 +13,53 @@ Build helper uses SDK platform/build-tools 36, the existing direct javac/D8/aapt
|
|
|
11
13
|
pattern, ephemeral signing material and temporary output. It does not modify other
|
|
12
14
|
apps or display settings. The automated journey installs/removes only this fixture
|
|
13
15
|
and checks session-owned ADB forwards. See [debugger tools](../../../docs/tools/debugger.md).
|
|
16
|
+
|
|
17
|
+
## Kotlin locals validation
|
|
18
|
+
|
|
19
|
+
The opt-in Kotlin variant compiles `MainActivity.kt` instead of the Java sources
|
|
20
|
+
into the same disposable package. Do not run the variants concurrently on one
|
|
21
|
+
device. It requires a Kotlin JVM compiler (`MCP_KOTLINC`, or `kotlinc` on PATH),
|
|
22
|
+
the matching standard-library jar (`MCP_KOTLIN_STDLIB`), and a compatible JDK.
|
|
23
|
+
Kotlin 2.0.21 with JDK 17 was used for the recorded validation. The fixture does
|
|
24
|
+
not add Gradle, download a compiler, or change the server's dependencies.
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
npm run build
|
|
28
|
+
export JAVA_HOME=/path/to/jdk-17
|
|
29
|
+
export MCP_KOTLINC=/path/to/kotlin/bin/kotlinc
|
|
30
|
+
export MCP_KOTLIN_STDLIB=/path/to/kotlin/lib/kotlin-stdlib.jar
|
|
31
|
+
MCP_KOTLIN_DEVICE=emulator-5554 npx tsx test/device/automated/debugger/kotlin.android.test.ts
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The compiler retains its default line/local metadata; D8 runs without shrinking
|
|
35
|
+
or obfuscation. The runner checks the class-file local-variable scope tables,
|
|
36
|
+
then reads `seed=7` before `known` is initialized, `known=42` before `answer` is
|
|
37
|
+
initialized, and `answer=43` afterward through MCP. Old references must fail as
|
|
38
|
+
stale. Resume/detach is followed by a fresh hierarchy assertion for
|
|
39
|
+
`Kotlin result 43`; exact owned forwards, package and temporary files are cleaned
|
|
40
|
+
up. Build failures also remove their temporary directory.
|
|
41
|
+
|
|
42
|
+
`npm run test:device` includes this journey only when `MCP_KOTLIN_DEVICE` is set;
|
|
43
|
+
otherwise it reports **UNRUN** explicitly. The existing Java journey separately
|
|
44
|
+
covers absent variable tables. A local outside its compiler scope and missing
|
|
45
|
+
metadata are different conditions; an empty ART table alone proves neither.
|
|
46
|
+
See the [dated validation record](../../../docs/specs/029-kotlin-locals-validation/report.md).
|
|
47
|
+
|
|
48
|
+
## Interaction regression journey
|
|
49
|
+
|
|
50
|
+
The same isolated fixture is also used by the [interaction device check](../../device/automated/interact/interaction.android.test.ts)
|
|
51
|
+
for clickable waits, stored handles, selector taps, visibility and enabled-state
|
|
52
|
+
assertions. Run it after building the server:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
MCP_INTERACTION_DEVICE=emulator-5554 npx tsx test/device/automated/interact/interaction.android.test.ts
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
It also runs in `npm run test:device` when `MCP_INTERACTION_DEVICE` or
|
|
59
|
+
`MCP_DEBUGGER_DEVICE` selects a device. Fixture journeys run sequentially and
|
|
60
|
+
remove their own package and temporary build outputs. This check does not attach
|
|
61
|
+
a debugger or modify another app's data.
|
|
62
|
+
|
|
63
|
+
The interaction journey also exercises a labelled native SeekBar. It adjusts
|
|
64
|
+
from 25 toward 75, checks convergence within tolerance, and confirms the displayed
|
|
65
|
+
value with a fresh hierarchy assertion. The same fixture cleanup applies.
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process'
|
|
2
|
-
import { existsSync, mkdirSync, mkdtempSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
3
3
|
import { tmpdir } from 'node:os'
|
|
4
4
|
import path from 'node:path'
|
|
5
5
|
import { fileURLToPath } from 'node:url'
|
|
6
6
|
export const debuggerAppId = 'dev.mobiledebugmcp.debugfixture'
|
|
7
|
-
export function buildDebuggerFixture() {
|
|
7
|
+
export function buildDebuggerFixture(kotlin = false) {
|
|
8
|
+
if (kotlin && !process.env.MCP_KOTLIN_STDLIB) throw new Error('MCP_KOTLIN_STDLIB must identify the matching Kotlin standard-library jar')
|
|
8
9
|
const source = path.dirname(fileURLToPath(import.meta.url))
|
|
9
10
|
const directory = mkdtempSync(path.join(tmpdir(), 'mcp-debugger-fixture-'))
|
|
10
11
|
const java = process.env.JAVA_HOME ?? (existsSync('/Applications/Android Studio.app/Contents/jbr/Contents/Home') ? '/Applications/Android Studio.app/Contents/jbr/Contents/Home' : undefined)
|
|
@@ -13,17 +14,26 @@ export function buildDebuggerFixture() {
|
|
|
13
14
|
const tools = path.join(sdk, 'build-tools/36.0.0')
|
|
14
15
|
const androidJar = path.join(sdk, 'platforms/android-36/android.jar')
|
|
15
16
|
const jdk = (name: string) => java ? path.join(java, 'bin', name) : name
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
17
|
+
try {
|
|
18
|
+
mkdirSync(path.join(directory, 'classes'))
|
|
19
|
+
writeFileSync(path.join(directory, 'AndroidManifest.xml'), `<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="${debuggerAppId}" android:versionCode="1" android:versionName="1.0"><uses-sdk android:minSdkVersion="26" android:targetSdkVersion="36"/><application android:debuggable="true" android:label="Debugger fixture" android:theme="@android:style/Theme.Material.Light.NoActionBar"><activity android:name=".MainActivity" android:exported="true"><intent-filter><action android:name="android.intent.action.MAIN"/><category android:name="android.intent.category.LAUNCHER"/></intent-filter></activity></application></manifest>`)
|
|
20
|
+
if (kotlin) {
|
|
21
|
+
run(process.env.MCP_KOTLINC ?? 'kotlinc', ['-no-stdlib', '-no-reflect', '-jvm-target', '1.8', '-classpath', [androidJar, process.env.MCP_KOTLIN_STDLIB!].join(path.delimiter), '-d', 'classes', path.join(source, 'MainActivity.kt')])
|
|
22
|
+
} else {
|
|
23
|
+
run(jdk('javac'), ['-g', '--release', '8', '-classpath', androidJar, '-d', 'classes', path.join(source, 'MainActivity.java')])
|
|
24
|
+
run(jdk('javac'), ['-g:source,lines', '--release', '8', '-classpath', androidJar, '-d', 'classes', path.join(source, 'NoLocals.java')])
|
|
25
|
+
}
|
|
26
|
+
run(jdk('jar'), ['cf', 'classes.jar', '-C', 'classes', '.'])
|
|
27
|
+
run(path.join(tools, 'd8'), ['--lib', androidJar, '--output', directory, 'classes.jar', ...(kotlin ? [process.env.MCP_KOTLIN_STDLIB!] : [])])
|
|
28
|
+
run(path.join(tools, 'aapt2'), ['link', '-I', androidJar, '--manifest', 'AndroidManifest.xml', '-o', 'unsigned.apk'])
|
|
29
|
+
run('zip', ['-q', 'unsigned.apk', 'classes.dex'])
|
|
30
|
+
run(path.join(tools, 'zipalign'), ['-f', '4', 'unsigned.apk', 'aligned.apk'])
|
|
31
|
+
run(jdk('keytool'), ['-genkeypair', '-keystore', 'fixture.jks', '-storepass', 'android', '-keypass', 'android', '-alias', 'fixture', '-dname', 'CN=Readiness Fixture', '-keyalg', 'RSA', '-validity', '365'])
|
|
32
|
+
const artifact = path.join(directory, 'Debugger.apk')
|
|
33
|
+
run(path.join(tools, 'apksigner'), ['sign', '--ks', 'fixture.jks', '--ks-pass', 'pass:android', '--out', artifact, 'aligned.apk'])
|
|
34
|
+
return { artifact, directory }
|
|
35
|
+
} catch (error) {
|
|
36
|
+
rmSync(directory, { recursive: true, force: true })
|
|
37
|
+
throw error
|
|
38
|
+
}
|
|
29
39
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
@inline(never) func increment(_ value: Int) -> Int {
|
|
4
|
+
let adjusted = value + 1 // STEP_INCREMENT
|
|
5
|
+
return adjusted // STEP_RETURN
|
|
6
|
+
}
|
|
7
|
+
@inline(never) func helper(_ value: Int) -> Int {
|
|
8
|
+
let result = increment(value) // STEP_HELPER
|
|
9
|
+
return result // HELPER_RETURN
|
|
10
|
+
}
|
|
11
|
+
actor AsyncWork {
|
|
12
|
+
func child(_ input: Int) async -> Int {
|
|
13
|
+
try? await Task.sleep(nanoseconds: 300_000_000)
|
|
14
|
+
let childValue = input + 1
|
|
15
|
+
return childValue // CHILD_RETURN
|
|
16
|
+
}
|
|
17
|
+
@inline(never) func calculate(_ seed: Int) async -> Int {
|
|
18
|
+
let known = seed
|
|
19
|
+
async let childResult = child(known)
|
|
20
|
+
let after = await childResult // BEFORE_AWAIT
|
|
21
|
+
let answer = helper(after + known - seed) // AFTER_AWAIT
|
|
22
|
+
return answer // ASYNC_RETURN
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
class Controller: UIViewController {
|
|
26
|
+
let label = UILabel()
|
|
27
|
+
var task: Task<Void, Never>?
|
|
28
|
+
override func viewDidLoad() {
|
|
29
|
+
super.viewDidLoad()
|
|
30
|
+
view.backgroundColor = .white
|
|
31
|
+
label.text = "Debugger ready"
|
|
32
|
+
label.accessibilityIdentifier = "debugger-result"
|
|
33
|
+
let stack = UIStackView()
|
|
34
|
+
stack.axis = .vertical
|
|
35
|
+
stack.spacing = 24
|
|
36
|
+
stack.addArrangedSubview(label)
|
|
37
|
+
for (title, action) in [("Calculate", #selector(calculate)), ("Run async", #selector(runAsync)), ("Start cancellation", #selector(startCancellation)), ("Cancel task", #selector(cancelTask))] {
|
|
38
|
+
let button = UIButton(type: .system)
|
|
39
|
+
button.setTitle(title, for: .normal)
|
|
40
|
+
button.addTarget(self, action: action, for: .touchUpInside)
|
|
41
|
+
stack.addArrangedSubview(button)
|
|
42
|
+
}
|
|
43
|
+
stack.translatesAutoresizingMaskIntoConstraints = false
|
|
44
|
+
view.addSubview(stack)
|
|
45
|
+
NSLayoutConstraint.activate([stack.centerXAnchor.constraint(equalTo: view.centerXAnchor), stack.centerYAnchor.constraint(equalTo: view.centerYAnchor)])
|
|
46
|
+
}
|
|
47
|
+
@objc func calculate() {
|
|
48
|
+
let known = 41
|
|
49
|
+
let result = helper(known) // BREAK_CALCULATE
|
|
50
|
+
label.text = "Debugger result \(result)" // CALCULATE_DONE
|
|
51
|
+
}
|
|
52
|
+
@objc func runAsync() {
|
|
53
|
+
task = Task { @MainActor in
|
|
54
|
+
let result = await AsyncWork().calculate(42)
|
|
55
|
+
label.text = "Async result \(result)"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
@objc func startCancellation() {
|
|
59
|
+
label.text = "Task running"
|
|
60
|
+
task = Task { @MainActor in
|
|
61
|
+
do {
|
|
62
|
+
try await Task.sleep(nanoseconds: 60_000_000_000)
|
|
63
|
+
label.text = "Unexpected completion"
|
|
64
|
+
} catch {
|
|
65
|
+
let cancelled = Task.isCancelled
|
|
66
|
+
label.text = cancelled ? "Task cancelled" : "Unexpected error" // CANCELLED
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
@objc func cancelTask() { task?.cancel() }
|
|
71
|
+
}
|
|
72
|
+
@main class App: UIResponder, UIApplicationDelegate {
|
|
73
|
+
var window: UIWindow?
|
|
74
|
+
func application(_ application: UIApplication, didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
|
75
|
+
window = UIWindow(frame: UIScreen.main.bounds)
|
|
76
|
+
window?.rootViewController = Controller()
|
|
77
|
+
window?.makeKeyAndVisible()
|
|
78
|
+
// Test-only control channel; no HID dependency or debugger evaluation.
|
|
79
|
+
Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { [weak self] _ in
|
|
80
|
+
let commandFile = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("debugger-command")
|
|
81
|
+
guard let command = try? String(contentsOf: commandFile, encoding: .utf8),
|
|
82
|
+
let controller = self?.window?.rootViewController as? Controller else { return }
|
|
83
|
+
try? FileManager.default.removeItem(at: commandFile)
|
|
84
|
+
switch command {
|
|
85
|
+
case "exit": exit(0)
|
|
86
|
+
case "calculate": controller.calculate()
|
|
87
|
+
case "async": controller.runAsync()
|
|
88
|
+
case "start-cancellation": controller.startCancellation()
|
|
89
|
+
case "cancel": controller.cancelTask()
|
|
90
|
+
default: break
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return true
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# iOS debugger fixture
|
|
2
|
+
|
|
3
|
+
Disposable UIKit app for simulator-only LLDB MCP checks. Builds with Xcode's
|
|
4
|
+
`swiftc -g -Onone`; no external package or user app is used. Select an explicit
|
|
5
|
+
booted simulator:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm run build
|
|
9
|
+
MCP_IOS_DEBUGGER_DEVICE=<UDID> node --import tsx test/device/automated/debugger/runtime.ios.test.ts
|
|
10
|
+
# Include the real 60-second pause expiry:
|
|
11
|
+
MCP_IOS_DEBUGGER_DEVICE=<UDID> MCP_IOS_DEBUGGER_EXTENDED=1 node --import tsx test/device/automated/debugger/runtime.ios.test.ts
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Requires macOS, full Xcode with Swift-enabled LLDB/Python, and working idb UI-tree
|
|
15
|
+
reads. The test installs/removes only `dev.mobiledebug.iosfixture`, and cleans its
|
|
16
|
+
private temporary build/module-cache directory. It does not boot/shut down devices.
|
|
17
|
+
The app's fixture-only `Documents/debugger-command` file invokes the same actions
|
|
18
|
+
as its buttons through a main-loop timer. The smoke runner writes only that file
|
|
19
|
+
because the installed idb HID backend expects SimulatorKit at a path absent in
|
|
20
|
+
this Xcode. This is a fixture trigger, not evidence that MCP tap delivery
|
|
21
|
+
succeeded. Paused MCP taps must be rejected before delivery. Completion and
|
|
22
|
+
cancellation use fresh MCP UI-tree assertions.
|
|
23
|
+
|
|
24
|
+
Async work uses a suspended child operation in an actor, known values on either
|
|
25
|
+
side of await, a main-actor UI update and a separate cancellation task. Boundary
|
|
26
|
+
stepping is recorded independently from source-breakpoint coverage. Stop-scoped
|
|
27
|
+
thread/frame IDs do not establish logical task identity.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process'
|
|
2
|
+
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs'
|
|
3
|
+
import os from 'node:os'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
export const iosDebuggerAppId = 'dev.mobiledebug.iosfixture'
|
|
7
|
+
export const iosDebuggerSource = fileURLToPath(new URL('./Main.swift', import.meta.url))
|
|
8
|
+
export function buildIOSDebuggerFixture() {
|
|
9
|
+
const directory = mkdtempSync(path.join(os.tmpdir(), 'mobile-debug-ios-fixture-'))
|
|
10
|
+
const app = path.join(directory, 'DebuggerFixture.app')
|
|
11
|
+
try {
|
|
12
|
+
execFileSync('/bin/mkdir', ['-p', app])
|
|
13
|
+
const sdk = execFileSync('xcrun', ['--sdk', 'iphonesimulator', '--show-sdk-path'], { encoding: 'utf8' }).trim()
|
|
14
|
+
execFileSync('xcrun', ['swiftc', '-g', '-module-cache-path', path.join(directory, 'modules'), '-Onone', '-parse-as-library', '-sdk', sdk, '-target', `${os.arch() === 'arm64' ? 'arm64' : 'x86_64'}-apple-ios18.0-simulator`, '-module-name', 'DebuggerFixture', iosDebuggerSource, '-o', path.join(app, 'DebuggerFixture')], { timeout: 120000, stdio: 'pipe' })
|
|
15
|
+
writeFileSync(path.join(app, 'Info.plist'), `<?xml version="1.0"?><plist version="1.0"><dict><key>CFBundleIdentifier</key><string>${iosDebuggerAppId}</string><key>CFBundleExecutable</key><string>DebuggerFixture</string><key>CFBundleName</key><string>DebuggerFixture</string><key>CFBundlePackageType</key><string>APPL</string><key>CFBundleVersion</key><string>1</string><key>CFBundleShortVersionString</key><string>1.0</string><key>LSRequiresIPhoneOS</key><true/><key>UILaunchScreen</key><dict/></dict></plist>`)
|
|
16
|
+
return { app, directory }
|
|
17
|
+
} catch (error) {
|
|
18
|
+
rmSync(directory, { recursive: true, force: true })
|
|
19
|
+
throw error
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Interaction latency fixture
|
|
2
2
|
|
|
3
|
+
[Contributing](../../../CONTRIBUTING.md) · [Documentation](../../../docs/README.md)
|
|
4
|
+
|
|
3
5
|
An offline native Android fixture, version 1.0. It requires Android SDK platform 36,
|
|
4
6
|
build-tools 36.0.0 and JDK 21. The harness builds and signs it using those installed
|
|
5
7
|
tools; no Gradle dependency download is needed.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Readiness fixture
|
|
2
2
|
|
|
3
|
+
[Contributing](../../../CONTRIBUTING.md) · [Documentation](../../../docs/README.md)
|
|
4
|
+
|
|
3
5
|
Offline native Android/iOS simulator apps used only for explicit readiness
|
|
4
6
|
verification. Ready stays visible while Show loader starts a 12-second loader;
|
|
5
7
|
completion hides the loader and changes its status to Done. No permissions,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { promises as fs } from 'node:fs'
|
|
3
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
|
4
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
|
|
5
|
+
|
|
6
|
+
export async function verifyIosTraceShutdown(target: Record<string,unknown>, env: Record<string,string> = {}) {
|
|
7
|
+
const client=new Client({name:'ios-trace-shutdown',version:'1.0.0'})
|
|
8
|
+
const transport=new StdioClientTransport({command:process.execPath,args:['dist/server.js'],stderr:'pipe',env})
|
|
9
|
+
let diagnostics=''
|
|
10
|
+
transport.stderr?.on('data',chunk=>{diagnostics=(diagnostics+String(chunk)).slice(-4096)})
|
|
11
|
+
try {
|
|
12
|
+
await client.connect(transport)
|
|
13
|
+
const capability=await client.callTool({name:'get_system_trace_capabilities',arguments:target},undefined,{timeout:120000})
|
|
14
|
+
const capabilities=JSON.parse((capability.content as any[]).find(c=>c.type==='text').text)
|
|
15
|
+
assert.equal(capabilities.supported,true,JSON.stringify(capabilities))
|
|
16
|
+
const response=await client.callTool({name:'start_system_trace',arguments:{...target,durationMs:30000}},undefined,{timeout:120000})
|
|
17
|
+
const result=JSON.parse((response.content as any[]).find(c=>c.type==='text').text)
|
|
18
|
+
assert.equal(result.state,'recording',JSON.stringify(result))
|
|
19
|
+
const directory=result.cleanup.remaining_resources[0]
|
|
20
|
+
assert.ok(transport.pid)
|
|
21
|
+
process.kill(transport.pid,'SIGTERM')
|
|
22
|
+
let removed=false
|
|
23
|
+
for(let i=0;i<24;i++){
|
|
24
|
+
try {await fs.stat(directory)} catch(error) {if((error as NodeJS.ErrnoException).code==='ENOENT'){removed=true;break}throw error}
|
|
25
|
+
await new Promise(resolve=>setTimeout(resolve,500))
|
|
26
|
+
}
|
|
27
|
+
assert.equal(removed,true,`Server shutdown must remove its owned partial trace directory; ${diagnostics}`)
|
|
28
|
+
} finally {await client.close()}
|
|
29
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { deviceForwards, reportAfterCleanup } from '../../device/automated/debugger/coroutine-lifecycle.js'
|
|
3
|
+
|
|
4
|
+
const baseline = deviceForwards('emulator-5554 tcp:2 jdwp:20\nemulator-5554 tcp:1 jdwp:10\nother-device tcp:3 jdwp:30', 'emulator-5554')
|
|
5
|
+
assert.deepEqual(deviceForwards('other-device tcp:4 jdwp:40\nemulator-5554 tcp:1 jdwp:10\nemulator-5554 tcp:2 jdwp:20\n', 'emulator-5554'), baseline)
|
|
6
|
+
assert.notDeepEqual(deviceForwards('emulator-5554 tcp:1 jdwp:10', 'emulator-5554'), baseline)
|
|
7
|
+
assert.deepEqual(deviceForwards('emulator-55540 tcp:1 jdwp:10', 'emulator-5554'), [])
|
|
8
|
+
|
|
9
|
+
const order: string[] = []
|
|
10
|
+
await reportAfterCleanup(async () => { order.push('work'); return 42 }, async () => { order.push('cleanup') }, (status, result) => {
|
|
11
|
+
order.push(status)
|
|
12
|
+
assert.equal(result, 42)
|
|
13
|
+
})
|
|
14
|
+
assert.deepEqual(order, ['work', 'cleanup', 'PASS'])
|
|
15
|
+
|
|
16
|
+
// Reproduce the review bug: cleanup rejects after work succeeds or fails.
|
|
17
|
+
// The runner must reject and emit only FAIL in both cases.
|
|
18
|
+
for (const workFails of [false, true]) {
|
|
19
|
+
const records: string[] = []
|
|
20
|
+
const cleanupError = new Error('cleanup')
|
|
21
|
+
const journeyError = new Error('journey')
|
|
22
|
+
await assert.rejects(reportAfterCleanup(async () => {
|
|
23
|
+
if (workFails) throw journeyError
|
|
24
|
+
return 42
|
|
25
|
+
}, async () => { throw cleanupError }, (status, evidence, error) => {
|
|
26
|
+
records.push(status)
|
|
27
|
+
assert.equal(evidence, undefined)
|
|
28
|
+
if (workFails) {
|
|
29
|
+
assert.ok(error instanceof AggregateError)
|
|
30
|
+
assert.deepEqual(error.errors, [journeyError, cleanupError])
|
|
31
|
+
} else assert.equal(error, cleanupError)
|
|
32
|
+
}), error => workFails ? error instanceof AggregateError : error === cleanupError)
|
|
33
|
+
assert.deepEqual(records, ['FAIL'])
|
|
34
|
+
}
|
|
35
|
+
let cleaned = false
|
|
36
|
+
const workError = new Error('journey')
|
|
37
|
+
await assert.rejects(reportAfterCleanup(async () => { throw workError }, async () => { cleaned = true }, (status, evidence, error) => {
|
|
38
|
+
assert.equal(cleaned, true)
|
|
39
|
+
assert.equal(status, 'FAIL')
|
|
40
|
+
assert.equal(evidence, undefined)
|
|
41
|
+
assert.equal(error, workError)
|
|
42
|
+
}), error => error === workError)
|
|
43
|
+
console.log('PASS coroutine assessment: cleanup precedes PASS; work/cleanup failures report FAIL; forwards scoped and sorted')
|