/** * @file * * Stops the emulators this harness started, and **verifies** each stop — the * marker-driven half of emulator ownership (`emulator-marker.ts`, **L56**). * * It lives apart from `transport-factory.ts` so that a process with no business * loading the whole transport stack can still stop an emulator correctly. That * process is the emulator reaper (`emulator-reaper.ts`): it runs detached from * any test runner, and in this repo's own suites it runs straight from source * under Node's type stripping, where a module reading the build-time * `OBSIDIAN_METADATA` global — which the factory's import graph reaches — * cannot load at all. So nothing this module imports may reach it either. */ import type { ChildProcess } from 'node:child_process'; import type { ProcessListEntry } from './emulator-backend.cjs'; /** * Budget for one quick `adb` call — `adb devices`, a console command. */ export declare const ADB_DEVICE_CHECK_TIMEOUT_IN_MILLISECONDS = 5000; export declare const ADB_DUMPSYS_MAX_BUFFER_IN_BYTES = 8388608; export declare const HOST_PROCESS_QUERY_TIMEOUT_IN_MILLISECONDS = 30000; /** * Parameters for {@link EmulatorReclaimer.reclaimLeftoverEmulators}. */ export interface EmulatorReclaimerReclaimLeftoverEmulatorsParams { /** An AVD whose leftover is left alone — the one a preflight is about to adopt instead. */ readonly exceptAvdName?: string | undefined; /** * `'preflight'` spares an emulator another live harness process still owns. * `'end-of-run'` does not: the caller holds the `android` lock with no run in * flight — the run's own global teardown, or the emulator reaper after the * run is gone — so that owner can only be one of the finished run's * processes, which no longer has a turn to stop it. */ readonly scope: 'end-of-run' | 'preflight'; } /** * Parameters for {@link EmulatorReclaimer.stopEmulator}. */ export interface EmulatorReclaimerStopEmulatorParams { /** The AVD name, named in the warning so the leftover is identifiable, and the key of its marker. */ readonly avdName: string; /** * The device the emulator is serving, shut down over its console and polled * to decide whether it actually stopped. `undefined` when the emulator failed * before any device appeared. */ readonly deviceId?: string | undefined; /** * The emulator launcher process this run spawned. `undefined` for a leftover * this run took over, whose launcher belonged to another process. */ readonly emulatorProcess?: ChildProcess | undefined; /** The emulator PIDs this run owns, escalated to when the console and the launcher's tree kill leave one behind. */ readonly ownedEmulatorPids: readonly number[]; } /** * A host command and its arguments. */ export interface HostCommandQuery { /** The executable to run. */ readonly command: string; /** The command's arguments. */ readonly commandArguments: string[]; } /** * Finds, stops and verifies the harness's emulators, reporting through the * caller's log so each caller keeps its own prefix. */ export declare class EmulatorReclaimer { private readonly log; /** * Creates a reclaimer that reports under the caller's own log prefix. * * @param log - Receives every progress and verdict line. */ constructor(log: (message: string) => void); /** * Runs `adb devices` and returns its raw stdout. * * The raw listing is what teardown needs: `getConnectedDeviceIds` keeps only * the `device` state, and an emulator on its way out answers `offline` while * still holding the AVD — see `adb-device-list.ts`. * * @returns The raw `adb devices` output. * @throws If adb could not be run at all. */ getDevicesOutput(): Promise; /** * Lists every process on the host. * * A host always has processes, so a listing that parses to **zero** rows is a * failed query however it exited, and comes back as `undefined` exactly like * one that did not run — never as an empty list a caller could read as "no * emulator is running". * * @returns The host's processes, or `undefined` when the listing failed. */ queryHostProcesses(): Promise; /** * Stops the emulators this harness started that no live run is responsible * for, and drops the markers that no longer describe a running emulator. * * Only ever acts on a **marker-verified** emulator: one whose marker names a * PID that is still a live emulator process. An emulator without a marker — * booted by hand, by CI, or by another tool — is never touched, which keeps * the L46 line: never a `qemu*` sweep. * * Callers must hold the `android` setup lock (L7), which is what makes a * leftover safe to stop: no other Android run can be mid-flight on it. * * @param params - Which AVD to leave alone, and whether this run is ending. */ reclaimLeftoverEmulators(params: EmulatorReclaimerReclaimLeftoverEmulatorsParams): Promise; /** * Stops an emulator this harness owns, and **verifies** it stopped. * * The console shutdown goes first because it is the only path that releases * the AVD's `multiinstance.lock`; a `taskkill` leaves the lock behind, and a * stale lock is what makes the next run fail with `Running multiple emulators * with the same AVD` — a FATAL the emulator writes to its own stdout, where * nobody sees it. * * Works without a launcher handle too: a leftover this run took over has only * its PIDs, which is all the escalation below ever needed. The marker goes * only with a **verified** stop, so an emulator that outlived this attempt * stays convictable by the next one. * * @param params - The emulator process, the device it serves and the PIDs this run owns. */ stopEmulator(params: EmulatorReclaimerStopEmulatorParams): Promise; /** * Decides whether the emulator this run started is really gone. * * Two independent proofs, cheapest first: none of the PIDs this run owns is * alive, and the device no longer appears in `adb devices` **in any state** (a * dying emulator answers `offline` while it still holds the AVD). * * @param params - The device and the PIDs this run owns. * @returns `true` when nothing of this run's emulator is left. */ private checkIsEmulatorGone; /** * Asks the emulator to shut itself down over its console. * * Preferred over killing the process outright because it is the path that * releases the AVD's `multiinstance.lock`. Best-effort: a console that does * not answer is reported and the caller falls through to the kill. * * @param deviceId - The emulator's device ID. */ private killEmulatorConsole; /** * Acts on one emulator marker for {@link reclaimLeftoverEmulators}. * * @param marker - The marker to judge. * @param liveEmulatorPids - The emulator processes currently running on the host. * @param scope - Whether an emulator another live harness process owns is spared. */ private reclaimLeftoverEmulator; /** * Polls until this run's emulator is gone, or the budget elapses. * * @param params - The device, the PIDs this run owns, and the budget. * @returns `true` when the emulator disappeared within the budget. */ private waitForEmulatorStopped; }