diff --git a/apps/server/src/device/DeviceHubOperations.ts b/apps/server/src/device/DeviceHubOperations.ts index 9371484f4c93f..910c063da73da 100644 --- a/apps/server/src/device/DeviceHubOperations.ts +++ b/apps/server/src/device/DeviceHubOperations.ts @@ -39,6 +39,21 @@ const HubActionResult = Schema.Struct({ ), }); +// Hub diagnostics can carry a whole stderr dump. +const boundDiagnostics = (text: string) => + text.length > 2_000 ? `${text.slice(0, 1_999)}…` : text; + +// The hub's `error` is a util.inspect of Node's exec error; the stack and the +// inspected fields follow the first frame, so keep the lines before it. +const errorHead = (error: string) => { + const lines = error.split(/\r?\n/); + const firstFrame = lines.findIndex((line) => /^\s+at /.test(line)); + return (firstFrame === -1 ? lines : lines.slice(0, firstFrame)) + .map((line) => line.trim()) + .filter(Boolean) + .join(" "); +}; + const vendorPrefix = (platform: DevicePlatform) => platform === "ios" ? "/vendor/serve-sim" : "/vendor/serve-emu"; @@ -109,7 +124,11 @@ export const makeDeviceHubOperations = (options: { /(?:^Error:\s*ENOENT:|\bcode:\s*['"]ENOENT['"])/.test(error.error ?? "") ), ); - const details = warnings?.map((error) => error.message) ?? []; + const details = + warnings?.map((error) => { + const head = errorHead(error.error ?? ""); + return boundDiagnostics(head ? `${error.message} ${head}` : error.message); + }) ?? []; let androidDiscoveryIncomplete = false; if (androidAvailable) { const avds = yield* run("emulator", ["-list-avds"]); @@ -167,12 +186,7 @@ export const makeDeviceHubOperations = (options: { : /timed? out|timeout/i.test(diagnostics) ? "timeout" : "launch_failed", - ...(diagnostics - ? { - diagnostics: - diagnostics.length > 2_000 ? `${diagnostics.slice(0, 1_999)}…` : diagnostics, - } - : {}), + ...(diagnostics ? { diagnostics: boundDiagnostics(diagnostics) } : {}), cause: result, }); } diff --git a/apps/server/src/device/DeviceService.test.ts b/apps/server/src/device/DeviceService.test.ts index c13179d9d4932..4031cdcf25e6a 100644 --- a/apps/server/src/device/DeviceService.test.ts +++ b/apps/server/src/device/DeviceService.test.ts @@ -522,6 +522,57 @@ it.effect("hides Android discovery failures when the host has no Android SDK", ( }).pipe(Effect.scoped), ); +// The hub reports `error` as a util.inspect of Node's exec error. +const simctlMessage = "[apple-utils] Failed to run `xcrun simctl list devices --json`:"; +const simctlCause = + "An error was encountered processing the command (domain=NSPOSIXErrorDomain, code=60): Unable to connect to CoreSimulatorService [xcrun shim fail mode]"; +const simctlError = `Error: Command failed: xcrun simctl list devices --json +${simctlCause} + + at genericNodeError (node:internal/errors:986:15) + at wrappedFn (node:internal/errors:540:14) + at ChildProcess.exithandler (node:child_process:417:12) + at ChildProcess._handle.onexit (node:internal/child_process:335:5) { + code: 1, + killed: false, + signal: null, + cmd: 'xcrun simctl list devices --json', + stdout: '', + stderr: '${simctlCause}\\n' +}`; +const avdmanager = "/Users/test/Library/Android/sdk/cmdline-tools/latest/bin/avdmanager"; +const avdmanagerError = `Error: spawn ${avdmanager} ENOENT + at ChildProcess._handle.onexit (node:internal/child_process:316:19) + at onErrorNT (node:internal/child_process:524:16) { + errno: -2, + code: 'ENOENT', + syscall: 'spawn ${avdmanager}', + path: '${avdmanager}', + spawnargs: [ 'list', 'avd' ], + cmd: '${avdmanager} list avd', + stdout: '', + stderr: '' +}`; + +it.effect("keeps the cause from the hub's error in discovery warnings, without the stack", () => + Effect.gen(function* () { + const { service, discoveryErrors } = yield* fixture(); + yield* Ref.set(discoveryErrors, [ + { message: simctlMessage, error: simctlError }, + { message: "[android-utils] Failed to run `avdmanager list avd`:", error: avdmanagerError }, + ]); + const state = yield* service.configure({ enabled: true }); + expect(state.hostStatusDetail).toBe( + [ + `${simctlMessage} Error: Command failed: xcrun simctl list devices --json ${simctlCause}`, + `[android-utils] Failed to run \`avdmanager list avd\`: Error: spawn ${avdmanager} ENOENT`, + ].join("\n"), + ); + expect(state.hostStatusDetail).not.toContain(" at "); + expect(state.hostStatusDetail).not.toContain("stderr:"); + }).pipe(Effect.scoped), +); + it.effect.each([ { diagnostic: "Insufficient disk space at /private/user/path",