Extract getSimulators to IOSBridge

Summary:
Extracts `getSimulator` interrnals to `SimctlBridge`. This allows this functionality to be used independently of things like the the flipper server.

For now this just moves the functionality, but future diffs will build on top of this.

Reviewed By: passy

Differential Revision: D33842986

fbshipit-source-id: bae26a9bd5c21c9813f8a2b10c3b3e3efc1c5929
This commit is contained in:
Lawrence Lomax
2022-01-31 07:23:29 -08:00
committed by Facebook GitHub Bot
parent 959a2a77d7
commit aeb0b5f317
3 changed files with 78 additions and 57 deletions

View File

@@ -9,6 +9,7 @@
import fs from 'fs-extra';
import child_process from 'child_process';
import type {IOSDeviceParams} from 'flipper-common';
import {DeviceType} from 'flipper-common';
import {v1 as uuid} from 'uuid';
import path from 'path';
@@ -21,6 +22,15 @@ export const ERR_NO_IDB_OR_XCODE_AVAILABLE =
export const ERR_PHYSICAL_DEVICE_LOGS_WITHOUT_IDB =
'Cannot provide logs from a physical device without idb.';
// eslint-disable-next-line @typescript-eslint/naming-convention
type iOSSimulatorDevice = {
state: 'Booted' | 'Shutdown' | 'Shutting Down';
availability?: string;
isAvailable?: 'YES' | 'NO' | true | false;
name: string;
udid: string;
};
export interface IOSBridge {
startLogListener: (
udid: string,
@@ -115,12 +125,49 @@ export class SimctlBridge implements IOSBridge {
);
}
async getActiveDevices(bootedOnly: boolean): Promise<Array<IOSDeviceParams>> {
return execFile('xcrun', [
'simctl',
...getDeviceSetPath(),
'list',
'devices',
'--json',
])
.then(({stdout}) => JSON.parse(stdout!.toString()).devices)
.then((simulatorDevices: Array<iOSSimulatorDevice>) => {
const simulators = Object.values(simulatorDevices).flat();
return simulators
.filter(
(simulator) =>
(!bootedOnly || simulator.state === 'Booted') &&
isSimulatorAvailable(simulator),
)
.map((simulator) => {
return {
...simulator,
type: 'emulator',
} as IOSDeviceParams;
});
});
}
async launchSimulator(udid: string): Promise<any> {
await execFile('xcrun', ['simctl', ...getDeviceSetPath(), 'boot', udid]);
await execFile('open', ['-a', 'simulator']);
}
}
function isSimulatorAvailable(simulator: iOSSimulatorDevice): boolean {
// For some users "availability" is set, for others it's "isAvailable"
// It's not clear which key is set, so we are checking both.
// We've also seen isAvailable return "YES" and true, depending on version.
return (
simulator.availability === '(available)' ||
simulator.isAvailable === 'YES' ||
simulator.isAvailable === true
);
}
async function isAvailable(idbPath: string): Promise<boolean> {
if (!idbPath) {
return false;

View File

@@ -18,7 +18,17 @@ import {
setFlipperServerConfig,
} from '../../../FlipperServerConfig';
let fakeSimctlBridge: any;
let hasCalledSimctlActiveDevices = false;
beforeEach(() => {
hasCalledSimctlActiveDevices = false;
fakeSimctlBridge = {
getActiveDevices: async (_bootedOnly: boolean) => {
hasCalledSimctlActiveDevices = true;
return [];
},
};
setFlipperServerConfig(getRenderHostInstance().serverConfig);
});
@@ -73,11 +83,13 @@ test('test getAllPromisesForQueryingDevices when xcode detected', () => {
);
flipperServer.ios.iosBridge = {} as IOSBridge;
(flipperServer.ios as any).idbConfig = getFlipperServerConfig().settings;
flipperServer.ios.simctlBridge = fakeSimctlBridge;
const promises = flipperServer.ios.getAllPromisesForQueryingDevices(
true,
false,
);
expect(promises.length).toEqual(2);
expect(hasCalledSimctlActiveDevices).toEqual(true);
});
test('test getAllPromisesForQueryingDevices when xcode is not detected', () => {
@@ -87,11 +99,13 @@ test('test getAllPromisesForQueryingDevices when xcode is not detected', () => {
);
flipperServer.ios.iosBridge = {} as IOSBridge;
(flipperServer.ios as any).idbConfig = getFlipperServerConfig().settings;
flipperServer.ios.simctlBridge = fakeSimctlBridge;
const promises = flipperServer.ios.getAllPromisesForQueryingDevices(
false,
true,
);
expect(promises.length).toEqual(1);
expect(hasCalledSimctlActiveDevices).toEqual(false);
});
test('test getAllPromisesForQueryingDevices when xcode and idb are both unavailable', () => {
@@ -101,11 +115,13 @@ test('test getAllPromisesForQueryingDevices when xcode and idb are both unavaila
);
flipperServer.ios.iosBridge = {} as IOSBridge;
(flipperServer.ios as any).idbConfig = getFlipperServerConfig().settings;
flipperServer.ios.simctlBridge = fakeSimctlBridge;
const promises = flipperServer.ios.getAllPromisesForQueryingDevices(
false,
false,
);
expect(promises.length).toEqual(0);
expect(hasCalledSimctlActiveDevices).toEqual(false);
});
test('test getAllPromisesForQueryingDevices when both idb and xcode are available', () => {
@@ -115,9 +131,11 @@ test('test getAllPromisesForQueryingDevices when both idb and xcode are availabl
);
flipperServer.ios.iosBridge = {} as IOSBridge;
(flipperServer.ios as any).idbConfig = getFlipperServerConfig().settings;
flipperServer.ios.simctlBridge = fakeSimctlBridge;
const promises = flipperServer.ios.getAllPromisesForQueryingDevices(
true,
true,
);
expect(promises.length).toEqual(2);
expect(hasCalledSimctlActiveDevices).toEqual(false);
});

View File

@@ -11,14 +11,13 @@ import {ChildProcess} from 'child_process';
import type {IOSDeviceParams} from 'flipper-common';
import path from 'path';
import childProcess from 'child_process';
import {exec, execFile} from 'promisify-child-process';
import {exec} from 'promisify-child-process';
import iosUtil from './iOSContainerUtility';
import IOSDevice from './IOSDevice';
import {
ERR_NO_IDB_OR_XCODE_AVAILABLE,
IOSBridge,
makeIOSBridge,
getDeviceSetPath,
SimctlBridge,
} from './IOSBridge';
import {FlipperServerImpl} from '../../FlipperServerImpl';
@@ -27,26 +26,6 @@ import {getFlipperServerConfig} from '../../FlipperServerConfig';
import {IdbConfig, setIdbConfig} from './idbConfig';
import {assertNotNull} from 'flipper-server-core/src/comms/Utilities';
// eslint-disable-next-line @typescript-eslint/naming-convention
type iOSSimulatorDevice = {
state: 'Booted' | 'Shutdown' | 'Shutting Down';
availability?: string;
isAvailable?: 'YES' | 'NO' | true | false;
name: string;
udid: string;
};
function isAvailable(simulator: iOSSimulatorDevice): boolean {
// For some users "availability" is set, for others it's "isAvailable"
// It's not clear which key is set, so we are checking both.
// We've also seen isAvailable return "YES" and true, depending on version.
return (
simulator.availability === '(available)' ||
simulator.isAvailable === 'YES' ||
simulator.isAvailable === true
);
}
export class IOSDeviceManager {
private portForwarders: Array<ChildProcess> = [];
private idbConfig?: IdbConfig;
@@ -214,30 +193,7 @@ export class IOSDeviceManager {
}
getSimulators(bootedOnly: boolean): Promise<Array<IOSDeviceParams>> {
return execFile('xcrun', [
'simctl',
...getDeviceSetPath(),
'list',
'devices',
'--json',
])
.then(({stdout}) => JSON.parse(stdout!.toString()).devices)
.then((simulatorDevices: Array<iOSSimulatorDevice>) => {
const simulators = Object.values(simulatorDevices).flat();
return simulators
.filter(
(simulator) =>
(!bootedOnly || simulator.state === 'Booted') &&
isAvailable(simulator),
)
.map((simulator) => {
return {
...simulator,
type: 'emulator',
} as IOSDeviceParams;
});
})
.catch((e: Error) => {
return this.simctlBridge.getActiveDevices(bootedOnly).catch((e: Error) => {
console.warn('Failed to query simulators:', e);
if (e.message.includes('Xcode license agreements')) {
this.flipperServer.emit('notification', {