diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index ded2d1d86b..aac2b2cc1d 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -167,3 +167,5 @@ $injector.requireCommand("resources|generate|splashes", "./commands/generate-ass $injector.requirePublic("assetsGenerationService", "./services/assets-generation/assets-generation-service"); $injector.require("filesHashService", "./services/files-hash-service"); +$injector.require("iOSLogParserService", "./services/ios-log-parser-service"); +$injector.require("iOSDebuggerPortService", "./services/ios-debugger-port-service"); diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 0a09cb8978..5ed5ec1d7e 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -143,13 +143,15 @@ export class DebugIOSCommand implements ICommand { private $injector: IInjector, private $projectData: IProjectData, private $platformsData: IPlatformsData, - $iosDeviceOperations: IIOSDeviceOperations) { + $iosDeviceOperations: IIOSDeviceOperations, + $iOSSimulatorLogProvider: Mobile.IiOSSimulatorLogProvider) { this.$projectData.initializeProjectData(); // Do not dispose ios-device-lib, so the process will remain alive and the debug application (NativeScript Inspector or Chrome DevTools) will be able to connect to the socket. // In case we dispose ios-device-lib, the socket will be closed and the code will fail when the debug application tries to read/send data to device socket. // That's why the `$ tns debug ios --justlaunch` command will not release the terminal. // In case we do not set it to false, the dispose will be called once the command finishes its execution, which will prevent the debugging. $iosDeviceOperations.setShouldDispose(false); + $iOSSimulatorLogProvider.setShouldDispose(false); } public execute(args: string[]): Promise { diff --git a/lib/common b/lib/common index 8a13846f54..894b0f7d35 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 8a13846f54df67c62516f862001c59fe39c3ac6b +Subproject commit 894b0f7d35637da7c790b29a8eaf1cbe9f6f85e1 diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index ae5637ae1d..f2b21b0e6b 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -645,7 +645,7 @@ interface ISocketProxyFactory extends NodeJS.EventEmitter { interface IiOSNotification { getWaitForDebug(projectId: string): string; - getAttachRequest(projectId: string): string; + getAttachRequest(projectId: string, deviceId: string): string; getAppLaunching(projectId: string): string; getReadyForAttach(projectId: string): string; getAttachAvailabilityQuery(projectId: string): string; diff --git a/lib/definitions/debug.d.ts b/lib/definitions/debug.d.ts index 1128eb489c..5afa583073 100644 --- a/lib/definitions/debug.d.ts +++ b/lib/definitions/debug.d.ts @@ -87,6 +87,10 @@ interface IDebugOptions { * Defines if the iOS App Inspector should be used instead of providing URL to debug the application with Chrome DevTools */ inspector?: boolean; + /** + * Defines if should print all availableDevices + */ + availableDevices?: boolean; } /** diff --git a/lib/definitions/ios-debugger-port-service.d.ts b/lib/definitions/ios-debugger-port-service.d.ts new file mode 100644 index 0000000000..326df11e75 --- /dev/null +++ b/lib/definitions/ios-debugger-port-service.d.ts @@ -0,0 +1,28 @@ +interface IIOSDebuggerPortInputData { + deviceId: string; + appId: string; +} + +interface IIOSDebuggerPortData extends IIOSDebuggerPortInputData { + port: number; +} + +interface IIOSDebuggerPortStoredData { + port: number; + timer?: NodeJS.Timer; +} + +interface IIOSDebuggerPortService { + /** + * Gets iOS debugger port for specified deviceId and appId + * @param {IIOSDebuggerPortInputData} data - Describes deviceId and appId + */ + getPort(data: IIOSDebuggerPortInputData): Promise; + /** + * Attaches on DEBUGGER_PORT_FOUND event and STARTING_IOS_APPLICATION events + * In case when DEBUGGER_PORT_FOUND event is emitted, stores the port and clears the timeout if such. + * In case when STARTING_IOS_APPLICATION event is emiited, sets the port to null and add timeout for 5000 miliseconds which checks if port is null and prints a warning. + * @param {Mobile.IDevice} device - Describes the device which logs should be parsed. + */ + attachToDebuggerPortFoundEvent(device: Mobile.IDevice): void; +} \ No newline at end of file diff --git a/lib/definitions/ios-log-parser-service.d.ts b/lib/definitions/ios-log-parser-service.d.ts new file mode 100644 index 0000000000..1a46f0011f --- /dev/null +++ b/lib/definitions/ios-log-parser-service.d.ts @@ -0,0 +1,7 @@ +interface IIOSLogParserService extends NodeJS.EventEmitter { + /** + * Starts looking for debugger port. Attaches on device logs and processes them. In case when port is found, DEBUGGER_PORT_FOUND event is emitted. + * @param {Mobile.IDevice} device - Describes the device which logs will be processed. + */ + startParsingLog(device: Mobile.IDevice): void; +} diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 4ec6eb1d49..4f878aa7d1 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -218,6 +218,7 @@ interface IProjectTemplatesService { interface IPlatformProjectServiceBase { getPluginPlatformsFolderPath(pluginData: IPluginData, platform: string): string; + getFrameworkVersion(projectData: IProjectData): string; } interface IBuildForDevice { @@ -270,7 +271,7 @@ interface ILocalBuildService { interface ICleanNativeAppData extends IProjectDir, IPlatform { } -interface IPlatformProjectService extends NodeJS.EventEmitter { +interface IPlatformProjectService extends NodeJS.EventEmitter, IPlatformProjectServiceBase { getPlatformData(projectData: IProjectData): IPlatformData; validate(projectData: IProjectData): Promise; createProject(frameworkDir: string, frameworkVersion: string, projectData: IProjectData, config: ICreateProjectOptions): Promise; diff --git a/lib/device-sockets/ios/notification.ts b/lib/device-sockets/ios/notification.ts index 7c44aa1858..d5bef34de4 100644 --- a/lib/device-sockets/ios/notification.ts +++ b/lib/device-sockets/ios/notification.ts @@ -1,4 +1,7 @@ -export class IOSNotification implements IiOSNotification { +import { EventEmitter } from "events"; +import { ATTACH_REQUEST_EVENT_NAME } from "../../common/constants"; + +export class IOSNotification extends EventEmitter implements IiOSNotification { private static WAIT_FOR_DEBUG_NOTIFICATION_NAME = "WaitForDebugger"; private static ATTACH_REQUEST_NOTIFICATION_NAME = "AttachRequest"; private static APP_LAUNCHING_NOTIFICATION_NAME = "AppLaunching"; @@ -11,8 +14,9 @@ export class IOSNotification implements IiOSNotification { return this.formatNotification(IOSNotification.WAIT_FOR_DEBUG_NOTIFICATION_NAME, projectId); } - public getAttachRequest(projectId: string): string { - return this.formatNotification(IOSNotification.ATTACH_REQUEST_NOTIFICATION_NAME, projectId); + public getAttachRequest(appId: string, deviceId: string): string { + this.emit(ATTACH_REQUEST_EVENT_NAME, { deviceId, appId }); + return this.formatNotification(IOSNotification.ATTACH_REQUEST_NOTIFICATION_NAME, appId); } public getAppLaunching(projectId: string): string { diff --git a/lib/device-sockets/ios/socket-request-executor.ts b/lib/device-sockets/ios/socket-request-executor.ts index c52728b2fa..88bb8e5bc4 100644 --- a/lib/device-sockets/ios/socket-request-executor.ts +++ b/lib/device-sockets/ios/socket-request-executor.ts @@ -61,7 +61,7 @@ export class IOSSocketRequestExecutor implements IiOSSocketRequestExecutor { const readyForAttachSocket = await this.$iOSNotificationService.postNotification(deviceIdentifier, this.$iOSNotification.getReadyForAttach(projectId), constants.IOS_OBSERVE_NOTIFICATION_COMMAND_TYPE); const readyForAttachPromise = this.$iOSNotificationService.awaitNotification(deviceIdentifier, +readyForAttachSocket, readyForAttachTimeout); - await this.$iOSNotificationService.postNotification(deviceIdentifier, this.$iOSNotification.getAttachRequest(projectId)); + await this.$iOSNotificationService.postNotification(deviceIdentifier, this.$iOSNotification.getAttachRequest(projectId, deviceIdentifier)); await readyForAttachPromise; } catch (e) { this.$logger.trace("Launch request error:"); @@ -76,7 +76,7 @@ export class IOSSocketRequestExecutor implements IiOSSocketRequestExecutor { // before we send the PostNotification. const readyForAttachSocket = await this.$iOSNotificationService.postNotification(deviceIdentifier, this.$iOSNotification.getReadyForAttach(projectId), constants.IOS_OBSERVE_NOTIFICATION_COMMAND_TYPE); const readyForAttachPromise = this.$iOSNotificationService.awaitNotification(deviceIdentifier, +readyForAttachSocket, timeout); - await this.$iOSNotificationService.postNotification(deviceIdentifier, this.$iOSNotification.getAttachRequest(projectId)); + await this.$iOSNotificationService.postNotification(deviceIdentifier, this.$iOSNotification.getAttachRequest(projectId, deviceIdentifier)); await readyForAttachPromise; } catch (e) { this.$errors.failWithoutHelp(`The application ${projectId} timed out when performing the socket handshake.`); diff --git a/lib/helpers/livesync-command-helper.ts b/lib/helpers/livesync-command-helper.ts index 3f16130441..52adbecc8f 100644 --- a/lib/helpers/livesync-command-helper.ts +++ b/lib/helpers/livesync-command-helper.ts @@ -10,7 +10,8 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { private $platformsData: IPlatformsData, private $analyticsService: IAnalyticsService, private $bundleValidatorHelper: IBundleValidatorHelper, - private $errors: IErrors) { + private $errors: IErrors, + private $iOSSimulatorLogProvider: Mobile.IiOSSimulatorLogProvider) { this.$analyticsService.setShouldDispose(this.$options.justlaunch || !this.$options.watch); } @@ -53,6 +54,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { const shouldKeepProcessAlive = this.$options.watch || !this.$options.justlaunch; if (workingWithiOSDevices && shouldKeepProcessAlive) { this.$iosDeviceOperations.setShouldDispose(false); + this.$iOSSimulatorLogProvider.setShouldDispose(false); } if (this.$options.release) { diff --git a/lib/services/ios-debug-service.ts b/lib/services/ios-debug-service.ts index c2ff8c6226..98ba3493c0 100644 --- a/lib/services/ios-debug-service.ts +++ b/lib/services/ios-debug-service.ts @@ -6,10 +6,6 @@ import { ChildProcess } from "child_process"; import { DebugServiceBase } from "./debug-service-base"; import { CONNECTION_ERROR_EVENT_NAME, AWAIT_NOTIFICATION_TIMEOUT_SECONDS } from "../constants"; import { getPidFromiOSSimulatorLogs } from "../common/helpers"; - -import byline = require("byline"); - -const inspectorBackendPort = 18181; const inspectorAppName = "NativeScript Inspector.app"; const inspectorNpmPackageName = "tns-ios-inspector"; const inspectorUiDir = "WebInspectorUI/"; @@ -17,7 +13,6 @@ const inspectorUiDir = "WebInspectorUI/"; export class IOSDebugService extends DebugServiceBase implements IPlatformDebugService { private _lldbProcess: ChildProcess; private _sockets: net.Socket[] = []; - private _childProcess: ChildProcess; private _socketProxy: any; constructor(protected device: Mobile.IDevice, @@ -29,11 +24,11 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS private $logger: ILogger, private $errors: IErrors, private $npmInstallationManager: INpmInstallationManager, + private $iOSDebuggerPortService: IIOSDebuggerPortService, private $iOSNotification: IiOSNotification, private $iOSSocketRequestExecutor: IiOSSocketRequestExecutor, private $processService: IProcessService, private $socketProxyFactory: ISocketProxyFactory, - private $net: INet, private $projectDataService: IProjectDataService) { super(device, $devicesService); this.$processService.attachToProcessExitSignals(this, this.debugStop); @@ -90,11 +85,6 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS await this.killProcess(this._lldbProcess); this._lldbProcess = undefined; } - - if (this._childProcess) { - await this.killProcess(this._childProcess); - this._childProcess = undefined; - } } protected getChromeDebugUrl(debugOptions: IDebugOptions, port: number): string { @@ -116,7 +106,7 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS private async emulatorDebugBrk(debugData: IDebugData, debugOptions: IDebugOptions): Promise { const args = debugOptions.debugBrk ? "--nativescript-debug-brk" : "--nativescript-debug-start"; - const child_process = await this.$iOSEmulatorServices.runApplicationOnEmulator(debugData.pathToAppPackage, { + const launchResult = await this.$iOSEmulatorServices.runApplicationOnEmulator(debugData.pathToAppPackage, { waitForDebugger: true, captureStdin: true, args: args, @@ -124,30 +114,13 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS skipInstall: true }); - const lineStream = byline(child_process.stdout); - this._childProcess = child_process; - - lineStream.on('data', (line: NodeBuffer) => { - const lineText = line.toString(); - if (lineText && _.startsWith(lineText, debugData.applicationIdentifier)) { - const pid = getPidFromiOSSimulatorLogs(debugData.applicationIdentifier, lineText); - if (!pid) { - this.$logger.trace(`Line ${lineText} does not contain PID of the application ${debugData.applicationIdentifier}.`); - return; - } - - this._lldbProcess = this.$childProcess.spawn("lldb", ["-p", pid]); - if (log4js.levels.TRACE.isGreaterThanOrEqualTo(this.$logger.getLevel())) { - this._lldbProcess.stdout.pipe(process.stdout); - } - this._lldbProcess.stderr.pipe(process.stderr); - this._lldbProcess.stdin.write("process continue\n"); - } else { - process.stdout.write(line + "\n"); - } - }); - - await this.waitForBackendPortToBeOpened(debugData.deviceIdentifier); + const pid = getPidFromiOSSimulatorLogs(debugData.applicationIdentifier, launchResult); + this._lldbProcess = this.$childProcess.spawn("lldb", ["-p", pid]); + if (log4js.levels.TRACE.isGreaterThanOrEqualTo(this.$logger.getLevel())) { + this._lldbProcess.stdout.pipe(process.stdout); + } + this._lldbProcess.stderr.pipe(process.stderr); + this._lldbProcess.stdin.write("process continue\n"); return this.wireDebuggerClient(debugData, debugOptions); } @@ -155,23 +128,13 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS private async emulatorStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise { const result = await this.wireDebuggerClient(debugData, debugOptions); - const attachRequestMessage = this.$iOSNotification.getAttachRequest(debugData.applicationIdentifier); + const attachRequestMessage = this.$iOSNotification.getAttachRequest(debugData.applicationIdentifier, debugData.deviceIdentifier); const iOSEmulatorService = this.$iOSEmulatorServices; - await iOSEmulatorService.postDarwinNotification(attachRequestMessage); - await this.waitForBackendPortToBeOpened(debugData.deviceIdentifier); + await iOSEmulatorService.postDarwinNotification(attachRequestMessage, debugData.deviceIdentifier); return result; } - private async waitForBackendPortToBeOpened(deviceIdentifier: string): Promise { - const portListens = await this.$net.waitForPortToListen({ port: inspectorBackendPort, timeout: 10000, interval: 200 }); - if (!portListens) { - const error = new Error("Unable to connect to application. Ensure application is running on simulator."); - error.deviceIdentifier = deviceIdentifier; - throw error; - } - } - private async deviceDebugBrk(debugData: IDebugData, debugOptions: IDebugOptions): Promise { await this.$devicesService.initialize({ platform: this.platform, deviceId: debugData.deviceIdentifier }); const projectData = this.$projectDataService.getProjectData(debugData.projectDir); @@ -219,7 +182,7 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS // the VSCode Ext starts `tns debug ios --no-client` to start/attach to debug sessions // check if --no-client is passed - default to opening a tcp socket (versus Chrome DevTools (websocket)) if ((debugOptions.inspector || !debugOptions.client) && this.$hostInfo.isDarwin) { - this._socketProxy = await this.$socketProxyFactory.createTCPSocketProxy(this.getSocketFactory(device)); + this._socketProxy = await this.$socketProxyFactory.createTCPSocketProxy(this.getSocketFactory(debugData, device)); await this.openAppInspector(this._socketProxy.address(), debugData, debugOptions); return null; } else { @@ -228,7 +191,7 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS } const deviceIdentifier = device ? device.deviceInfo.identifier : debugData.deviceIdentifier; - this._socketProxy = await this.$socketProxyFactory.createWebSocketProxy(this.getSocketFactory(device), deviceIdentifier); + this._socketProxy = await this.$socketProxyFactory.createWebSocketProxy(this.getSocketFactory(debugData, device), deviceIdentifier); return this.getChromeDebugUrl(debugOptions, this._socketProxy.options.port); } } @@ -247,9 +210,13 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS } } - private getSocketFactory(device?: Mobile.IiOSDevice): () => Promise { + private getSocketFactory(debugData: IDebugData, device?: Mobile.IiOSDevice): () => Promise { const factory = async () => { - const socket = device ? await device.connectToPort(inspectorBackendPort) : net.connect(inspectorBackendPort); + const port = await this.$iOSDebuggerPortService.getPort({ deviceId: debugData.deviceIdentifier, appId: debugData.applicationIdentifier }); + if (!port) { + this.$errors.fail("NativeScript debugger was not able to get inspector socket port."); + } + const socket = device ? await device.connectToPort(port) : net.connect(port); this._sockets.push(socket); return socket; }; diff --git a/lib/services/ios-debugger-port-service.ts b/lib/services/ios-debugger-port-service.ts new file mode 100644 index 0000000000..2baae21c7e --- /dev/null +++ b/lib/services/ios-debugger-port-service.ts @@ -0,0 +1,103 @@ +import { DEBUGGER_PORT_FOUND_EVENT_NAME, ATTACH_REQUEST_EVENT_NAME } from "../common/constants"; +import { cache } from "../common/decorators"; +import * as semver from "semver"; + +export class IOSDebuggerPortService implements IIOSDebuggerPortService { + private mapDebuggerPortData: IDictionary = {}; + private static DEFAULT_PORT = 18181; + private static MIN_REQUIRED_FRAMEWORK_VERSION = "4.0.1"; + + constructor(private $iOSLogParserService: IIOSLogParserService, + private $iOSProjectService: IPlatformProjectService, + private $logger: ILogger, + private $projectData: IProjectData) { } + + public getPort(data: IIOSDebuggerPortInputData): Promise { + return new Promise((resolve, reject) => { + if (!this.canStartLookingForDebuggerPort()) { + return IOSDebuggerPortService.DEFAULT_PORT; + } + + const key = `${data.deviceId}${data.appId}`; + let retryCount: number = 10; + + const interval = setInterval(() => { + let port = this.getPortByKey(key); + if (port || retryCount === 0) { + clearInterval(interval); + resolve(port); + } else { + port = this.getPortByKey(key); + retryCount--; + } + }, 500); + }); + } + + public attachToDebuggerPortFoundEvent(device: Mobile.IDevice): void { + if (!this.canStartLookingForDebuggerPort()) { + return; + } + + this.attachToDebuggerPortFoundEventCore(); + this.attachToAttachRequestEvent(device); + + this.$iOSLogParserService.startParsingLog(device); + } + + private canStartLookingForDebuggerPort(): boolean { + const frameworkVersion = this.$iOSProjectService.getFrameworkVersion(this.$projectData); + return semver.gt(frameworkVersion, IOSDebuggerPortService.MIN_REQUIRED_FRAMEWORK_VERSION); + } + + @cache() + private attachToDebuggerPortFoundEventCore(): void { + this.$iOSLogParserService.on(DEBUGGER_PORT_FOUND_EVENT_NAME, (data: IIOSDebuggerPortData) => { + this.$logger.trace(DEBUGGER_PORT_FOUND_EVENT_NAME, data); + this.setData(data, { port: data.port }); + this.clearTimeout(data); + }); + } + + @cache() + private attachToAttachRequestEvent(device: Mobile.IDevice): void { + device.applicationManager.on(ATTACH_REQUEST_EVENT_NAME, (data: IIOSDebuggerPortData) => { + this.$logger.trace(ATTACH_REQUEST_EVENT_NAME, data); + const timer = setTimeout(() => { + this.clearTimeout(data); + if (!this.getPortByKey(`${data.deviceId}${data.appId}`)) { + this.$logger.warn(`NativeScript debugger was not able to get inspector socket port on device ${data.deviceId} for application ${data.appId}.`); + } + }, 5000); + + this.setData(data, { port: null, timer }); + }); + } + + private getPortByKey(key: string): number { + if (this.mapDebuggerPortData[key]) { + return this.mapDebuggerPortData[key].port; + } + + return null; + } + + private setData(data: IIOSDebuggerPortData, storedData: IIOSDebuggerPortStoredData): void { + const key = `${data.deviceId}${data.appId}`; + + if (!this.mapDebuggerPortData[key]) { + this.mapDebuggerPortData[key] = {}; + } + + this.mapDebuggerPortData[key].port = storedData.port; + this.mapDebuggerPortData[key].timer = storedData.timer; + } + + private clearTimeout(data: IIOSDebuggerPortData): void { + const storedData = this.mapDebuggerPortData[`${data.deviceId}${data.appId}`]; + if (storedData && storedData.timer) { + clearTimeout(storedData.timer); + } + } +} +$injector.register("iOSDebuggerPortService", IOSDebuggerPortService); diff --git a/lib/services/ios-log-parser-service.ts b/lib/services/ios-log-parser-service.ts new file mode 100644 index 0000000000..dd8108f34e --- /dev/null +++ b/lib/services/ios-log-parser-service.ts @@ -0,0 +1,50 @@ +import { DEBUGGER_PORT_FOUND_EVENT_NAME, DEVICE_LOG_EVENT_NAME } from "../common/constants"; +import { cache } from "../common/decorators"; +import { EventEmitter } from "events"; + +export class IOSLogParserService extends EventEmitter implements IIOSLogParserService { + private static MESSAGE_REGEX = /NativeScript debugger has opened inspector socket on port (\d+?) for (.*)[.]/; + + constructor(private $deviceLogProvider: Mobile.IDeviceLogProvider, + private $iosDeviceOperations: IIOSDeviceOperations, + private $iOSSimulatorLogProvider: Mobile.IiOSSimulatorLogProvider, + private $logger: ILogger, + private $projectData: IProjectData) { + super(); + } + + public startParsingLog(device: Mobile.IDevice): void { + this.$deviceLogProvider.setProjectNameForDevice(device.deviceInfo.identifier, this.$projectData.projectName); + + this.startParsingLogCore(device); + this.startLogProcess(device); + } + + @cache() + private startParsingLogCore(device: Mobile.IDevice): void { + const logProvider = device.isEmulator ? this.$iOSSimulatorLogProvider : this.$iosDeviceOperations; + logProvider.on(DEVICE_LOG_EVENT_NAME, (response: IOSDeviceLib.IDeviceLogData) => this.processDeviceLogResponse(response)); + } + + private processDeviceLogResponse(response: IOSDeviceLib.IDeviceLogData) { + const matches = IOSLogParserService.MESSAGE_REGEX.exec(response.message); + if (matches) { + const data = { + port: parseInt(matches[1]), + appId: matches[2], + deviceId: response.deviceId + }; + this.$logger.trace(`Emitting ${DEBUGGER_PORT_FOUND_EVENT_NAME} event`, data); + this.emit(DEBUGGER_PORT_FOUND_EVENT_NAME, data); + } + } + + private startLogProcess(device: Mobile.IDevice): void { + if (device.isEmulator) { + return this.$iOSSimulatorLogProvider.startNewMutedLogProcess(device.deviceInfo.identifier); + } + + return this.$iosDeviceOperations.startDeviceLog(device.deviceInfo.identifier); + } +} +$injector.register("iOSLogParserService", IOSLogParserService); diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index 4191bee724..a56a6c1961 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -125,7 +125,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ } public getAppResourcesDestinationDirectoryPath(projectData: IProjectData): string { - const frameworkVersion = this.getFrameworkVersion(this.getPlatformData(projectData).frameworkPackageName, projectData.projectDir); + const frameworkVersion = this.getFrameworkVersion(projectData); if (semver.lt(frameworkVersion, "1.3.0")) { return path.join(this.getPlatformData(projectData).projectRoot, projectData.projectName, "Resources", "icons"); @@ -337,7 +337,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ ]; // Starting from tns-ios 1.4 the xcconfig file is referenced in the project template - const frameworkVersion = this.getFrameworkVersion(this.getPlatformData(projectData).frameworkPackageName, projectData.projectDir); + const frameworkVersion = this.getFrameworkVersion(projectData); if (semver.lt(frameworkVersion, "1.4.0")) { basicArgs.push("-xcconfig", path.join(projectRoot, projectData.projectName, BUILD_XCCONFIG_FILE_NAME)); } diff --git a/lib/services/livesync/ios-device-livesync-service.ts b/lib/services/livesync/ios-device-livesync-service.ts index 3626943a75..80a4698c5a 100644 --- a/lib/services/livesync/ios-device-livesync-service.ts +++ b/lib/services/livesync/ios-device-livesync-service.ts @@ -6,7 +6,6 @@ import { DeviceLiveSyncServiceBase } from "./device-livesync-service-base"; let currentPageReloadId = 0; export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implements INativeScriptDeviceLiveSyncService { - private static BACKEND_PORT = 18181; private socket: net.Socket; private device: Mobile.IiOSDevice; @@ -14,28 +13,32 @@ export class IOSDeviceLiveSyncService extends DeviceLiveSyncServiceBase implemen private $iOSSocketRequestExecutor: IiOSSocketRequestExecutor, private $iOSNotification: IiOSNotification, private $iOSEmulatorServices: Mobile.IiOSSimulatorService, + private $iOSDebuggerPortService: IIOSDebuggerPortService, private $logger: ILogger, private $fs: IFileSystem, private $processService: IProcessService, protected $platformsData: IPlatformsData) { - super($platformsData); - this.device = _device; + super($platformsData); + this.$iOSDebuggerPortService.attachToDebuggerPortFoundEvent(_device); + this.device = _device; } - private async setupSocketIfNeeded(projectId: string): Promise { + private async setupSocketIfNeeded(appId: string): Promise { if (this.socket) { return true; } if (this.device.isEmulator) { - await this.$iOSEmulatorServices.postDarwinNotification(this.$iOSNotification.getAttachRequest(projectId)); - this.socket = await this.$iOSEmulatorServices.connectToPort({ port: IOSDeviceLiveSyncService.BACKEND_PORT }); + await this.$iOSEmulatorServices.postDarwinNotification(this.$iOSNotification.getAttachRequest(appId, this.device.deviceInfo.identifier), this.device.deviceInfo.identifier); + const port = await this.$iOSDebuggerPortService.getPort({ deviceId: this.device.deviceInfo.identifier, appId }); + this.socket = await this.$iOSEmulatorServices.connectToPort({ port }); if (!this.socket) { return false; } } else { - await this.$iOSSocketRequestExecutor.executeAttachRequest(this.device, constants.AWAIT_NOTIFICATION_TIMEOUT_SECONDS, projectId); - this.socket = await this.device.connectToPort(IOSDeviceLiveSyncService.BACKEND_PORT); + await this.$iOSSocketRequestExecutor.executeAttachRequest(this.device, constants.AWAIT_NOTIFICATION_TIMEOUT_SECONDS, appId); + const port = await this.$iOSDebuggerPortService.getPort({ deviceId: this.device.deviceInfo.identifier, appId }); + this.socket = await this.device.connectToPort(port); } this.attachEventHandlers(); diff --git a/lib/services/platform-project-service-base.ts b/lib/services/platform-project-service-base.ts index 6672516cb6..2b7dc9fec0 100644 --- a/lib/services/platform-project-service-base.ts +++ b/lib/services/platform-project-service-base.ts @@ -1,15 +1,21 @@ import { EventEmitter } from "events"; -export class PlatformProjectServiceBase extends EventEmitter implements IPlatformProjectServiceBase { +export abstract class PlatformProjectServiceBase extends EventEmitter implements IPlatformProjectServiceBase { constructor(protected $fs: IFileSystem, protected $projectDataService: IProjectDataService) { super(); } + protected abstract getPlatformData(projectData: IProjectData): IPlatformData; + public getPluginPlatformsFolderPath(pluginData: IPluginData, platform: string): string { return pluginData.pluginPlatformsFolderPath(platform); } + public getFrameworkVersion(projectData: IProjectData): string { + return this.$projectDataService.getNSValue(projectData.projectDir, this.getPlatformData(projectData).frameworkPackageName).version; + } + protected getAllNativeLibrariesForPlugin(pluginData: IPluginData, platform: string, filter: (fileName: string, _pluginPlatformsFolderPath: string) => boolean): string[] { const pluginPlatformsFolderPath = this.getPluginPlatformsFolderPath(pluginData, platform); let nativeLibraries: string[] = []; @@ -23,8 +29,4 @@ export class PlatformProjectServiceBase extends EventEmitter implements IPlatfor return nativeLibraries; } - - protected getFrameworkVersion(runtimePackageName: string, projectDir: string): string { - return this.$projectDataService.getNSValue(projectDir, runtimePackageName).version; - } } diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 2d06367bd2..4e6135fd9f 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -40,7 +40,7 @@ "@types/color": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/color/-/color-3.0.0.tgz", - "integrity": "sha512-5qqtNia+m2I0/85+pd2YzAXaTyKO8j+svirO5aN+XaQJ5+eZ8nx0jPtEWZLxCi50xwYsX10xUHetFzfb1WEs4Q==", + "integrity": "sha1-QPimvy/YbpaYdrM5qDfY/xsKbjA=", "dev": true, "requires": { "@types/color-convert": "1.9.0" @@ -49,7 +49,7 @@ "@types/color-convert": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-1.9.0.tgz", - "integrity": "sha512-OKGEfULrvSL2VRbkl/gnjjgbbF7ycIlpSsX7Nkab4MOWi5XxmgBYvuiQ7lcCFY5cPDz7MUNaKgxte2VRmtr4Fg==", + "integrity": "sha1-v6ggPkHnxlRx6YQdfjBqfNi1Fy0=", "dev": true, "requires": { "@types/color-name": "1.1.0" @@ -58,7 +58,7 @@ "@types/color-name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.0.tgz", - "integrity": "sha512-gZ/Rb+MFXF0pXSEQxdRoPMm5jeO3TycjOdvbpbcpHX/B+n9AqaHFe5q6Ga9CsZ7ir/UgIWPfrBzUzn3F19VH/w==", + "integrity": "sha1-km929+ZvScxZrYgLsVsDCrvwtm0=", "dev": true }, "@types/events": { @@ -91,7 +91,7 @@ "@types/ora": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@types/ora/-/ora-1.3.3.tgz", - "integrity": "sha512-XaSVRyCfnGq1xGlb6iuoxnomMXPIlZnvIIkKiGNMTCeVOg7G1Si+FA9N1lPrykPEfiRHwbuZXuTCSoYcHyjcdg==", + "integrity": "sha1-0xhkGMPPN6gxeZs3a+ykqOG/yi0=", "dev": true, "requires": { "@types/node": "6.0.61" @@ -153,7 +153,7 @@ "@types/xml2js": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.2.tgz", - "integrity": "sha512-8aKUBSj3oGcnuiBmDLm3BIk09RYg01mz9HlQ2u4aS17oJ25DxjQrEUVGFSBVNOfM45pQW4OjcBPplq6r/exJdA==", + "integrity": "sha1-pLhLOHn/1HEJU/2Syr/emopOhFY=", "dev": true, "requires": { "@types/node": "6.0.61" @@ -272,7 +272,7 @@ "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=" }, "array-find-index": { "version": "1.0.2", @@ -766,7 +766,7 @@ "color": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", - "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", + "integrity": "sha1-2SC0Mo1TSjrIKV1o971LpsQnvpo=", "requires": { "color-convert": "1.9.1", "color-string": "1.5.2" @@ -2155,7 +2155,7 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", "requires": { - "natives": "1.1.3" + "natives": "1.1.4" } } } @@ -2868,9 +2868,9 @@ } }, "ios-sim-portable": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ios-sim-portable/-/ios-sim-portable-3.3.3.tgz", - "integrity": "sha512-2RJMB865NQbes1tcGxbjMVfEZhXb9ACaGyDindYUcBVWGDz6bep/Z00HbBC+6VpRmnce6hMvmL/EyUnFdml7mg==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/ios-sim-portable/-/ios-sim-portable-3.4.1.tgz", + "integrity": "sha512-5zouPe8hjN6tLCwoxYXT4S1l2lY0aMZAXOGK9L8pPF8nXtanNKbVxpCrEszKcCbGzNQ/WyY/diJXTNL1ZO6HZg==", "requires": { "bplist-parser": "https://github.com/telerik/node-bplist-parser/tarball/master", "colors": "0.6.2", @@ -3676,7 +3676,7 @@ "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + "integrity": "sha1-Ms2eXGRVO9WNGaVor0Uqz/BJgbE=" }, "mime-db": { "version": "1.33.0", @@ -3807,9 +3807,9 @@ "optional": true }, "natives": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.3.tgz", - "integrity": "sha512-BZGSYV4YOLxzoTK73l0/s/0sH9l8SHs2ocReMH1f8JYSh5FUWu4ZrKCpJdRkWXV6HFR/pZDz7bwWOVAY07q77g==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.4.tgz", + "integrity": "sha512-Q29yeg9aFKwhLVdkTAejM/HvYG0Y1Am1+HUkFQGn5k2j8GS+v60TVmZh6nujpEAj/qql+wGUrlryO8bF+b1jEg==" }, "nativescript-doctor": { "version": "1.0.0", @@ -5668,7 +5668,7 @@ "xhr": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.4.1.tgz", - "integrity": "sha512-pAIU5vBr9Hiy5cpFIbPnwf0C18ZF86DBsZKrlsf87N5De/JbA6RJ83UP/cv+aljl4S40iRVMqP4pr4sF9Dnj0A==", + "integrity": "sha1-upgsztIFrl7sOHFprJ3HfKSFPTg=", "requires": { "global": "4.3.2", "is-function": "1.0.1", @@ -5684,7 +5684,7 @@ "xml2js": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", - "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "integrity": "sha1-aGwg8hMgnpSr8NG88e+qKRx4J6c=", "requires": { "sax": "1.2.4", "xmlbuilder": "9.0.7" diff --git a/package.json b/package.json index 46dc03553d..33941e7c0b 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "inquirer": "0.9.0", "ios-device-lib": "0.4.11", "ios-mobileprovision-finder": "1.0.10", - "ios-sim-portable": "3.3.3", + "ios-sim-portable": "3.4.1", "jimp": "0.2.28", "lockfile": "1.0.3", "lodash": "4.13.1", diff --git a/test/services/ios-debug-service.ts b/test/services/ios-debug-service.ts index ca0cc6efca..06ddfb74ad 100644 --- a/test/services/ios-debug-service.ts +++ b/test/services/ios-debug-service.ts @@ -14,6 +14,7 @@ class IOSDebugServiceInheritor extends IOSDebugService { $logger: ILogger, $errors: IErrors, $npmInstallationManager: INpmInstallationManager, + $iOSDebuggerPortService: IIOSDebuggerPortService, $iOSNotification: IiOSNotification, $iOSSocketRequestExecutor: IiOSSocketRequestExecutor, $processService: IProcessService, @@ -21,7 +22,7 @@ class IOSDebugServiceInheritor extends IOSDebugService { $net: INet, $projectDataService: IProjectDataService) { super({}, $devicesService, $platformService, $iOSEmulatorServices, $childProcess, $hostInfo, $logger, $errors, - $npmInstallationManager, $iOSNotification, $iOSSocketRequestExecutor, $processService, $socketProxyFactory, $net, $projectDataService); + $npmInstallationManager, $iOSDebuggerPortService, $iOSNotification, $iOSSocketRequestExecutor, $processService, $socketProxyFactory, $projectDataService); } public getChromeDebugUrl(debugOptions: IDebugOptions, port: number): string { @@ -56,6 +57,7 @@ const createTestInjector = (): IInjector => { }); testInjector.register("projectDataService", {}); + testInjector.register("iOSDebuggerPortService", {}); return testInjector; }; diff --git a/test/services/ios-debugger-port-service.ts b/test/services/ios-debugger-port-service.ts new file mode 100644 index 0000000000..fda70f07c9 --- /dev/null +++ b/test/services/ios-debugger-port-service.ts @@ -0,0 +1,162 @@ +import { assert } from "chai"; +import { CONNECTED_STATUS, DEBUGGER_PORT_FOUND_EVENT_NAME, DEVICE_LOG_EVENT_NAME } from "../../lib/common/constants"; +import { ErrorsStub, LoggerStub } from "../stubs"; +import { IOSDebuggerPortService } from "../../lib/services/ios-debugger-port-service"; +import { IOSLogParserService } from "../../lib/services/ios-log-parser-service"; +import { IOSSimulatorLogProvider } from "../../lib/common/mobile/ios/simulator/ios-simulator-log-provider"; +import { Yok } from "../../lib/common/yok"; +import { EventEmitter } from "events"; + +class DeviceApplicationManagerMock extends EventEmitter { } + +class IOSDeviceOperationsMock extends EventEmitter { + public startDeviceLog(deviceId: string): void { + // need this to be empty + } +} + +const appId = "org.nativescript.test"; +const deviceId = "fbece8e562ac63749a1018a9f1ea57614c5c953a"; +const device = { + deviceInfo: { + identifier: deviceId, + status: CONNECTED_STATUS + }, + applicationManager: new DeviceApplicationManagerMock() +}; + +function createTestInjector() { + const injector = new Yok(); + + injector.register("deviceLogProvider", { + setProjectNameForDevice: () => ({}) + }); + injector.register("errors", ErrorsStub); + injector.register("iOSDebuggerPortService", IOSDebuggerPortService); + injector.register("iosDeviceOperations", IOSDeviceOperationsMock); + injector.register("iOSLogParserService", IOSLogParserService); + injector.register("iOSProjectService", { + getFrameworkVersion: () => "4.1.0" + }); + injector.register("iOSSimResolver", { + iOSSim: () => ({}) + }); + injector.register("iOSSimulatorLogProvider", IOSSimulatorLogProvider); + injector.register("logger", LoggerStub); + injector.register("processService", { + attachToProcessExitSignals: () => ({}) + }); + injector.register("projectData", { + projectName: "test", + projectId: appId + }); + + return injector; +} + +function getDebuggerPortMessage(port: number) { + return `NativeScript debugger has opened inspector socket on port ${port} for ${appId}.`; +} + +function getMultilineDebuggerPortMessage(port: number) { + return `2018-04-20 09:45:48.645149+0300 localhost locationd[1040]: Created Activity ID: 0x7b46b, Description: CL: notifyClientsWithData (Fallback) + 2018-04-20 09:45:48.645199+0300 localhost locationd[1040]: Created Activity ID: 0x7b46c, Description: CL: notifyClientsWithData (Fallback) + 2018-04-20 09:45:48.645234+0300 localhost locationd[1040]: Created Activity ID: 0x7b46d, Description: CL: notifyClientsWithData (Fallback) + 2018-04-20 09:45:48.645278+0300 localhost locationd[1040]: Created Activity ID: 0x7b46e, Description: CL: notifyClientsWithData (Fallback) + 2018-04-20 09:45:49.645448+0300 localhost locationd[1040]: Created Activity ID: 0x7b46f, Description: CL: notifyClientsWithData (Fallback) + 2018-04-20 09:45:49.645518+0300 localhost locationd[1040]: Created Activity ID: 0x7b490, Description: CL: notifyClientsWithData (Fallback) + 2018-04-20 09:45:49.645554+0300 localhost locationd[1040]: Created Activity ID: 0x7b491, Description: CL: notifyClientsWithData (Fallback) + 2018-04-20 09:45:49.645592+0300 localhost locationd[1040]: Created Activity ID: 0x7b492, Description: CL: notifyClientsWithData (Fallback) + 2018-04-20 09:45:49.645623+0300 localhost locationd[1040]: Created Activity ID: 0x7b493, Description: CL: notifyClientsWithData (Fallback) + 2018-04-20 09:45:49.645641+0300 localhost locationd[1040]: Created Activity ID: 0x7b494, Description: CL: notifyClientsWithData (Fallback) + 2018-04-20 09:45:50.647222+0300 localhost locationd[1040]: Created Activity ID: 0x7b495, Description: CL: notifyClientsWithData (Fallback) + 2018-04-20 09:45:50.647294+0300 localhost locationd[1040]: Created Activity ID: 0x7b496, Description: CL: notifyClientsWithData (Fallback) + 2018-04-20 09:45:50.647331+0300 localhost locationd[1040]: Created Activity ID: 0x7b497, Description: CL: notifyClientsWithData (Fallback) + 2018-04-20 09:45:50.647369+0300 localhost locationd[1040]: Created Activity ID: 0x7b498, Description: CL: notifyClientsWithData (Fallback) + 2018-04-20 09:45:50.647400+0300 localhost locationd[1040]: Created Activity ID: 0x7b499, Description: CL: notifyClientsWithData (Fallback) + 2018-04-20 09:45:50.647417+0300 localhost locationd[1040]: Created Activity ID: 0x7b49a, Description: CL: notifyClientsWithData (Fallback) + 2018-04-20 09:45:51.071718+0300 localhost securityuploadd[17963]: [com.apple.securityd:lifecycle] will exit when clean + 2018-04-20 09:45:51.256053+0300 localhost CoreSimulatorBridge[1046]: Pasteboard change listener callback port registered + 2018-04-20 09:45:51.260951+0300 localhost nglog[17917]: NativeScript debugger has opened inspector socket on port ${port} for ${appId}.`; +} + +describe("iOSDebuggerPortService", () => { + let injector: IInjector, iOSDebuggerPortService: IIOSDebuggerPortService, iosDeviceOperations: IIOSDeviceOperations; + + beforeEach(() => { + injector = createTestInjector(); + iOSDebuggerPortService = injector.resolve("iOSDebuggerPortService"); + iosDeviceOperations = injector.resolve("iosDeviceOperations"); + }); + + function emitDeviceLog(message: string) { + iosDeviceOperations.emit(DEVICE_LOG_EVENT_NAME, { + deviceId: device.deviceInfo.identifier, + message: message + }); + } + + function emitStartingIOSApplicationEvent() { + device.applicationManager.emit("STARTING_IOS_APPLICATION", { + appId: appId, + deviceId: device.deviceInfo.identifier + }); + } + + describe("getPort", () => { + const testCases = [ + { + name: `should return null when ${DEBUGGER_PORT_FOUND_EVENT_NAME} event is not emitted`, + emittedPort: null, + emitStartingIOSApplicationEvent: false + }, + { + name: `should return default port when ${DEBUGGER_PORT_FOUND_EVENT_NAME} event is emitted`, + emittedPort: 18181, + emitStartingIOSApplicationEvent: false + }, + { + name: `should return random port when ${DEBUGGER_PORT_FOUND_EVENT_NAME} event is emitted`, + emittedPort: 65432, + emitStartingIOSApplicationEvent: false + }, + { + name: `should return default port when ${DEBUGGER_PORT_FOUND_EVENT_NAME} and STARTING_IOS_APPLICATION events are emitted`, + emittedPort: 18181, + emitStartingIOSApplicationEvent: true + }, + { + name: `should return random port when ${DEBUGGER_PORT_FOUND_EVENT_NAME} and STARTING_IOS_APPLICATION events are emitted`, + emittedPort: 12345, + emitStartingIOSApplicationEvent: true + } + ]; + + _.each(testCases, testCase => { + it(testCase.name, async () => { + iOSDebuggerPortService.attachToDebuggerPortFoundEvent(device); + if (testCase.emitStartingIOSApplicationEvent) { + emitStartingIOSApplicationEvent(); + } + if (testCase.emittedPort) { + emitDeviceLog(getDebuggerPortMessage(testCase.emittedPort)); + } + + const port = await iOSDebuggerPortService.getPort({ deviceId: deviceId, appId: appId }); + assert.deepEqual(port, testCase.emittedPort); + }); + it(`${testCase.name} for multiline debugger port message.`, async () => { + iOSDebuggerPortService.attachToDebuggerPortFoundEvent(device); + if (testCase.emitStartingIOSApplicationEvent) { + emitStartingIOSApplicationEvent(); + } + if (testCase.emittedPort) { + emitDeviceLog(getMultilineDebuggerPortMessage(testCase.emittedPort)); + } + + const port = await iOSDebuggerPortService.getPort({ deviceId: deviceId, appId: appId }); + assert.deepEqual(port, testCase.emittedPort); + }); + }); + }); +}); diff --git a/test/services/ios-log-parser-service.ts b/test/services/ios-log-parser-service.ts new file mode 100644 index 0000000000..937f0c9470 --- /dev/null +++ b/test/services/ios-log-parser-service.ts @@ -0,0 +1,146 @@ +import { assert } from "chai"; +import { CONNECTED_STATUS, DEBUGGER_PORT_FOUND_EVENT_NAME, DEVICE_LOG_EVENT_NAME } from "../../lib/common/constants"; +import { IOSDeviceOperations } from "../../lib/common/mobile/ios/device/ios-device-operations"; +import { IOSLogParserService } from "../../lib/services/ios-log-parser-service"; +import { IOSSimulatorLogProvider } from "../../lib/common/mobile/ios/simulator/ios-simulator-log-provider"; +import { Yok } from "../../lib/common/yok"; + +const appId = "org.nativescript.test"; +const deviceId = "fbece8e562ac63749a1018a9f1ea57614c5c953a"; +const device = { + deviceInfo: { + identifier: deviceId, + status: CONNECTED_STATUS + } +}; + +function createTestInjector() { + const injector = new Yok(); + injector.register("deviceLogProvider", { + setProjectNameForDevice: () => ({}) + }); + + injector.register("iosDeviceOperations", IOSDeviceOperations); + injector.register("iOSLogParserService", IOSLogParserService); + injector.register("iOSProjectService", { + getFrameworkVersion: () => "4.1.0" + }); + injector.register("iOSSimResolver", () => ({})); + injector.register("iOSSimulatorLogProvider", IOSSimulatorLogProvider); + + injector.register("logger", { + trace: () => ({}) + }); + + injector.register("processService", { + attachToProcessExitSignals: () => ({}) + }); + injector.register("projectData", { + projectName: "test", + projectId: appId + }); + + return injector; +} + +function getDebuggerPortMessage(port: number) { + return `NativeScript debugger has opened inspector socket on port ${port} for ${appId}.`; +} + +describe("iOSLogParserService", () => { + let injector: IInjector, iOSLogParserService: IIOSLogParserService, iosDeviceOperations: IIOSDeviceOperations; + + beforeEach(() => { + injector = createTestInjector(); + iOSLogParserService = injector.resolve("iOSLogParserService"); + iosDeviceOperations = injector.resolve("iosDeviceOperations"); + iosDeviceOperations.startDeviceLog = () => ({}); + }); + + function emitDeviceLog(message: string) { + iosDeviceOperations.emit(DEVICE_LOG_EVENT_NAME, { + deviceId: device.deviceInfo.identifier, + message: message + }); + } + + function attachOnDebuggerFoundEvent(emittedMessagesCount: number): Promise { + const receivedData: IIOSDebuggerPortData[] = []; + + return new Promise((resolve, reject) => { + iOSLogParserService.on(DEBUGGER_PORT_FOUND_EVENT_NAME, (data: IIOSDebuggerPortData) => { + receivedData.push(data); + if (receivedData.length === emittedMessagesCount) { + resolve(receivedData); + } + }); + }); + } + + describe("startLookingForDebuggerPort", () => { + it(`should emit ${DEBUGGER_PORT_FOUND_EVENT_NAME} event`, async () => { + const emittedMessagesCount = 1; + const promise = attachOnDebuggerFoundEvent(emittedMessagesCount); + + iOSLogParserService.startParsingLog(device); + emitDeviceLog("test message"); + emitDeviceLog(getDebuggerPortMessage(18181)); + + const data = await promise; + + assert.deepEqual(data.length, emittedMessagesCount); + assert.deepEqual(data[0], { port: 18181, deviceId: deviceId, appId: appId }); + }); + it("should receive all log data when cache logs are emitted in case when default port is not available", async () => { + const emittedMessagesCount = 5; + const promise = attachOnDebuggerFoundEvent(emittedMessagesCount); + + iOSLogParserService.startParsingLog(device); + emitDeviceLog(getDebuggerPortMessage(18181)); + emitDeviceLog(getDebuggerPortMessage(18181)); + emitDeviceLog(getDebuggerPortMessage(18181)); + emitDeviceLog(getDebuggerPortMessage(18181)); + emitDeviceLog(getDebuggerPortMessage(64087)); + + const data = await promise; + + assert.deepEqual(data.length, emittedMessagesCount); + assert.deepEqual(data[0], { port: 18181, deviceId: deviceId, appId: appId }); + assert.deepEqual(data[1], { port: 18181, deviceId: deviceId, appId: appId }); + assert.deepEqual(data[2], { port: 18181, deviceId: deviceId, appId: appId }); + assert.deepEqual(data[3], { port: 18181, deviceId: deviceId, appId: appId }); + assert.deepEqual(data[4], { port: 64087, deviceId: deviceId, appId: appId }); + }); + it("should receive all log data when cache logs are emitted in case when default port is available", async () => { + const emittedMessagesCount = 5; + const promise = attachOnDebuggerFoundEvent(emittedMessagesCount); + + iOSLogParserService.startParsingLog(device); + emitDeviceLog(getDebuggerPortMessage(45898)); + emitDeviceLog(getDebuggerPortMessage(1809)); + emitDeviceLog(getDebuggerPortMessage(65072)); + emitDeviceLog(getDebuggerPortMessage(12345)); + emitDeviceLog(getDebuggerPortMessage(18181)); + + const data = await promise; + + assert.deepEqual(data.length, emittedMessagesCount); + assert.deepEqual(data[0], { port: 45898, deviceId: deviceId, appId: appId }); + assert.deepEqual(data[1], { port: 1809, deviceId: deviceId, appId: appId }); + assert.deepEqual(data[2], { port: 65072, deviceId: deviceId, appId: appId }); + assert.deepEqual(data[3], { port: 12345, deviceId: deviceId, appId: appId }); + assert.deepEqual(data[4], { port: 18181, deviceId: deviceId, appId: appId }); + }); + it(`should not receive ${DEBUGGER_PORT_FOUND_EVENT_NAME} event when debugger port message is not emitted`, () => { + let isDebuggedPortFound = false; + + iOSLogParserService.on(DEBUGGER_PORT_FOUND_EVENT_NAME, (data: IIOSDebuggerPortData) => isDebuggedPortFound = true); + + iOSLogParserService.startParsingLog(device); + emitDeviceLog("some test message"); + emitDeviceLog("another test message"); + + assert.isFalse(isDebuggedPortFound); + }); + }); +}); diff --git a/test/stubs.ts b/test/stubs.ts index 8a43f1eebd..5eb77f402d 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -408,6 +408,12 @@ export class PlatformProjectServiceStub extends EventEmitter implements IPlatfor async checkForChanges(changesInfo: IProjectChangesInfo, options: IProjectChangesOptions, projectData: IProjectData): Promise { // Nothing yet. } + getFrameworkVersion(projectData: IProjectData): string { + return ""; + } + getPluginPlatformsFolderPath(pluginData: IPluginData, platform: string): string { + return ""; + } } export class PlatformsDataStub extends EventEmitter implements IPlatformsData {