Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions apps/server/src/device/SshDeviceHost.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,118 @@ it.effect("preserves installed status after probes and cleans failed agent activ
expect(forwards).toBe(0);
}).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
);

it.effect("lists Android devices without probing the host again once it reports Android", () =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const home = yield* fs.makeTempDirectoryScoped();
const modes: string[] = [];
let androidInstalled = false;
const spawner = ChildProcessSpawner.make((command) =>
Effect.gen(function* () {
if (command._tag !== "StandardCommand") return yield* Effect.die("Unexpected command");
const forwarding = command.args.includes("-L");
let output = "";
if (!forwarding) {
const stdin = command.options.stdin;
const script =
stdin && typeof stdin === "object" && "stream" in stdin && Stream.isStream(stdin.stream)
? yield* stdin.stream.pipe(
Stream.decodeText(),
Stream.runFold(
() => "",
(a, b) => a + b,
),
)
: "";
const mode = /const mode = "([^"]+)"/.exec(script)?.[1] ?? "";
modes.push(mode);
output =
mode === ""
? "Pixel_8\n"
: JSON.stringify({
nodePath: "/node",
platforms: [
{ platform: "ios", available: true },
{ platform: "android", available: androidInstalled },
],
hubPort: 1234,
helpers: { serveSimAxSettings: null, serveSimCli: null },
});
}
return ChildProcessSpawner.makeHandle({
pid: ChildProcessSpawner.ProcessId(123),
stdout: Stream.make(new TextEncoder().encode(output)),
stderr: Stream.empty,
all: Stream.empty,
exitCode: forwarding ? Effect.never : Effect.succeed(ChildProcessSpawner.ExitCode(0)),
isRunning: Effect.succeed(forwarding),
kill: () => Effect.void,
stdin: Sink.drain,
getInputFd: () => Sink.drain,
getOutputFd: () => Stream.empty,
unref: Effect.succeed(Effect.void),
});
}),
);
const host = yield* SshDeviceHost.make({
kind: "ssh",
id: "test",
label: "Test",
target: "test.example",
}).pipe(
Effect.provide(Layer.mergeAll(ServerConfig.layerTest(home, home), Net.layer)),
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
Effect.provideService(
HttpClient.HttpClient,
HttpClient.make((request) =>
Effect.succeed(
HttpClientResponse.fromWeb(
request,
new Response(
request.url.endsWith("/api/devices")
? JSON.stringify({ simulators: [], emulators: [] })
: "ok",
),
),
),
),
),
);
const ready = yield* host.ensureReady(() => Effect.void);
const androidNames = ready.devices.list.pipe(
Effect.map((list) =>
list.devices.filter((device) => device.platform === "android").map((device) => device.name),
),
);

modes.splice(0);

expect(yield* androidNames).toEqual([]);
expect(modes.splice(0)).toEqual(["probe"]);
expect(yield* androidNames).toEqual([]);
expect(modes.splice(0)).toEqual(["probe"]);

// The SDK appears after connecting, so the next listing still probes the host.
androidInstalled = true;
expect(yield* androidNames).toEqual(["Pixel_8"]);
expect(modes.splice(0)).toEqual(["probe", ""]);

// Android is now known to be available, so listing only runs `emulator -list-avds`.
expect(yield* androidNames).toEqual(["Pixel_8"]);
expect(modes.splice(0)).toEqual([""]);

androidInstalled = false;
expect((yield* host.platformAvailability("android")).available).toBe(false);
expect(modes.splice(0)).toEqual(["probe"]);
expect(yield* androidNames).toEqual([]);
expect(modes.splice(0)).toEqual(["probe"]);

androidInstalled = true;
yield* host.inspect;
expect(modes.splice(0)).toEqual(["probe"]);
expect(yield* androidNames).toEqual(["Pixel_8"]);
expect(modes.splice(0)).toEqual([""]);
yield* host.stop;
}).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),
);
9 changes: 7 additions & 2 deletions apps/server/src/device/SshDeviceHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,13 @@ export const make = Effect.fn("SshDeviceHost.make")(function* (
origin: hubOrigin,
http,
run,
androidAvailable: platformAvailability("android").pipe(
Effect.map((availability) => availability.available),
// Keep probing negative reports so an SDK installed after connecting is still found.
androidAvailable: Effect.suspend(() =>
summary.platforms.some((p) => p.platform === "android" && p.available)
? Effect.succeed(true)
: platformAvailability("android").pipe(
Effect.map((availability) => availability.available),
),
),
}),
...(remote.daemonPort !== undefined &&
Expand Down
Loading