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
3 changes: 3 additions & 0 deletions src/__tests__/platform-runtime-runtime-hints.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { bindTestCommandHost } from '@agent-device/test-kit';
import { test } from 'vitest';
import assert from 'node:assert/strict';
import { promises as fs } from 'node:fs';
Expand Down Expand Up @@ -418,3 +419,5 @@ test('clearRuntimeHintValues deletes iOS simulator React Native defaults', async
);
});
});

bindTestCommandHost();
54 changes: 39 additions & 15 deletions src/__tests__/provider-device-runtimes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import {
test('daemon composition registers installed provider runtimes and their lazy platform modules', async () => {
const { home, env } = pluginHome();
selectPlugin(home, 'example', 'example', registrationSource('example'));
const composition = await createDaemonProviderRuntimeComposition(env);
const composition = await createDaemonProviderRuntimeComposition({
env: env,
cwd: process.cwd(),
});
const plugin = composition.runtimes.find((runtime) => runtime.provider === 'example');
assert.ok(plugin);
assert.equal(
Expand All @@ -26,7 +29,10 @@ test('daemon composition registers installed provider runtimes and their lazy pl
test('built-in providers register ahead of the store and an unconfigured Limrun still claims its id', async () => {
const { home, env } = pluginHome();
selectPlugin(home, 'example', 'example', registrationSource('example'));
const composition = await createDaemonProviderRuntimeComposition(env);
const composition = await createDaemonProviderRuntimeComposition({
env: env,
cwd: process.cwd(),
});

assert.deepEqual(
composition.runtimes.map((runtime) => runtime.provider),
Expand All @@ -47,10 +53,13 @@ test('daemon composition records Limrun as failed for a partial instance set and
const { home, env } = pluginHome();
selectPlugin(home, 'example', 'example', registrationSource('example'));
const composition = await createDaemonProviderRuntimeComposition({
...env,
LIMRUN_API_KEY: 'lim_test_key',
LIM_ANDROID_INSTANCE_URL: 'https://region.limrun.example/v1/android_x/api',
LIM_ANDROID_INSTANCE_TOKEN: 'android-instance-token',
env: {
...env,
LIMRUN_API_KEY: 'lim_test_key',
LIM_ANDROID_INSTANCE_URL: 'https://region.limrun.example/v1/android_x/api',
LIM_ANDROID_INSTANCE_TOKEN: 'android-instance-token',
},
cwd: process.cwd(),
});

assert.deepEqual(
Expand All @@ -68,7 +77,10 @@ test('a plugin claiming a built-in provider ID is recorded as failed and the bui
for (const provider of ['limrun', 'browserstack', 'aws-device-farm']) {
const { home, env } = pluginHome();
selectPlugin(home, 'example', provider, 'throw new Error("must not evaluate");');
const composition = await createDaemonProviderRuntimeComposition(env);
const composition = await createDaemonProviderRuntimeComposition({
env: env,
cwd: process.cwd(),
});
const plugin = composition.inventory.find(({ package: name }) => name === 'example');
assert.equal(plugin?.state, 'failed');
assert.equal(plugin?.error?.reason, 'duplicate_provider');
Expand All @@ -85,8 +97,11 @@ test('Limrun stays unregistered when only the removed API key alias is configure
const { env } = pluginHome();
const { runtimes, platformModules, inventory, shutdown } =
await createDaemonProviderRuntimeComposition({
...env,
LIM_API_KEY: 'lim_test_key',
env: {
...env,
LIM_API_KEY: 'lim_test_key',
},
cwd: process.cwd(),
});

assert.equal(
Expand All @@ -104,8 +119,11 @@ test('Limrun stays unregistered when only the removed API key alias is configure
test('Limrun registers when a Limrun API key is configured', async () => {
const { env } = pluginHome();
const { runtimes, platformModules, shutdown } = await createDaemonProviderRuntimeComposition({
...env,
LIMRUN_API_KEY: 'lim_test_key',
env: {
...env,
LIMRUN_API_KEY: 'lim_test_key',
},
cwd: process.cwd(),
});

const limrun = runtimes.find((runtime) => runtime.provider === 'limrun');
Expand All @@ -118,9 +136,12 @@ test('Limrun registers when a Limrun API key is configured', async () => {
test('Limrun registers for an existing instance without an API key', async () => {
const { env } = pluginHome();
const { runtimes, platformModules, shutdown } = await createDaemonProviderRuntimeComposition({
...env,
LIM_IOS_INSTANCE_URL: 'https://region.limrun.example/v1/ios_x/api',
LIM_IOS_INSTANCE_TOKEN: 'ios-instance-token',
env: {
...env,
LIM_IOS_INSTANCE_URL: 'https://region.limrun.example/v1/ios_x/api',
LIM_IOS_INSTANCE_TOKEN: 'ios-instance-token',
},
cwd: process.cwd(),
});

const limrun = runtimes.find((runtime) => runtime.provider === 'limrun');
Expand Down Expand Up @@ -167,7 +188,10 @@ test('composition shutdown is the only route the daemon uses: it shuts every run
});`,
{ selectionOptions: { log } },
);
const composition = await createDaemonProviderRuntimeComposition(env);
const composition = await createDaemonProviderRuntimeComposition({
env: env,
cwd: process.cwd(),
});
assert.ok(composition.runtimes.some((runtime) => runtime.provider === 'cleaning'));

await composition.shutdown();
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/test-utils/builtin-plugin-inventory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { builtinProviderCandidates } from '../../builtin-provider-plugins.ts';
*/
export async function builtinPluginInventory(): Promise<readonly PluginInventoryEntry[]> {
const resolved = await resolveProviderPlugins(
builtinProviderCandidates({ LIMRUN_API_KEY: 'configured' }),
builtinProviderCandidates({ env: { LIMRUN_API_KEY: 'configured' }, cwd: process.cwd() }),
);
return resolved.inventory();
}
20 changes: 12 additions & 8 deletions src/builtin-provider-plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
type CloudWebDriverProviderDeclaration,
} from '@agent-device/provider-webdriver/providers';
import { LIMRUN_ID } from './builtin-provider-ids.ts';
import type { EnvMap } from '@agent-device/kernel/source-value';
import type { HostEnvironment } from '@agent-device/kernel/source-value';
import {
limrunCredentialDeclaration,
readLimrunCredentials,
Expand All @@ -17,14 +17,14 @@ import {
* load only when a runtime is set up; a candidate that needs configuration claims its id without
* registering until the environment holds it.
*/
export function builtinProviderCandidates(env: EnvMap): PluginCandidate[] {
export function builtinProviderCandidates(host: HostEnvironment): PluginCandidate[] {
return [
...CLOUD_WEBDRIVER_PROVIDER_DECLARATIONS.map((declaration): PluginCandidate => ({
source: 'builtin',
provider: declaration.provider,
load: () => cloudWebDriverPlugin(declaration),
})),
limrunCandidate(env),
limrunCandidate(host),
];
}

Expand Down Expand Up @@ -64,11 +64,11 @@ function cloudWebDriverPlugin(declaration: CloudWebDriverProviderDeclaration): A
};
}

function limrunCandidate(env: EnvMap): PluginCandidate {
function limrunCandidate(host: HostEnvironment): PluginCandidate {
const base = { source: 'builtin', provider: LIMRUN_ID } as const;
let credentials: LimrunCredentials | undefined;
try {
credentials = readLimrunCredentials(env);
credentials = readLimrunCredentials(host.env);
} catch (error) {
return { ...base, load: () => Promise.reject(error) };
}
Expand All @@ -85,9 +85,13 @@ function limrunCandidate(env: EnvMap): PluginCandidate {
import('@agent-device/provider-limrun'),
import('./sdk/limrun-runtime-dependencies.ts'),
]);
return runtime.createLimrunRuntime(limrun, dependencies.createLimrunRuntimeDependencies(), {
includePlatformModule: true,
});
return runtime.createLimrunRuntime(
limrun,
dependencies.createLimrunRuntimeDependencies(host),
{
includePlatformModule: true,
},
);
},
}),
};
Expand Down
2 changes: 1 addition & 1 deletion src/daemon-composition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export const createDaemonRuntimeDeps: ComposeDaemonRuntimeDeps = async (
resolveSessionDir,
});
const hostDiagnostics = createHostDiagnostics(host);
const providerComposition = await createDaemonProviderRuntimeComposition(env);
const providerComposition = await createDaemonProviderRuntimeComposition(host);
// The registry has set up every plugin. Until this function returns, nothing else owns its
// cleanup, so any later failure closes it here; success transfers ownership to the daemon.
try {
Expand Down
4 changes: 3 additions & 1 deletion src/platform-runtime-android-adb-binding.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ vi.mock('@agent-device/host-kit/command', async (importOriginal) => {
});

import { createPlatformRuntimeGateway } from './platform-runtime.ts';
import { createLiveTestHostEnvironment } from '@agent-device/test-kit';
import { bindTestCommandHost, createLiveTestHostEnvironment } from '@agent-device/test-kit';

const sessionArtifacts = {
sessionsDir: '/sessions',
Expand Down Expand Up @@ -102,3 +102,5 @@ test('the composed gateway reads Android app state from inside the platform pack

await gateway.shutdown();
});

bindTestCommandHost();
4 changes: 3 additions & 1 deletion src/platform-runtime-android-adb-host.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
parseAndroidAdbArgv,
runAndroidHostAdb,
} from '@agent-device/platform-android/mechanics';
import { ANDROID_EMULATOR, mkdtempForTestSync } from '@agent-device/test-kit';
import { bindTestCommandHost, ANDROID_EMULATOR, mkdtempForTestSync } from '@agent-device/test-kit';
import './platform-runtime-android-adb-host.ts';

const UNGUARDED_DEVICE_SHELL = {
Expand Down Expand Up @@ -324,3 +324,5 @@ test.skipIf(process.platform === 'win32')(
assert.equal(fs.existsSync(markerPath), true);
},
);

bindTestCommandHost();
11 changes: 6 additions & 5 deletions src/platform-runtime-android-adb-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { assertDeviceShellArgv } from '@agent-device/kernel/device-shell';
import { createHash, randomUUID } from 'node:crypto';
import {
coerceExecResult,
commandHostEnvironment,
execFailureDetails,
runCmd,
runCmdBackground,
Expand All @@ -27,8 +28,6 @@ import {
import os from 'node:os';
import path from 'node:path';

const environment = process.env;

/**
* The adb process boundary: a `shell`/`exec-out` command reaches the device's `sh` verbatim, so the
* command must have been minted by the device-shell funnel. Checked on the command, not the lowered
Expand All @@ -39,7 +38,9 @@ function requireQuotedDeviceShellCommand(command: readonly string[]): void {
}

bindAndroidAdbHost({
environment,
get environment() {
return commandHostEnvironment().env;
},
files: {
access: async (candidate) => await access(candidate),
ensureDirectory: async (directory) => {
Expand Down Expand Up @@ -93,7 +94,7 @@ bindAndroidAdbHost({
},
execAdb: async (invocation, options) => {
requireQuotedDeviceShellCommand(invocation.command);
const lowered = lowerAndroidAdbInvocation(invocation, options, environment);
const lowered = lowerAndroidAdbInvocation(invocation, options, commandHostEnvironment().env);
return await runCmd('adb', lowered.args, {
...lowered.options,
// adb's fork-server is a grandchild: without its own process group a deadline can only
Expand All @@ -103,7 +104,7 @@ bindAndroidAdbHost({
},
spawnAdb: (invocation, options) => {
requireQuotedDeviceShellCommand(invocation.command);
const lowered = lowerAndroidAdbInvocation(invocation, options, environment);
const lowered = lowerAndroidAdbInvocation(invocation, options, commandHostEnvironment().env);
const background = runCmdBackground('adb', lowered.args, {
...lowered.options,
allowFailure: true,
Expand Down
6 changes: 4 additions & 2 deletions src/platform-runtime-app-log-android-transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
AppLogProcessTransport,
} from '@agent-device/contracts/app-log-runtime';
import type { HostCommandResult } from '@agent-device/contracts/platform-runtime-host';
import type { EnvMap } from '@agent-device/kernel/source-value';
import type { DeviceInfo } from '@agent-device/kernel/device';
import type { AndroidAdbProcess } from '@agent-device/platform-android/mechanics';
import {
Expand All @@ -15,20 +16,21 @@ export async function resolveAndroidAppLogProcessTransport(
sessionsDir: string,
device: DeviceInfo,
local: AppLogProcessTransport,
env: EnvMap,
): Promise<AppLogProcessTransport> {
const { resolveScopedAndroidAdbBackgroundTransport } = await loadAndroidMechanics();
const transport = resolveScopedAndroidAdbBackgroundTransport(device);
if (transport.mode === 'local') return local;
if (!transport.spawn) return Object.freeze({ mode: 'transport-composed' });
const spawn = transport.spawn;
const processes = createManagedAppLogProcesses(sessionsDir, {
const processes = createManagedAppLogProcesses(sessionsDir, env, {
launch: async (command, signal) => {
signal?.throwIfAborted();
const adb = providerAdbCommand(command, device.id);
const child = spawn([...adb.args], {
allowFailure: adb.options?.allowFailure,
cwd: adb.options?.cwd,
env: adb.options?.env ? { ...process.env, ...adb.options.env } : undefined,
env: { ...env, ...adb.options?.env },
captureOutput: false,
signal,
});
Expand Down
Loading
Loading