Simplify bridge types
Summary: This turns the bridge type into a simpler struct with always-present methods so you don't need to add additional null check to the calling logic which are hard to deal with. Reviewed By: mweststrate Differential Revision: D30248628 fbshipit-source-id: cdaee44efcbb19dcbb301099b4a7d0eb0c350e67
This commit is contained in:
committed by
Facebook GitHub Bot
parent
52b3edc5ad
commit
757ba91bf6
@@ -14,7 +14,10 @@ import JSONStream from 'JSONStream';
|
|||||||
import {Transform} from 'stream';
|
import {Transform} from 'stream';
|
||||||
import {exec} from 'promisify-child-process';
|
import {exec} from 'promisify-child-process';
|
||||||
import {default as promiseTimeout} from '../utils/promiseTimeout';
|
import {default as promiseTimeout} from '../utils/promiseTimeout';
|
||||||
import {IOSBridge} from '../utils/IOSBridge';
|
import {
|
||||||
|
ERR_PHYSICAL_DEVICE_LOGS_WITHOUT_IDB,
|
||||||
|
IOSBridge,
|
||||||
|
} from '../utils/IOSBridge';
|
||||||
import split2 from 'split2';
|
import split2 from 'split2';
|
||||||
|
|
||||||
type IOSLogLevel = 'Default' | 'Info' | 'Debug' | 'Error' | 'Fault';
|
type IOSLogLevel = 'Default' | 'Info' | 'Debug' | 'Error' | 'Fault';
|
||||||
@@ -63,8 +66,7 @@ export default class IOSDevice extends BaseDevice {
|
|||||||
if (!this.connected.get()) {
|
if (!this.connected.get()) {
|
||||||
return Buffer.from([]);
|
return Buffer.from([]);
|
||||||
}
|
}
|
||||||
// HACK: Will restructure the types to allow for the ! to be removed.
|
return await this.iOSBridge.screenshot(this.serial);
|
||||||
return await this.iOSBridge.screenshot!(this.serial);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
navigateToLocation(location: string) {
|
navigateToLocation(location: string) {
|
||||||
@@ -86,14 +88,18 @@ export default class IOSDevice extends BaseDevice {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const logListener = iOSBridge.startLogListener;
|
if (!this.log) {
|
||||||
if (
|
try {
|
||||||
!this.log &&
|
this.log = iOSBridge.startLogListener(this.serial, this.deviceType);
|
||||||
logListener &&
|
} catch (e) {
|
||||||
(this.deviceType === 'emulator' ||
|
if (e.message === ERR_PHYSICAL_DEVICE_LOGS_WITHOUT_IDB) {
|
||||||
(this.deviceType === 'physical' && iOSBridge.idbAvailable))
|
console.warn(e);
|
||||||
) {
|
} else {
|
||||||
this.log = logListener(this.serial, this.deviceType);
|
console.error('Failed to initialise device logs:', e);
|
||||||
|
this.startLogListener(iOSBridge, retries - 1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.log.on('error', (err: Error) => {
|
this.log.on('error', (err: Error) => {
|
||||||
console.error('iOS log tailer error', err);
|
console.error('iOS log tailer error', err);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
import configureStore from 'redux-mock-store';
|
import configureStore from 'redux-mock-store';
|
||||||
import {State, createRootReducer} from '../../reducers/index';
|
import {State, createRootReducer} from '../../reducers/index';
|
||||||
import {getInstance} from '../../fb-stubs/Logger';
|
import {getInstance} from '../../fb-stubs/Logger';
|
||||||
|
import {IOSBridge} from '../../utils/IOSBridge';
|
||||||
|
|
||||||
const mockStore = configureStore<State, {}>([])(
|
const mockStore = configureStore<State, {}>([])(
|
||||||
createRootReducer()(undefined, {type: 'INIT'}),
|
createRootReducer()(undefined, {type: 'INIT'}),
|
||||||
@@ -64,9 +65,7 @@ test('test getAllPromisesForQueryingDevices when xcode detected', () => {
|
|||||||
const promises = getAllPromisesForQueryingDevices(
|
const promises = getAllPromisesForQueryingDevices(
|
||||||
mockStore,
|
mockStore,
|
||||||
logger,
|
logger,
|
||||||
{
|
{} as IOSBridge,
|
||||||
idbAvailable: false,
|
|
||||||
},
|
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
expect(promises.length).toEqual(3);
|
expect(promises.length).toEqual(3);
|
||||||
@@ -76,9 +75,7 @@ test('test getAllPromisesForQueryingDevices when xcode is not detected', () => {
|
|||||||
const promises = getAllPromisesForQueryingDevices(
|
const promises = getAllPromisesForQueryingDevices(
|
||||||
mockStore,
|
mockStore,
|
||||||
logger,
|
logger,
|
||||||
{
|
{} as IOSBridge,
|
||||||
idbAvailable: true,
|
|
||||||
},
|
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
expect(promises.length).toEqual(1);
|
expect(promises.length).toEqual(1);
|
||||||
|
|||||||
@@ -21,7 +21,11 @@ import IOSDevice from '../devices/IOSDevice';
|
|||||||
import {addErrorNotification} from '../reducers/notifications';
|
import {addErrorNotification} from '../reducers/notifications';
|
||||||
import {getStaticPath} from '../utils/pathUtils';
|
import {getStaticPath} from '../utils/pathUtils';
|
||||||
import {destroyDevice} from '../reducers/connections';
|
import {destroyDevice} from '../reducers/connections';
|
||||||
import {IOSBridge, makeIOSBridge} from '../utils/IOSBridge';
|
import {
|
||||||
|
ERR_NO_IDB_OR_XCODE_AVAILABLE,
|
||||||
|
IOSBridge,
|
||||||
|
makeIOSBridge,
|
||||||
|
} from '../utils/IOSBridge';
|
||||||
|
|
||||||
type iOSSimulatorDevice = {
|
type iOSSimulatorDevice = {
|
||||||
state: 'Booted' | 'Shutdown' | 'Shutting Down';
|
state: 'Booted' | 'Shutdown' | 'Shutting Down';
|
||||||
@@ -312,21 +316,29 @@ export default (store: Store, logger: Logger) => {
|
|||||||
}
|
}
|
||||||
iosUtil
|
iosUtil
|
||||||
.isXcodeDetected()
|
.isXcodeDetected()
|
||||||
.then(
|
.then(async (isDetected) => {
|
||||||
(isDetected) => {
|
|
||||||
store.dispatch(setXcodeDetected(isDetected));
|
store.dispatch(setXcodeDetected(isDetected));
|
||||||
if (store.getState().settingsState.enablePhysicalIOS) {
|
if (store.getState().settingsState.enablePhysicalIOS) {
|
||||||
startDevicePortForwarders();
|
startDevicePortForwarders();
|
||||||
}
|
}
|
||||||
return makeIOSBridge(
|
try {
|
||||||
|
// Awaiting the promise here to trigger immediate error handling.
|
||||||
|
return await makeIOSBridge(
|
||||||
store.getState().settingsState.idbPath,
|
store.getState().settingsState.idbPath,
|
||||||
isDetected,
|
isDetected,
|
||||||
);
|
);
|
||||||
},
|
} catch (err) {
|
||||||
(err) => {
|
// This case is expected if both Xcode and idb are missing.
|
||||||
|
if (err.message === ERR_NO_IDB_OR_XCODE_AVAILABLE) {
|
||||||
|
console.warn(
|
||||||
|
'Failed to init iOS device. You may want to disable iOS support in the settings.',
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
console.error('Failed to initialize iOS dispatcher:', err);
|
console.error('Failed to initialize iOS dispatcher:', err);
|
||||||
},
|
}
|
||||||
)
|
}
|
||||||
|
})
|
||||||
.then(
|
.then(
|
||||||
(iosBridge) => iosBridge && queryDevicesForever(store, logger, iosBridge),
|
(iosBridge) => iosBridge && queryDevicesForever(store, logger, iosBridge),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,13 +15,18 @@ import path from 'path';
|
|||||||
import {exec} from 'promisify-child-process';
|
import {exec} from 'promisify-child-process';
|
||||||
import {getAppTempPath} from '../utils/pathUtils';
|
import {getAppTempPath} from '../utils/pathUtils';
|
||||||
|
|
||||||
|
export const ERR_NO_IDB_OR_XCODE_AVAILABLE =
|
||||||
|
'Neither Xcode nor idb available. Cannot provide iOS device functionality.';
|
||||||
|
|
||||||
|
export const ERR_PHYSICAL_DEVICE_LOGS_WITHOUT_IDB =
|
||||||
|
'Cannot provide logs from a physical device without idb.';
|
||||||
|
|
||||||
export interface IOSBridge {
|
export interface IOSBridge {
|
||||||
idbAvailable: boolean;
|
startLogListener: (
|
||||||
startLogListener?: (
|
|
||||||
udid: string,
|
udid: string,
|
||||||
deviceType: DeviceType,
|
deviceType: DeviceType,
|
||||||
) => child_process.ChildProcessWithoutNullStreams;
|
) => child_process.ChildProcessWithoutNullStreams;
|
||||||
screenshot?: (serial: string) => Promise<Buffer>;
|
screenshot: (serial: string) => Promise<Buffer>;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function isAvailable(idbPath: string): Promise<boolean> {
|
async function isAvailable(idbPath: string): Promise<boolean> {
|
||||||
@@ -65,6 +70,9 @@ export function idbStartLogListener(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function xcrunStartLogListener(udid: string, deviceType: DeviceType) {
|
export function xcrunStartLogListener(udid: string, deviceType: DeviceType) {
|
||||||
|
if (deviceType === 'physical') {
|
||||||
|
throw new Error(ERR_PHYSICAL_DEVICE_LOGS_WITHOUT_IDB);
|
||||||
|
}
|
||||||
const deviceSetPath = process.env.DEVICE_SET_PATH
|
const deviceSetPath = process.env.DEVICE_SET_PATH
|
||||||
? ['--set', process.env.DEVICE_SET_PATH]
|
? ['--set', process.env.DEVICE_SET_PATH]
|
||||||
: [];
|
: [];
|
||||||
@@ -121,7 +129,6 @@ export async function makeIOSBridge(
|
|||||||
// prefer idb
|
// prefer idb
|
||||||
if (await isAvailableFn(idbPath)) {
|
if (await isAvailableFn(idbPath)) {
|
||||||
return {
|
return {
|
||||||
idbAvailable: true,
|
|
||||||
startLogListener: idbStartLogListener.bind(null, idbPath),
|
startLogListener: idbStartLogListener.bind(null, idbPath),
|
||||||
screenshot: idbScreenshot,
|
screenshot: idbScreenshot,
|
||||||
};
|
};
|
||||||
@@ -130,13 +137,10 @@ export async function makeIOSBridge(
|
|||||||
// no idb, if it's a simulator and xcode is available, we can use xcrun
|
// no idb, if it's a simulator and xcode is available, we can use xcrun
|
||||||
if (isXcodeDetected) {
|
if (isXcodeDetected) {
|
||||||
return {
|
return {
|
||||||
idbAvailable: false,
|
|
||||||
startLogListener: xcrunStartLogListener,
|
startLogListener: xcrunStartLogListener,
|
||||||
screenshot: xcrunScreenshot,
|
screenshot: xcrunScreenshot,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// no idb, and not a simulator, we can't log this device
|
|
||||||
return {
|
throw new Error(ERR_NO_IDB_OR_XCODE_AVAILABLE);
|
||||||
idbAvailable: false,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,9 +20,8 @@ const exec = mocked(promisifyChildProcess.exec);
|
|||||||
|
|
||||||
test('uses xcrun with no idb when xcode is detected', async () => {
|
test('uses xcrun with no idb when xcode is detected', async () => {
|
||||||
const ib = await makeIOSBridge('', true);
|
const ib = await makeIOSBridge('', true);
|
||||||
expect(ib.startLogListener).toBeDefined();
|
|
||||||
|
|
||||||
ib.startLogListener!('deadbeef', 'emulator');
|
ib.startLogListener('deadbeef', 'emulator');
|
||||||
|
|
||||||
expect(spawn).toHaveBeenCalledWith(
|
expect(spawn).toHaveBeenCalledWith(
|
||||||
'xcrun',
|
'xcrun',
|
||||||
@@ -45,9 +44,8 @@ test('uses xcrun with no idb when xcode is detected', async () => {
|
|||||||
|
|
||||||
test('uses idb when present and xcode detected', async () => {
|
test('uses idb when present and xcode detected', async () => {
|
||||||
const ib = await makeIOSBridge('/usr/local/bin/idb', true, async (_) => true);
|
const ib = await makeIOSBridge('/usr/local/bin/idb', true, async (_) => true);
|
||||||
expect(ib.startLogListener).toBeDefined();
|
|
||||||
|
|
||||||
ib.startLogListener!('deadbeef', 'emulator');
|
ib.startLogListener('deadbeef', 'emulator');
|
||||||
|
|
||||||
expect(spawn).toHaveBeenCalledWith(
|
expect(spawn).toHaveBeenCalledWith(
|
||||||
'/usr/local/bin/idb',
|
'/usr/local/bin/idb',
|
||||||
@@ -69,9 +67,8 @@ test('uses idb when present and xcode detected', async () => {
|
|||||||
|
|
||||||
test('uses idb when present and xcode detected and physical device connected', async () => {
|
test('uses idb when present and xcode detected and physical device connected', async () => {
|
||||||
const ib = await makeIOSBridge('/usr/local/bin/idb', true, async (_) => true);
|
const ib = await makeIOSBridge('/usr/local/bin/idb', true, async (_) => true);
|
||||||
expect(ib.startLogListener).toBeDefined();
|
|
||||||
|
|
||||||
ib.startLogListener!('deadbeef', 'physical');
|
ib.startLogListener('deadbeef', 'physical');
|
||||||
|
|
||||||
expect(spawn).toHaveBeenCalledWith(
|
expect(spawn).toHaveBeenCalledWith(
|
||||||
'/usr/local/bin/idb',
|
'/usr/local/bin/idb',
|
||||||
@@ -88,19 +85,19 @@ test('uses idb when present and xcode detected and physical device connected', a
|
|||||||
|
|
||||||
test("without idb physical devices can't log", async () => {
|
test("without idb physical devices can't log", async () => {
|
||||||
const ib = await makeIOSBridge('', true);
|
const ib = await makeIOSBridge('', true);
|
||||||
expect(ib.idbAvailable).toBeFalsy();
|
|
||||||
expect(ib.startLogListener).toBeDefined(); // since we have xcode
|
expect(ib.startLogListener).toBeDefined(); // since we have xcode
|
||||||
});
|
});
|
||||||
|
|
||||||
test('uses no log listener when xcode is not detected', async () => {
|
test('throws if no iOS support', async () => {
|
||||||
const ib = await makeIOSBridge('', false);
|
await expect(makeIOSBridge('', false)).rejects.toThrow(
|
||||||
expect(ib.startLogListener).toBeUndefined();
|
'Neither Xcode nor idb available. Cannot provide iOS device functionality.',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('uses xcrun to take screenshots with no idb when xcode is detected', async () => {
|
test('uses xcrun to take screenshots with no idb when xcode is detected', async () => {
|
||||||
const ib = await makeIOSBridge('', true);
|
const ib = await makeIOSBridge('', true);
|
||||||
|
|
||||||
ib.screenshot!('deadbeef');
|
ib.screenshot('deadbeef');
|
||||||
|
|
||||||
expect(exec).toHaveBeenCalledWith(
|
expect(exec).toHaveBeenCalledWith(
|
||||||
'xcrun simctl io deadbeef screenshot /temp/00000000-0000-0000-0000-000000000000.png',
|
'xcrun simctl io deadbeef screenshot /temp/00000000-0000-0000-0000-000000000000.png',
|
||||||
@@ -110,7 +107,7 @@ test('uses xcrun to take screenshots with no idb when xcode is detected', async
|
|||||||
test('uses idb to take screenshots when available', async () => {
|
test('uses idb to take screenshots when available', async () => {
|
||||||
const ib = await makeIOSBridge('/usr/local/bin/idb', true, async (_) => true);
|
const ib = await makeIOSBridge('/usr/local/bin/idb', true, async (_) => true);
|
||||||
|
|
||||||
ib.screenshot!('deadbeef');
|
ib.screenshot('deadbeef');
|
||||||
|
|
||||||
expect(exec).toHaveBeenCalledWith(
|
expect(exec).toHaveBeenCalledWith(
|
||||||
'idb screenshot --udid deadbeef /temp/00000000-0000-0000-0000-000000000000.png',
|
'idb screenshot --udid deadbeef /temp/00000000-0000-0000-0000-000000000000.png',
|
||||||
|
|||||||
Reference in New Issue
Block a user