From 0b89d3efe4630feb3270babf6294857bac93bee5 Mon Sep 17 00:00:00 2001 From: Dimitar Kerezov Date: Wed, 23 Aug 2017 22:08:19 +0300 Subject: [PATCH] Expose enable/disable debugging (#3065) Expose enable/disable debugging when CLI is required as a library. Includes: * Unify `debug-livesync-service` and `livesync-service` into one class. Enable said class to attach a debugger at runtime during LiveSync * Remove redundant `debugLivesync` property from `$config` and its every usage * Remove redundant `debugStop` code in `android-debug-service` * Raise events in the event of inability to start/stop the app on iOS --- PublicAPI.md | 98 +++++++++ lib/bootstrap.ts | 1 - lib/commands/debug.ts | 30 +-- lib/commands/run.ts | 2 +- lib/common | 2 +- lib/config.ts | 1 - lib/constants.ts | 3 + lib/declarations.d.ts | 4 +- lib/definitions/debug.d.ts | 25 +-- .../emulator-platform-service.d.ts | 3 +- lib/definitions/livesync.d.ts | 99 +++++++-- lib/definitions/platform.d.ts | 7 +- lib/definitions/project.d.ts | 6 +- .../ios/socket-proxy-factory.ts | 5 +- lib/services/android-debug-service.ts | 67 ++---- lib/services/android-project-service.ts | 29 ++- lib/services/debug-service-base.ts | 7 +- lib/services/debug-service.ts | 51 +++-- lib/services/ios-debug-service.ts | 16 +- .../livesync/debug-livesync-service.ts | 79 ------- .../livesync/livesync-command-helper.ts | 18 +- lib/services/livesync/livesync-service.ts | 204 ++++++++++++++++-- lib/services/test-execution-service.ts | 33 ++- test/debug.ts | 37 +--- test/nativescript-cli-lib.ts | 2 +- test/services/android-debug-service.ts | 4 +- test/services/debug-service.ts | 78 +------ test/services/ios-debug-service.ts | 4 +- test/services/livesync-service.ts | 9 + test/stubs.ts | 8 + 30 files changed, 532 insertions(+), 400 deletions(-) delete mode 100644 lib/services/livesync/debug-livesync-service.ts diff --git a/PublicAPI.md b/PublicAPI.md index dce94a45d4..a2056851cc 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -552,6 +552,8 @@ After calling the method once, you can add new devices to the same LiveSync oper > NOTE: In case a consecutive call to `liveSync` method requires change in the pattern for watching files (i.e. `liveSyncData.syncAllFiles` option has changed), current watch operation will be stopped and a new one will be started. +> NOTE: In case `debugggingEnabled` is set to `true` in a deviceDescriptor, debugging will initially be enabled for that device and a debugger will be attached after a successful livesync operation. + * Definition ```TypeScript /** @@ -623,6 +625,92 @@ tns.liveSyncService.stopLiveSync(projectDir, deviceIdentifiers) }); ``` +### enableDebugging +Enables debugging during a LiveSync operation. This method will try to attach a debugger to the application. Note that `userInteractionNeeded` event may be raised. Additional details about the arguments can be seen [here](https://github.com/NativeScript/nativescript-cli/blob/master/lib/definitions/livesync.d.ts). + +* Definition +```TypeScript +/** +* Enables debugging for the specified devices +* @param {IEnableDebuggingDeviceOptions[]} deviceOpts Settings used for enabling debugging for each device. +* @param {IDebuggingAdditionalOptions} enableDebuggingOptions Settings used for enabling debugging. +* @returns {Promise[]} Array of promises for each device. +*/ +enableDebugging(deviceOpts: IEnableDebuggingDeviceOptions[], enableDebuggingOptions: IDebuggingAdditionalOptions): Promise[]; +``` + +* Usage +```JavaScript +const projectDir = "/tmp/myProject"; +const liveSyncData = { projectDir }; +const devices = [androidDeviceDescriptor, iOSDeviceDescriptor]; +tns.liveSyncService.liveSync(devices, liveSyncData) + .then(() => { + console.log("LiveSync operation started."); + devices.forEach(device => { + tns.liveSyncService.enableDebugging([{ + deviceIdentifier: device.identifier + }], { projectDir }); + }); + }); +``` + +### attachDebugger +Attaches a debugger to the specified device. Additional details about the argument can be seen [here](https://github.com/NativeScript/nativescript-cli/blob/master/lib/definitions/livesync.d.ts). + +* Definition +```TypeScript +/** +* Attaches a debugger to the specified device. +* @param {IAttachDebuggerOptions} settings Settings used for controling the attaching process. +* @returns {Promise} +*/ +attachDebugger(settings: IAttachDebuggerOptions): Promise; +``` + +* Usage +```JavaScript +tns.liveSyncService.on("userInteractionNeeded", data => { + console.log("Please restart the app manually"); + return tns.liveSyncService.attachDebugger(data); +}); +``` + +### disableDebugging +Disables debugging during a LiveSync operation. This method will try to detach a debugger from the application. Additional details about the arguments can be seen [here](https://github.com/NativeScript/nativescript-cli/blob/master/lib/definitions/livesync.d.ts). + +* Definition +```TypeScript +/** +* Disables debugging for the specified devices +* @param {IDisableDebuggingDeviceOptions[]} deviceOptions Settings used for disabling debugging for each device. +* @param {IDebuggingAdditionalOptions} debuggingAdditionalOptions Settings used for disabling debugging. +* @returns {Promise[]} Array of promises for each device. +*/ +disableDebugging(deviceOptions: IDisableDebuggingDeviceOptions[], debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise[]; +``` + +* Usage +```JavaScript +const projectDir = "/tmp/myProject"; +const liveSyncData = { projectDir }; +const devices = [androidDeviceDescriptor, iOSDeviceDescriptor]; +tns.liveSyncService.liveSync(devices, liveSyncData) + .then(() => { + console.log("LiveSync operation started."); + devices.forEach(device => { + tns.liveSyncService.enableDebugging([{ + deviceIdentifier: device.identifier + }], { projectDir }); + setTimeout(() => { + tns.liveSyncService.disableDebugging([{ + deviceIdentifier: device.identifier + }], { projectDir }); + }, 1000 * 30); + }); + }); +``` + ### getLiveSyncDeviceDescriptors Gives information for currently running LiveSync operation and parameters used to start it on each device. @@ -741,6 +829,16 @@ tns.liveSyncService.on("notify", data => { }); ``` +* userInteractionNeeded - raised whenever CLI needs to restart an application but cannot so the user has to restart it manually. The event is raised with an object, which can later be passed to `attachDebugger` method of `liveSyncService`: + +Example: +```JavaScript +tns.liveSyncService.on("userInteractionNeeded", data => { + console.log("Please restart the app manually"); + return tns.liveSyncService.attachDebugger(data); +}); +``` + ## How to add a new method to Public API CLI is designed as command line tool and when it is used as a library, it does not give you access to all of the methods. This is mainly implementation detail. Most of the CLI's code is created to work in command line, not as a library, so before adding method to public API, most probably it will require some modification. For example the `$options` injected module contains information about all `--` options passed on the terminal. When the CLI is used as a library, the options are not populated. Before adding method to public API, make sure its implementation does not rely on `$options`. diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 4beee8e2c0..96c6f24eda 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -106,7 +106,6 @@ $injector.requireCommand("platform|clean", "./commands/platform-clean"); $injector.requirePublicClass("liveSyncService", "./services/livesync/livesync-service"); $injector.require("liveSyncCommandHelper", "./services/livesync/livesync-command-helper"); -$injector.require("debugLiveSyncService", "./services/livesync/debug-livesync-service"); $injector.require("androidLiveSyncService", "./services/livesync/android-livesync-service"); $injector.require("iOSLiveSyncService", "./services/livesync/ios-livesync-service"); $injector.require("usbLiveSyncService", "./services/livesync/livesync-service"); // The name is used in https://github.com/NativeScript/nativescript-dev-typescript diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index cc04b09411..6ac49ad201 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -6,8 +6,8 @@ import { DebugCommandErrors } from "../constants"; export class DebugPlatformCommand implements ICommand { public allowedParameters: ICommandParameter[] = []; - constructor(private debugService: IPlatformDebugService, - private platform: string, + constructor(private platform: string, + private $debugService: IDebugService, protected $devicesService: Mobile.IDevicesService, protected $platformService: IPlatformService, protected $projectData: IProjectData, @@ -16,31 +16,29 @@ export class DebugPlatformCommand implements ICommand { protected $logger: ILogger, protected $errors: IErrors, private $debugDataService: IDebugDataService, - private $debugLiveSyncService: IDebugLiveSyncService, - private $config: IConfiguration, + private $liveSyncService: IDebugLiveSyncService, private $prompter: IPrompter, private $liveSyncCommandHelper: ILiveSyncCommandHelper) { } public async execute(args: string[]): Promise { - const debugOptions = this.$options; + const debugOptions = _.cloneDeep(this.$options.argv); let debugData = this.$debugDataService.createDebugData(this.$projectData, this.$options); await this.$platformService.trackProjectType(this.$projectData); - const selectedDeviceForDebug = await this.getDeviceForDebug(); debugData.deviceIdentifier = selectedDeviceForDebug.deviceInfo.identifier; if (this.$options.start) { - return this.$debugLiveSyncService.printDebugInformation(await this.debugService.debug(debugData, debugOptions)); + return this.$liveSyncService.printDebugInformation(await this.$debugService.debug(debugData, debugOptions)); } - this.$config.debugLivesync = true; - await this.$devicesService.detectCurrentlyAttachedDevices({ shouldReturnImmediateResult: false, platform: this.platform }); - await this.$liveSyncCommandHelper.executeLiveSyncOperation([selectedDeviceForDebug], this.$debugLiveSyncService, this.platform); + await this.$liveSyncCommandHelper.executeLiveSyncOperation([selectedDeviceForDebug], this.platform, { + [selectedDeviceForDebug.deviceInfo.identifier]: true + }); } public async getDeviceForDebug(): Promise { @@ -104,6 +102,10 @@ export class DebugPlatformCommand implements ICommand { this.$errors.fail(`Applications for platform ${this.platform} can not be built on this OS`); } + if (this.$options.release) { + this.$errors.fail("--release flag is not applicable to this command"); + } + const platformData = this.$platformsData.getPlatformData(this.platform, this.$projectData); const platformProjectService = platformData.platformProjectService; await platformProjectService.validate(this.$projectData); @@ -123,7 +125,7 @@ export class DebugIOSCommand implements ICommand { @cache() private get debugPlatformCommand(): DebugPlatformCommand { - return this.$injector.resolve(DebugPlatformCommand, { debugService: this.$iOSDebugService, platform: this.platform }); + return this.$injector.resolve(DebugPlatformCommand, { platform: this.platform }); } public allowedParameters: ICommandParameter[] = []; @@ -135,7 +137,6 @@ export class DebugIOSCommand implements ICommand { private $injector: IInjector, private $projectData: IProjectData, private $platformsData: IPlatformsData, - private $iOSDebugService: IDebugService, $iosDeviceOperations: IIOSDeviceOperations) { 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. @@ -166,7 +167,7 @@ export class DebugAndroidCommand implements ICommand { @cache() private get debugPlatformCommand(): DebugPlatformCommand { - return this.$injector.resolve(DebugPlatformCommand, { debugService: this.$androidDebugService, platform: this.platform }); + return this.$injector.resolve(DebugPlatformCommand, { platform: this.platform }); } public allowedParameters: ICommandParameter[] = []; @@ -177,8 +178,7 @@ export class DebugAndroidCommand implements ICommand { private $options: IOptions, private $injector: IInjector, private $projectData: IProjectData, - private $platformsData: IPlatformsData, - private $androidDebugService: IDebugService) { + private $platformsData: IPlatformsData) { this.$projectData.initializeProjectData(); } diff --git a/lib/commands/run.ts b/lib/commands/run.ts index 76a974a185..28b76c92de 100644 --- a/lib/commands/run.ts +++ b/lib/commands/run.ts @@ -59,7 +59,7 @@ export class RunCommandBase implements ICommand { await this.$devicesService.detectCurrentlyAttachedDevices({ shouldReturnImmediateResult: false, platform: this.platform }); let devices = this.$devicesService.getDeviceInstances(); devices = devices.filter(d => !this.platform || d.deviceInfo.platform.toLowerCase() === this.platform.toLowerCase()); - await this.$liveSyncCommandHelper.executeLiveSyncOperation(devices, this.$liveSyncService, this.platform); + await this.$liveSyncCommandHelper.executeLiveSyncOperation(devices, this.platform); } } diff --git a/lib/common b/lib/common index 2287fd9efa..ef87c15950 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 2287fd9efa56ee985d720d49f8aef4e7cd362da2 +Subproject commit ef87c15950c43c8ebf6c217f3ec192cf9e18af94 diff --git a/lib/config.ts b/lib/config.ts index a3b9d77e49..52265023cb 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -8,7 +8,6 @@ export class Configuration extends ConfigBase implements IConfiguration { // Use TYPESCRIPT_COMPILER_OPTIONS = {}; ANDROID_DEBUG_UI: string = null; USE_POD_SANDBOX: boolean = false; - debugLivesync: boolean = false; /*don't require logger and everything that has logger as dependency in config.js due to cyclic dependency*/ constructor(protected $fs: IFileSystem) { diff --git a/lib/constants.ts b/lib/constants.ts index 0b0d046507..2ce856a677 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -85,6 +85,8 @@ export const ANGULAR_NAME = "angular"; export const TYPESCRIPT_NAME = "typescript"; export const BUILD_OUTPUT_EVENT_NAME = "buildOutput"; export const CONNECTION_ERROR_EVENT_NAME = "connectionError"; +export const USER_INTERACTION_NEEDED_EVENT_NAME = "userInteractionNeeded"; +export const DEBUGGER_ATTACHED_EVENT_NAME = "debuggerAttached"; export const VERSION_STRING = "version"; export const INSPECTOR_CACHE_DIRNAME = "ios-inspector"; export const POST_INSTALL_COMMAND_NAME = "post-install-cli"; @@ -92,6 +94,7 @@ export const POST_INSTALL_COMMAND_NAME = "post-install-cli"; export class DebugCommandErrors { public static UNABLE_TO_USE_FOR_DEVICE_AND_EMULATOR = "The options --for-device and --emulator cannot be used simultaneously. Please use only one of them."; public static NO_DEVICES_EMULATORS_FOUND_FOR_OPTIONS = "Unable to find device or emulator for specified options."; + public static UNSUPPORTED_DEVICE_OS_FOR_DEBUGGING = "Unsupported device OS for debugging"; } export const enum NativePlatformStatus { diff --git a/lib/declarations.d.ts b/lib/declarations.d.ts index 9c19342437..f26c55ba52 100644 --- a/lib/declarations.d.ts +++ b/lib/declarations.d.ts @@ -311,7 +311,6 @@ interface IStaticConfig extends Config.IStaticConfig { } interface IConfiguration extends Config.IConfig { ANDROID_DEBUG_UI: string; USE_POD_SANDBOX: boolean; - debugLivesync: boolean; } interface IApplicationPackage { @@ -405,8 +404,7 @@ interface IDeviceEmulator extends IEmulator, IDeviceIdentifier { } interface IRunPlatformOptions extends IJustLaunch, IDeviceEmulator { } -interface IDeployPlatformOptions extends IAndroidReleaseOptions, IPlatformTemplate, IRelease, IClean, IDeviceEmulator, IProvision, ITeamIdentifier { - projectDir: string; +interface IDeployPlatformOptions extends IAndroidReleaseOptions, IPlatformTemplate, IRelease, IClean, IDeviceEmulator, IProvision, ITeamIdentifier, IProjectDir { forceInstall?: boolean; } diff --git a/lib/definitions/debug.d.ts b/lib/definitions/debug.d.ts index 50bd31b886..37640c0848 100644 --- a/lib/definitions/debug.d.ts +++ b/lib/definitions/debug.d.ts @@ -1,12 +1,7 @@ /** * Describes information for starting debug process. */ -interface IDebugData { - /** - * Id of the device on which the debug process will be started. - */ - deviceIdentifier: string; - +interface IDebugData extends Mobile.IDeviceIdentifier { /** * Application identifier of the app that it will be debugged. */ @@ -115,14 +110,19 @@ interface IDebugServiceBase extends NodeJS.EventEmitter { debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise; } -interface IDebugService { - getDebugService(device: Mobile.IDevice): IPlatformDebugService; +interface IDebugService extends IDebugServiceBase { + /** + * Stops debug operation for a specific device. + * @param {string} deviceIdentifier Identifier of the device fo which debugging will be stopped. + * @returns {Promise} + */ + debugStop(deviceIdentifier: string): Promise; } /** * Describes actions required for debugging on specific platform (Android or iOS). */ -interface IPlatformDebugService extends IDebugServiceBase { +interface IPlatformDebugService extends IDebugServiceBase, IPlatform { /** * Starts debug operation. * @param {IDebugData} debugData Describes information for device and application that will be debugged. @@ -135,10 +135,5 @@ interface IPlatformDebugService extends IDebugServiceBase { * Stops debug operation. * @returns {Promise} */ - debugStop(): Promise - - /** - * Mobile platform of the device - Android or iOS. - */ - platform: string; + debugStop(): Promise; } diff --git a/lib/definitions/emulator-platform-service.d.ts b/lib/definitions/emulator-platform-service.d.ts index 259bbabbb2..e088a2efc8 100644 --- a/lib/definitions/emulator-platform-service.d.ts +++ b/lib/definitions/emulator-platform-service.d.ts @@ -1,7 +1,6 @@ -interface IEmulatorInfo { +interface IEmulatorInfo extends IPlatform { name: string; version: string; - platform: string; id: string; type: string; isRunning?: boolean; diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 4216a9bbeb..1ba0230268 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -68,10 +68,19 @@ interface ILiveSyncProcessInfo { currentSyncAction: Promise; } +interface IOptionalOutputPath { + /** + * Path where the build result is located (directory containing .ipa, .apk or .zip). + * This is required for initial checks where LiveSync will skip the rebuild in case there's already a build result and no change requiring rebuild is made since then. + * In case it is not passed, the default output for local builds will be used. + */ + outputPath?: string; +} + /** * Describes information for LiveSync on a device. */ -interface ILiveSyncDeviceInfo { +interface ILiveSyncDeviceInfo extends IOptionalOutputPath { /** * Device identifier. */ @@ -84,16 +93,14 @@ interface ILiveSyncDeviceInfo { buildAction: () => Promise; /** - * Path where the build result is located (directory containing .ipa, .apk or .zip). - * This is required for initial checks where LiveSync will skip the rebuild in case there's already a build result and no change requiring rebuild is made since then. - * In case it is not passed, the default output for local builds will be used. + * Whether to skip preparing the native platform. */ - outputPath?: string; + skipNativePrepare?: boolean; /** - * Whether to skip preparing the native platform. + * Whether debugging has been enabled for this device or not */ - skipNativePrepare?: boolean; + debugggingEnabled?: boolean; /** * Describes options specific for each platform, like provision for iOS, target sdk for Android, etc. @@ -104,12 +111,7 @@ interface ILiveSyncDeviceInfo { /** * Describes a LiveSync operation. */ -interface ILiveSyncInfo { - /** - * Directory of the project that will be synced. - */ - projectDir: string; - +interface ILiveSyncInfo extends IProjectDir, IOptionalDebuggingOptions { /** * Defines if the watcher should be skipped. If not passed, fs.Watcher will be started. */ @@ -135,9 +137,11 @@ interface ILiveSyncInfo { interface ILatestAppPackageInstalledSettings extends IDictionary> { /* empty */ } -interface ILiveSyncBuildInfo { - platform: string; +interface IIsEmulator { isEmulator: boolean; +} + +interface ILiveSyncBuildInfo extends IIsEmulator, IPlatform { pathToBuildItem: string; } @@ -206,6 +210,67 @@ interface IDebugLiveSyncService extends ILiveSyncService { * @returns {void} */ printDebugInformation(information: string): void; + + /** + * Enables debugging for the specified devices + * @param {IEnableDebuggingDeviceOptions[]} deviceOpts Settings used for enabling debugging for each device. + * @param {IDebuggingAdditionalOptions} enableDebuggingOptions Settings used for enabling debugging. + * @returns {Promise[]} Array of promises for each device. + */ + enableDebugging(deviceOpts: IEnableDebuggingDeviceOptions[], enableDebuggingOptions: IDebuggingAdditionalOptions): Promise[]; + + /** + * Disables debugging for the specified devices + * @param {IDisableDebuggingDeviceOptions[]} deviceOptions Settings used for disabling debugging for each device. + * @param {IDebuggingAdditionalOptions} debuggingAdditionalOptions Settings used for disabling debugging. + * @returns {Promise[]} Array of promises for each device. + */ + disableDebugging(deviceOptions: IDisableDebuggingDeviceOptions[], debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise[]; + + /** + * Attaches a debugger to the specified device. + * @param {IAttachDebuggerOptions} settings Settings used for controling the attaching process. + * @returns {Promise} + */ + attachDebugger(settings: IAttachDebuggerOptions): Promise; +} + +/** + * Describes additional debugging settings. + */ +interface IDebuggingAdditionalOptions extends IProjectDir { } + +/** + * Describes settings used when disabling debugging. + */ +interface IDisableDebuggingDeviceOptions extends Mobile.IDeviceIdentifier { } + +interface IOptionalDebuggingOptions { + /** + * Optional debug options - can be used to control the start of a debug process. + */ + debugOptions?: IDebugOptions; +} + +/** + * Describes settings used when enabling debugging. + */ +interface IEnableDebuggingDeviceOptions extends Mobile.IDeviceIdentifier, IOptionalDebuggingOptions { } + +/** + * Describes settings passed to livesync service in order to control event emitting during refresh application. + */ +interface IShouldSkipEmitLiveSyncNotification { + /** + * Whether to skip emitting an event during refresh. Default is false. + */ + shouldSkipEmitLiveSyncNotification: boolean; +} + +/** + * Describes settings used for attaching a debugger. + */ +interface IAttachDebuggerOptions extends IDebuggingAdditionalOptions, IEnableDebuggingDeviceOptions, IIsEmulator, IPlatform, IOptionalOutputPath { } interface ILiveSyncWatchInfo { @@ -284,10 +349,10 @@ interface ILiveSyncCommandHelper { /** * Method sets up configuration, before calling livesync and expects that devices are already discovered. * @param {Mobile.IDevice[]} devices List of discovered devices - * @param {ILiveSyncService} liveSyncService Service expected to do the actual livesyncing * @param {string} platform The platform for which the livesync will be ran + * @param {IDictionary} deviceDebugMap @optional A map representing devices which have debugging enabled initially. * @returns {Promise} */ - executeLiveSyncOperation(devices: Mobile.IDevice[], liveSyncService: ILiveSyncService, platform: string): Promise; + executeLiveSyncOperation(devices: Mobile.IDevice[], platform: string, deviceDebugMap?: IDictionary): Promise; getPlatformsForOperation(platform: string): string[]; } diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index 3c5e8cd5d1..535680ba60 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -233,17 +233,12 @@ interface IPlatformSpecificData extends IProvision, ITeamIdentifier { /** * Describes information that will be tracked for specific action related for platforms - build, livesync, etc. */ -interface ITrackPlatformAction { +interface ITrackPlatformAction extends IPlatform { /** * Name of the action. */ action: string; - /** - * Platform for which the action will be executed. - */ - platform: string; - /** * Defines if the action is for device or emulator. */ diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index f8d4cbd076..e88532323e 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -53,8 +53,7 @@ interface IProjectService { isValidNativeScriptProject(pathToProject?: string): boolean; } -interface IProjectData { - projectDir: string; +interface IProjectData extends IProjectDir { projectName: string; platformsDir: string; projectFilePath: string; @@ -136,8 +135,7 @@ interface INativePrepare { skipNativePrepare: boolean; } -interface IBuildConfig extends IAndroidBuildOptionsSettings, IiOSBuildConfig { - projectDir: string; +interface IBuildConfig extends IAndroidBuildOptionsSettings, IiOSBuildConfig, IProjectDir { clean?: boolean; architectures?: string[]; buildOutputStdio?: string; diff --git a/lib/device-sockets/ios/socket-proxy-factory.ts b/lib/device-sockets/ios/socket-proxy-factory.ts index 9a2cfb4022..7cace33215 100644 --- a/lib/device-sockets/ios/socket-proxy-factory.ts +++ b/lib/device-sockets/ios/socket-proxy-factory.ts @@ -9,7 +9,6 @@ import temp = require("temp"); export class SocketProxyFactory extends EventEmitter implements ISocketProxyFactory { constructor(private $logger: ILogger, private $errors: IErrors, - private $config: IConfiguration, private $options: IOptions, private $net: INet) { super(); @@ -29,7 +28,7 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact frontendSocket.on("end", () => { this.$logger.info('Frontend socket closed!'); - if (!(this.$config.debugLivesync && this.$options.watch)) { + if (!this.$options.watch) { process.exit(0); } }); @@ -39,7 +38,7 @@ export class SocketProxyFactory extends EventEmitter implements ISocketProxyFact backendSocket.on("end", () => { this.$logger.info("Backend socket closed!"); - if (!(this.$config.debugLivesync && this.$options.watch)) { + if (!this.$options.watch) { process.exit(0); } }); diff --git a/lib/services/android-debug-service.ts b/lib/services/android-debug-service.ts index ce08d73d4c..65b3cd25d7 100644 --- a/lib/services/android-debug-service.ts +++ b/lib/services/android-debug-service.ts @@ -1,34 +1,24 @@ import { sleep } from "../common/helpers"; -import { ChildProcess } from "child_process"; import { DebugServiceBase } from "./debug-service-base"; export class AndroidDebugService extends DebugServiceBase implements IPlatformDebugService { - private _device: Mobile.IAndroidDevice = null; - private _debuggerClientProcess: ChildProcess; - + private _packageName: string; public get platform() { return "android"; } - private get device(): Mobile.IAndroidDevice { - return this._device; - } - - private set device(newDevice) { - this._device = newDevice; - } - - constructor(protected $devicesService: Mobile.IDevicesService, + constructor(protected device: Mobile.IAndroidDevice, + protected $devicesService: Mobile.IDevicesService, private $errors: IErrors, private $logger: ILogger, - private $config: IConfiguration, private $androidDeviceDiscovery: Mobile.IDeviceDiscovery, private $androidProcessService: Mobile.IAndroidProcessService, private $net: INet) { - super($devicesService); + super(device, $devicesService); } public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + this._packageName = debugData.applicationIdentifier; return debugOptions.emulator ? this.debugOnEmulator(debugData, debugOptions) : this.debugOnDevice(debugData, debugOptions); @@ -36,17 +26,13 @@ export class AndroidDebugService extends DebugServiceBase implements IPlatformDe public async debugStart(debugData: IDebugData, debugOptions: IDebugOptions): Promise { await this.$devicesService.initialize({ platform: this.platform, deviceId: debugData.deviceIdentifier }); - let action = (device: Mobile.IAndroidDevice): Promise => { - this.device = device; - return this.debugStartCore(debugData.applicationIdentifier, debugOptions); - }; + const action = (device: Mobile.IAndroidDevice): Promise => this.debugStartCore(debugData.applicationIdentifier, debugOptions); await this.$devicesService.execute(action, this.getCanExecuteAction(debugData.deviceIdentifier)); } - public async debugStop(): Promise { - this.stopDebuggerClient(); - return; + public debugStop(): Promise { + return this.removePortForwarding(); } protected getChromeDebugUrl(debugOptions: IDebugOptions, port: number): string { @@ -65,6 +51,11 @@ export class AndroidDebugService extends DebugServiceBase implements IPlatformDe return this.debugOnDevice(debugData, debugOptions); } + private async removePortForwarding(packageName?: string): Promise { + const port = await this.getForwardedLocalDebugPortForPackageName(this.device.deviceInfo.identifier, packageName || this._packageName); + return this.device.adb.executeCommand(["forward", "--remove", `tcp:${port}`]); + } + private async getForwardedLocalDebugPortForPackageName(deviceId: string, packageName: string): Promise { let port = -1; let forwardsResult = await this.device.adb.executeCommand(["forward", "--list"]); @@ -100,24 +91,22 @@ export class AndroidDebugService extends DebugServiceBase implements IPlatformDe await this.$devicesService.initialize({ platform: this.platform, deviceId: debugData.deviceIdentifier }); - let action = (device: Mobile.IAndroidDevice): Promise => this.debugCore(device, packageFile, debugData.applicationIdentifier, debugOptions); + const action = (device: Mobile.IAndroidDevice): Promise => this.debugCore(device, packageFile, debugData.applicationIdentifier, debugOptions); const deviceActionResult = await this.$devicesService.execute(action, this.getCanExecuteAction(debugData.deviceIdentifier)); return deviceActionResult[0].result; } private async debugCore(device: Mobile.IAndroidDevice, packageFile: string, packageName: string, debugOptions: IDebugOptions): Promise { - this.device = device; - await this.printDebugPort(device.deviceInfo.identifier, packageName); if (debugOptions.start) { return await this.attachDebugger(device.deviceInfo.identifier, packageName, debugOptions); } else if (debugOptions.stop) { - await this.detachDebugger(packageName); + await this.removePortForwarding(); return null; } else { - await this.startAppWithDebugger(packageFile, packageName, debugOptions); + await this.debugStartCore(packageName, debugOptions); return await this.attachDebugger(device.deviceInfo.identifier, packageName, debugOptions); } } @@ -132,26 +121,11 @@ export class AndroidDebugService extends DebugServiceBase implements IPlatformDe this.$errors.failWithoutHelp(`The application ${packageName} does not appear to be running on ${deviceId} or is not built with debugging enabled.`); } - const startDebuggerCommand = ["am", "broadcast", "-a", `\"${packageName}-debug\"`, "--ez", "enable", "true"]; - await this.device.adb.executeShellCommand(startDebuggerCommand); - const port = await this.getForwardedLocalDebugPortForPackageName(deviceId, packageName); return this.getChromeDebugUrl(debugOptions, port); } - private detachDebugger(packageName: string): Promise { - return this.device.adb.executeShellCommand(["am", "broadcast", "-a", `${packageName}-debug`, "--ez", "enable", "false"]); - } - - private async startAppWithDebugger(packageFile: string, packageName: string, debugOptions: IDebugOptions): Promise { - if (!debugOptions.emulator && !this.$config.debugLivesync) { - await this.device.applicationManager.uninstallApplication(packageName); - await this.device.applicationManager.installApplication(packageFile); - } - await this.debugStartCore(packageName, debugOptions); - } - private async debugStartCore(packageName: string, debugOptions: IDebugOptions): Promise { // Arguments passed to executeShellCommand must be in array ([]), but it turned out adb shell "arg with intervals" still works correctly. // As we need to redirect output of a command on the device, keep using only one argument. @@ -198,13 +172,6 @@ export class AndroidDebugService extends DebugServiceBase implements IPlatformDe return !!_.find(debuggableApps, a => a.appIdentifier === appIdentifier); } - - private stopDebuggerClient(): void { - if (this._debuggerClientProcess) { - this._debuggerClientProcess.kill(); - this._debuggerClientProcess = null; - } - } } -$injector.register("androidDebugService", AndroidDebugService); +$injector.register("androidDebugService", AndroidDebugService, false); diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index 8b7c53afea..4406bf8064 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -35,7 +35,6 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject private $injector: IInjector, private $pluginVariablesService: IPluginVariablesService, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - private $config: IConfiguration, private $npm: INodePackageManager) { super($fs, $projectDataService); this._androidProjectPropertiesManagers = Object.create(null); @@ -408,25 +407,23 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject } public async beforePrepareAllPlugins(projectData: IProjectData, dependencies?: IDependencyData[]): Promise { - if (!this.$config.debugLivesync) { - if (dependencies) { - let platformDir = path.join(projectData.platformsDir, "android"); - let buildDir = path.join(platformDir, "build-tools"); - let checkV8dependants = path.join(buildDir, "check-v8-dependants.js"); - if (this.$fs.exists(checkV8dependants)) { - let stringifiedDependencies = JSON.stringify(dependencies); - try { - await this.spawn('node', [checkV8dependants, stringifiedDependencies, projectData.platformsDir], { stdio: "inherit" }); - } catch (e) { - this.$logger.info("Checking for dependants on v8 public API failed. This is likely caused because of cyclic production dependencies. Error code: " + e.code + "\nMore information: https://github.com/NativeScript/nativescript-cli/issues/2561"); - } + if (dependencies) { + let platformDir = path.join(projectData.platformsDir, "android"); + let buildDir = path.join(platformDir, "build-tools"); + let checkV8dependants = path.join(buildDir, "check-v8-dependants.js"); + if (this.$fs.exists(checkV8dependants)) { + let stringifiedDependencies = JSON.stringify(dependencies); + try { + await this.spawn('node', [checkV8dependants, stringifiedDependencies, projectData.platformsDir], { stdio: "inherit" }); + } catch (e) { + this.$logger.info("Checking for dependants on v8 public API failed. This is likely caused because of cyclic production dependencies. Error code: " + e.code + "\nMore information: https://github.com/NativeScript/nativescript-cli/issues/2561"); } } + } - let projectRoot = this.getPlatformData(projectData).projectRoot; + let projectRoot = this.getPlatformData(projectData).projectRoot; - await this.cleanProject(projectRoot, projectData); - } + await this.cleanProject(projectRoot, projectData); } public stopServices(projectRoot: string): Promise { diff --git a/lib/services/debug-service-base.ts b/lib/services/debug-service-base.ts index 3429e18e35..054c8f8578 100644 --- a/lib/services/debug-service-base.ts +++ b/lib/services/debug-service-base.ts @@ -1,7 +1,12 @@ import { EventEmitter } from "events"; export abstract class DebugServiceBase extends EventEmitter implements IPlatformDebugService { - constructor(protected $devicesService: Mobile.IDevicesService) { super(); } + constructor( + protected device: Mobile.IDevice, + protected $devicesService: Mobile.IDevicesService + ) { + super(); + } public abstract get platform(): string; diff --git a/lib/services/debug-service.ts b/lib/services/debug-service.ts index 303177379b..ab7151baca 100644 --- a/lib/services/debug-service.ts +++ b/lib/services/debug-service.ts @@ -1,17 +1,17 @@ import { platform } from "os"; import { EventEmitter } from "events"; -import { CONNECTION_ERROR_EVENT_NAME } from "../constants"; +import { CONNECTION_ERROR_EVENT_NAME, DebugCommandErrors } from "../constants"; import { CONNECTED_STATUS } from "../common/constants"; export class DebugService extends EventEmitter implements IDebugService { + private _platformDebugServices: IDictionary; constructor(private $devicesService: Mobile.IDevicesService, - private $androidDebugService: IPlatformDebugService, - private $iOSDebugService: IPlatformDebugService, private $errors: IErrors, + private $injector: IInjector, private $hostInfo: IHostInfo, private $mobileHelper: Mobile.IMobileHelper) { super(); - this.attachConnectionErrorHandlers(); + this._platformDebugServices = {}; } public async debug(debugData: IDebugData, options: IDebugOptions): Promise { @@ -29,15 +29,12 @@ export class DebugService extends EventEmitter implements IDebugService { this.$errors.failWithoutHelp(`The application ${debugData.applicationIdentifier} is not installed on device with identifier ${debugData.deviceIdentifier}.`); } - const debugOptions: IDebugOptions = _.merge({}, options); - debugOptions.start = true; + const debugOptions: IDebugOptions = _.cloneDeep(options); // TODO: Check if app is running. // For now we can only check if app is running on Android. // After we find a way to check on iOS we should use it here. - const isAppRunning = true; let result: string; - debugOptions.chrome = true; const debugService = this.getDebugService(device); if (!debugService) { @@ -50,10 +47,6 @@ export class DebugService extends EventEmitter implements IDebugService { } if (this.$hostInfo.isWindows) { - if (!isAppRunning) { - this.$errors.failWithoutHelp(`Application ${debugData.applicationIdentifier} is not running. To be able to debug the application on Windows you must run it.`); - } - debugOptions.emulator = false; } else if (!this.$hostInfo.isDarwin) { this.$errors.failWithoutHelp(`Debugging on iOS devices is not supported for ${platform()} yet.`); @@ -67,19 +60,37 @@ export class DebugService extends EventEmitter implements IDebugService { return result; } - public getDebugService(device: Mobile.IDevice): IPlatformDebugService { - if (this.$mobileHelper.isiOSPlatform(device.deviceInfo.platform)) { - return this.$iOSDebugService; - } else if (this.$mobileHelper.isAndroidPlatform(device.deviceInfo.platform)) { - return this.$androidDebugService; + public debugStop(deviceIdentifier: string): Promise { + const debugService = this.getDebugServiceByIdentifier(deviceIdentifier); + return debugService.debugStop(); + } + + protected getDebugService(device: Mobile.IDevice): IPlatformDebugService { + if (!this._platformDebugServices[device.deviceInfo.identifier]) { + const platform = device.deviceInfo.platform; + if (this.$mobileHelper.isiOSPlatform(platform)) { + this._platformDebugServices[device.deviceInfo.identifier] = this.$injector.resolve("iOSDebugService", { device }); + } else if (this.$mobileHelper.isAndroidPlatform(platform)) { + this._platformDebugServices[device.deviceInfo.identifier] = this.$injector.resolve("androidDebugService", { device }); + } else { + this.$errors.failWithoutHelp(DebugCommandErrors.UNSUPPORTED_DEVICE_OS_FOR_DEBUGGING); + } + + this.attachConnectionErrorHandlers(this._platformDebugServices[device.deviceInfo.identifier]); } + + return this._platformDebugServices[device.deviceInfo.identifier]; + } + + private getDebugServiceByIdentifier(deviceIdentifier: string): IPlatformDebugService { + const device = this.$devicesService.getDeviceByIdentifier(deviceIdentifier); + return this.getDebugService(device); } - private attachConnectionErrorHandlers() { + private attachConnectionErrorHandlers(platformDebugService: IPlatformDebugService) { let connectionErrorHandler = (e: Error) => this.emit(CONNECTION_ERROR_EVENT_NAME, e); connectionErrorHandler = connectionErrorHandler.bind(this); - this.$androidDebugService.on(CONNECTION_ERROR_EVENT_NAME, connectionErrorHandler); - this.$iOSDebugService.on(CONNECTION_ERROR_EVENT_NAME, connectionErrorHandler); + platformDebugService.on(CONNECTION_ERROR_EVENT_NAME, connectionErrorHandler); } } diff --git a/lib/services/ios-debug-service.ts b/lib/services/ios-debug-service.ts index bb14ef2db1..03fa2f651a 100644 --- a/lib/services/ios-debug-service.ts +++ b/lib/services/ios-debug-service.ts @@ -20,10 +20,12 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS private _childProcess: ChildProcess; private _socketProxy: any; - constructor(protected $devicesService: Mobile.IDevicesService, + constructor(protected device: Mobile.IDevice, + protected $devicesService: Mobile.IDevicesService, private $platformService: IPlatformService, private $iOSEmulatorServices: Mobile.IEmulatorPlatformServices, private $childProcess: IChildProcess, + private $hostInfo: IHostInfo, private $logger: ILogger, private $errors: IErrors, private $npmInstallationManager: INpmInstallationManager, @@ -31,7 +33,7 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS private $iOSSocketRequestExecutor: IiOSSocketRequestExecutor, private $processService: IProcessService, private $socketProxyFactory: ISocketProxyFactory) { - super($devicesService); + super(device, $devicesService); this.$processService.attachToProcessExitSignals(this, this.debugStop); this.$socketProxyFactory.on(CONNECTION_ERROR_EVENT_NAME, (e: Error) => this.emit(CONNECTION_ERROR_EVENT_NAME, e)); } @@ -40,7 +42,7 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS return "ios"; } - public async debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { + public debug(debugData: IDebugData, debugOptions: IDebugOptions): Promise { if (debugOptions.debugBrk && debugOptions.start) { this.$errors.failWithoutHelp("Expected exactly one of the --debug-brk or --start options."); } @@ -51,9 +53,9 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS if (debugOptions.emulator) { if (debugOptions.start) { - return await this.emulatorStart(debugData, debugOptions); + return this.emulatorStart(debugData, debugOptions); } else { - return await this.emulatorDebugBrk(debugData, debugOptions); + return this.emulatorDebugBrk(debugData, debugOptions); } } else { if (debugOptions.start) { @@ -200,7 +202,7 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS } private async wireDebuggerClient(debugData: IDebugData, debugOptions: IDebugOptions, device?: Mobile.IiOSDevice): Promise { - if (debugOptions.chrome) { + if (debugOptions.chrome || !this.$hostInfo.isDarwin) { this._socketProxy = await this.$socketProxyFactory.createWebSocketProxy(this.getSocketFactory(device)); return this.getChromeDebugUrl(debugOptions, this._socketProxy.options.port); @@ -237,4 +239,4 @@ export class IOSDebugService extends DebugServiceBase implements IPlatformDebugS } } -$injector.register("iOSDebugService", IOSDebugService); +$injector.register("iOSDebugService", IOSDebugService, false); diff --git a/lib/services/livesync/debug-livesync-service.ts b/lib/services/livesync/debug-livesync-service.ts deleted file mode 100644 index 568ba3a3ea..0000000000 --- a/lib/services/livesync/debug-livesync-service.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { EOL } from "os"; -import { LiveSyncService } from "./livesync-service"; - -export class DebugLiveSyncService extends LiveSyncService implements IDebugLiveSyncService { - - constructor(protected $platformService: IPlatformService, - $projectDataService: IProjectDataService, - protected $devicesService: Mobile.IDevicesService, - $mobileHelper: Mobile.IMobileHelper, - $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, - $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder, - protected $logger: ILogger, - $processService: IProcessService, - $hooksService: IHooksService, - $pluginsService: IPluginsService, - protected $injector: IInjector, - private $options: IOptions, - private $debugDataService: IDebugDataService, - private $projectData: IProjectData, - private $debugService: IDebugService, - private $config: IConfiguration) { - - super($platformService, - $projectDataService, - $devicesService, - $mobileHelper, - $devicePlatformsConstants, - $nodeModulesDependenciesBuilder, - $logger, - $processService, - $hooksService, - $pluginsService, - $injector); - } - - protected async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo): Promise { - const debugOptions = this.$options; - const deployOptions: IDeployPlatformOptions = { - clean: this.$options.clean, - device: this.$options.device, - emulator: this.$options.emulator, - platformTemplate: this.$options.platformTemplate, - projectDir: this.$options.path, - release: this.$options.release, - provision: this.$options.provision, - teamId: this.$options.teamId - }; - - let debugData = this.$debugDataService.createDebugData(this.$projectData, { device: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier }); - const debugService = this.$debugService.getDebugService(liveSyncResultInfo.deviceAppData.device); - - await this.$platformService.trackProjectType(this.$projectData); - - if (this.$options.start) { - return this.printDebugInformation(await debugService.debug(debugData, debugOptions)); - } - - const deviceAppData = liveSyncResultInfo.deviceAppData; - this.$config.debugLivesync = true; - - await debugService.debugStop(); - - let applicationId = deviceAppData.appIdentifier; - await deviceAppData.device.applicationManager.stopApplication(applicationId, projectData.projectName); - - const buildConfig: IBuildConfig = _.merge({ buildForDevice: !deviceAppData.device.isEmulator }, deployOptions); - debugData.pathToAppPackage = this.$platformService.lastOutputPath(debugService.platform, buildConfig, projectData); - - this.printDebugInformation(await debugService.debug(debugData, debugOptions)); - } - - public printDebugInformation(information: string): void { - if (!!information) { - this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${information}${EOL}`.cyan); - } - } -} - -$injector.register("debugLiveSyncService", DebugLiveSyncService); diff --git a/lib/services/livesync/livesync-command-helper.ts b/lib/services/livesync/livesync-command-helper.ts index 869c148160..fae89af3c9 100644 --- a/lib/services/livesync/livesync-command-helper.ts +++ b/lib/services/livesync/livesync-command-helper.ts @@ -1,9 +1,9 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { - constructor(protected $platformService: IPlatformService, - protected $projectData: IProjectData, - protected $options: IOptions, - protected $devicesService: Mobile.IDevicesService, + constructor(private $platformService: IPlatformService, + private $projectData: IProjectData, + private $options: IOptions, + private $liveSyncService: ILiveSyncService, private $iosDeviceOperations: IIOSDeviceOperations, private $mobileHelper: Mobile.IMobileHelper, private $platformsData: IPlatformsData, @@ -15,7 +15,7 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { return availablePlatforms; } - public async executeLiveSyncOperation(devices: Mobile.IDevice[], liveSyncService: ILiveSyncService, platform: string): Promise { + public async executeLiveSyncOperation(devices: Mobile.IDevice[], platform: string, deviceDebugMap?: IDictionary): Promise { if (!devices || !devices.length) { if (platform) { this.$errors.failWithoutHelp("Unable to find applicable devices to execute operation. Ensure connected devices are trusted and try again."); @@ -60,7 +60,8 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { await this.$platformService.buildPlatform(d.deviceInfo.platform, buildConfig, this.$projectData); const result = await this.$platformService.lastOutputPath(d.deviceInfo.platform, buildConfig, this.$projectData); return result; - } + }, + debugggingEnabled: deviceDebugMap && deviceDebugMap[d.deviceInfo.identifier] }; return info; @@ -70,10 +71,11 @@ export class LiveSyncCommandHelper implements ILiveSyncCommandHelper { projectDir: this.$projectData.projectDir, skipWatcher: !this.$options.watch, watchAllFiles: this.$options.syncAllFiles, - clean: this.$options.clean + clean: this.$options.clean, + debugOptions: this.$options }; - await liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); + await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); } diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index b8b0829979..7b0d9379d8 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -1,8 +1,10 @@ import * as path from "path"; import * as choki from "chokidar"; +import { parse } from "url"; +import { EOL } from "os"; import { EventEmitter } from "events"; import { hook } from "../../common/helpers"; -import { APP_FOLDER_NAME, PACKAGE_JSON_FILE_NAME, LiveSyncTrackActionNames } from "../../constants"; +import { APP_FOLDER_NAME, PACKAGE_JSON_FILE_NAME, LiveSyncTrackActionNames, USER_INTERACTION_NEEDED_EVENT_NAME, DEBUGGER_ATTACHED_EVENT_NAME } from "../../constants"; import { FileExtensions, DeviceTypes } from "../../common/constants"; const deviceDescriptorPrimaryKey = "identifier"; @@ -15,21 +17,24 @@ const LiveSyncEvents = { liveSyncNotification: "notify" }; -export class LiveSyncService extends EventEmitter implements ILiveSyncService { +export class LiveSyncService extends EventEmitter implements IDebugLiveSyncService { // key is projectDir protected liveSyncProcessesInfo: IDictionary = {}; - constructor(protected $platformService: IPlatformService, + constructor(private $platformService: IPlatformService, private $projectDataService: IProjectDataService, - protected $devicesService: Mobile.IDevicesService, + private $devicesService: Mobile.IDevicesService, private $mobileHelper: Mobile.IMobileHelper, - protected $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder, - protected $logger: ILogger, + private $logger: ILogger, private $processService: IProcessService, private $hooksService: IHooksService, private $pluginsService: IPluginsService, - protected $injector: IInjector) { + private $debugService: IDebugService, + private $errors: IErrors, + private $debugDataService: IDebugDataService, + private $injector: IInjector) { super(); } @@ -96,7 +101,15 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { return currentDescriptors || []; } - protected async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo): Promise { + private async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string): Promise { + const deviceDescriptor = this.getDeviceDescriptor(liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, projectData.projectDir); + + return deviceDescriptor && deviceDescriptor.debugggingEnabled ? + this.refreshApplicationWithDebug(projectData, liveSyncResultInfo, debugOpts, outputPath) : + this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOpts, outputPath); + } + + private async refreshApplicationWithoutDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOpts?: IDebugOptions, outputPath?: string, settings?: IShouldSkipEmitLiveSyncNotification): Promise { const platformLiveSyncService = this.getLiveSyncService(liveSyncResultInfo.deviceAppData.platform); try { await platformLiveSyncService.refreshApplication(projectData, liveSyncResultInfo); @@ -104,12 +117,14 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { this.$logger.info(`Error while trying to start application ${projectData.projectId} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Error is: ${err.message || err}`); const msg = `Unable to start application ${projectData.projectId} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}. Try starting it manually.`; this.$logger.warn(msg); - this.emit(LiveSyncEvents.liveSyncNotification, { - projectDir: projectData.projectDir, - applicationIdentifier: projectData.projectId, - deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, - notification: msg - }); + if (!settings || !settings.shouldSkipEmitLiveSyncNotification) { + this.emit(LiveSyncEvents.liveSyncNotification, { + projectDir: projectData.projectDir, + applicationIdentifier: projectData.projectId, + deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, + notification: msg + }); + } } this.emit(LiveSyncEvents.liveSyncExecuted, { @@ -123,6 +138,161 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); } + private async refreshApplicationWithDebug(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, debugOptions: IDebugOptions, outputPath?: string): Promise { + await this.$platformService.trackProjectType(projectData); + + const deviceAppData = liveSyncResultInfo.deviceAppData; + + const deviceIdentifier = liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier; + await this.$debugService.debugStop(deviceIdentifier); + + let applicationId = deviceAppData.appIdentifier; + const attachDebuggerOptions: IAttachDebuggerOptions = { + platform: liveSyncResultInfo.deviceAppData.device.deviceInfo.platform, + isEmulator: liveSyncResultInfo.deviceAppData.device.isEmulator, + projectDir: projectData.projectDir, + deviceIdentifier, + debugOptions, + outputPath + }; + + try { + await deviceAppData.device.applicationManager.stopApplication(applicationId, projectData.projectName); + // Now that we've stopped the application we know it isn't started, so set debugOptions.start to false + // so that it doesn't default to true in attachDebugger method + debugOptions = debugOptions || {}; + debugOptions.start = false; + } catch (err) { + this.$logger.trace("Could not stop application during debug livesync. Will try to restart app instead.", err); + if ((err.message || err) === "Could not find developer disk image") { + await this.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, debugOptions, outputPath, { shouldSkipEmitLiveSyncNotification: true }); + this.emit(USER_INTERACTION_NEEDED_EVENT_NAME, attachDebuggerOptions); + return; + } else { + throw err; + } + } + + const deviceOption = { + deviceIdentifier: liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier, + debugOptions: debugOptions, + }; + + return this.enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption, { projectDir: projectData.projectDir }); + } + + public async attachDebugger(settings: IAttachDebuggerOptions): Promise { + // Default values + if (settings.debugOptions) { + settings.debugOptions.chrome = settings.debugOptions.chrome === undefined ? true : settings.debugOptions.chrome; + settings.debugOptions.start = settings.debugOptions.start === undefined ? true : settings.debugOptions.start; + } else { + settings.debugOptions = { + chrome: true, + start: true + }; + } + + const projectData = this.$projectDataService.getProjectData(settings.projectDir); + let debugData = this.$debugDataService.createDebugData(projectData, { device: settings.deviceIdentifier }); + + // Of the properties below only `buildForDevice` and `release` are currently used. + // Leaving the others with placeholder values so that they may not be forgotten in future implementations. + const buildConfig: IBuildConfig = { + buildForDevice: !settings.isEmulator, + release: false, + device: settings.deviceIdentifier, + provision: null, + teamId: null, + projectDir: settings.projectDir + }; + debugData.pathToAppPackage = this.$platformService.lastOutputPath(settings.platform, buildConfig, projectData, settings.outputPath); + + this.printDebugInformation(await this.$debugService.debug(debugData, settings.debugOptions)); + } + + public printDebugInformation(information: string): void { + if (!!information) { + const wsQueryParam = parse(information).query.ws; + const hostPortSplit = wsQueryParam && wsQueryParam.split(":"); + this.emit(DEBUGGER_ATTACHED_EVENT_NAME, { + url: information, + port: hostPortSplit && hostPortSplit[1] + }); + this.$logger.info(`To start debugging, open the following URL in Chrome:${EOL}${information}${EOL}`.cyan); + } + } + + public enableDebugging(deviceOpts: IEnableDebuggingDeviceOptions[], debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise[] { + return _.map(deviceOpts, d => this.enableDebuggingCore(d, debuggingAdditionalOptions)); + } + + private getDeviceDescriptor(deviceIdentifier: string, projectDir: string) { + const deviceDescriptors = this.getLiveSyncDeviceDescriptors(projectDir); + + return _.find(deviceDescriptors, d => d.identifier === deviceIdentifier); + } + + private async enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption: IEnableDebuggingDeviceOptions, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { + const currentDeviceDescriptor = this.getDeviceDescriptor(deviceOption.deviceIdentifier, debuggingAdditionalOptions.projectDir); + if (!currentDeviceDescriptor) { + this.$errors.failWithoutHelp(`Couldn't enable debugging for ${deviceOption.deviceIdentifier}`); + } + + currentDeviceDescriptor.debugggingEnabled = true; + const currentDeviceInstance = this.$devicesService.getDeviceByIdentifier(deviceOption.deviceIdentifier); + const attachDebuggerOptions: IAttachDebuggerOptions = { + deviceIdentifier: deviceOption.deviceIdentifier, + isEmulator: currentDeviceInstance.isEmulator, + outputPath: currentDeviceDescriptor.outputPath, + platform: currentDeviceInstance.deviceInfo.platform, + projectDir: debuggingAdditionalOptions.projectDir, + debugOptions: deviceOption.debugOptions + }; + + try { + await this.attachDebugger(attachDebuggerOptions); + } catch (err) { + this.$logger.trace("Couldn't attach debugger, will modify options and try again.", err); + attachDebuggerOptions.debugOptions.start = false; + await this.attachDebugger(attachDebuggerOptions); + } + } + + private async enableDebuggingCore(deviceOption: IEnableDebuggingDeviceOptions, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { + const liveSyncProcessInfo: ILiveSyncProcessInfo = this.liveSyncProcessesInfo[debuggingAdditionalOptions.projectDir]; + if (liveSyncProcessInfo && liveSyncProcessInfo.currentSyncAction) { + await liveSyncProcessInfo.currentSyncAction; + } + + return this.enableDebuggingCoreWithoutWaitingCurrentAction(deviceOption, debuggingAdditionalOptions); + } + + public disableDebugging(deviceOptions: IDisableDebuggingDeviceOptions[], debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise[] { + return _.map(deviceOptions, d => this.disableDebuggingCore(d, debuggingAdditionalOptions)); + } + + public async disableDebuggingCore(deviceOption: IDisableDebuggingDeviceOptions, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise { + const liveSyncProcessInfo = this.liveSyncProcessesInfo[debuggingAdditionalOptions.projectDir]; + if (liveSyncProcessInfo.currentSyncAction) { + await liveSyncProcessInfo.currentSyncAction; + } + + const currentDeviceDescriptor = this.getDeviceDescriptor(deviceOption.deviceIdentifier, debuggingAdditionalOptions.projectDir); + if (currentDeviceDescriptor) { + currentDeviceDescriptor.debugggingEnabled = false; + } else { + this.$errors.failWithoutHelp(`Couldn't disable debugging for ${deviceOption.deviceIdentifier}`); + } + + const currentDevice = this.$devicesService.getDeviceByIdentifier(currentDeviceDescriptor.identifier); + if (!currentDevice) { + this.$errors.failWithoutHelp(`Couldn't disable debugging for ${deviceOption.deviceIdentifier}. Could not find device.`); + } + + return this.$debugService.debugStop(currentDevice.deviceInfo.identifier); + } + @hook("liveSync") private async liveSyncOperation(deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo, projectData: IProjectData): Promise { @@ -161,7 +331,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { return this.$injector.resolve("androidLiveSyncService"); } - throw new Error(`Invalid platform ${platform}. Supported platforms are: ${this.$mobileHelper.platformNames.join(", ")}`); + this.$errors.failWithoutHelp(`Invalid platform ${platform}. Supported platforms are: ${this.$mobileHelper.platformNames.join(", ")}`); } private async ensureLatestAppPackageIsInstalledOnDevice(options: IEnsureLatestAppPackageIsInstalledOnDeviceOptions, nativePrepare?: INativePrepare): Promise { @@ -263,7 +433,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { watch: !liveSyncData.skipWatcher }); await this.$platformService.trackActionForPlatform({ action: "LiveSync", platform: device.deviceInfo.platform, isForDevice: !device.isEmulator, deviceOsVersion: device.deviceInfo.version }); - await this.refreshApplication(projectData, liveSyncResultInfo); + await this.refreshApplication(projectData, liveSyncResultInfo, liveSyncData.debugOptions, deviceBuildInfoDescriptor.outputPath); this.emit(LiveSyncEvents.liveSyncStarted, { projectDir: projectData.projectDir, @@ -369,7 +539,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { }; const liveSyncResultInfo = await service.liveSyncWatchAction(device, settings); - await this.refreshApplication(projectData, liveSyncResultInfo); + await this.refreshApplication(projectData, liveSyncResultInfo, liveSyncData.debugOptions, deviceBuildInfoDescriptor.outputPath); }, (device: Mobile.IDevice) => { const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir]; diff --git a/lib/services/test-execution-service.ts b/lib/services/test-execution-service.ts index 376490882c..eaee3ee9cd 100644 --- a/lib/services/test-execution-service.ts +++ b/lib/services/test-execution-service.ts @@ -16,7 +16,6 @@ class TestExecutionService implements ITestExecutionService { private $platformService: IPlatformService, private $platformsData: IPlatformsData, private $liveSyncService: ILiveSyncService, - private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, private $debugDataService: IDebugDataService, private $httpClient: Server.IHttpClient, private $config: IConfiguration, @@ -25,8 +24,7 @@ class TestExecutionService implements ITestExecutionService { private $options: IOptions, private $pluginsService: IPluginsService, private $errors: IErrors, - private $androidDebugService: IPlatformDebugService, - private $iOSDebugService: IPlatformDebugService, + private $debugService: IDebugService, private $devicesService: Mobile.IDevicesService, private $childProcess: IChildProcess) { } @@ -112,7 +110,12 @@ class TestExecutionService implements ITestExecutionService { return info; }); - const liveSyncInfo: ILiveSyncInfo = { projectDir: projectData.projectDir, skipWatcher: !this.$options.watch || this.$options.justlaunch, watchAllFiles: this.$options.syncAllFiles }; + const liveSyncInfo: ILiveSyncInfo = { + projectDir: projectData.projectDir, + skipWatcher: !this.$options.watch || this.$options.justlaunch, + watchAllFiles: this.$options.syncAllFiles, + debugOptions: this.$options + }; await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); @@ -188,9 +191,8 @@ class TestExecutionService implements ITestExecutionService { }; if (this.$options.debugBrk) { - const debugService = this.getDebugService(platform); const debugData = this.getDebugData(platform, projectData, deployOptions); - await debugService.debug(debugData, this.$options); + await this.$debugService.debug(debugData, this.$options); } else { const devices = this.$devicesService.getDeviceInstances(); // Now let's take data for each device: @@ -223,7 +225,13 @@ class TestExecutionService implements ITestExecutionService { return info; }); - const liveSyncInfo: ILiveSyncInfo = { projectDir: projectData.projectDir, skipWatcher: !this.$options.watch || this.$options.justlaunch, watchAllFiles: this.$options.syncAllFiles }; + const liveSyncInfo: ILiveSyncInfo = { + projectDir: projectData.projectDir, + skipWatcher: !this.$options.watch || this.$options.justlaunch, + watchAllFiles: this.$options.syncAllFiles, + debugOptions: this.$options + }; + await this.$liveSyncService.liveSync(deviceDescriptors, liveSyncInfo); } }; @@ -277,17 +285,6 @@ class TestExecutionService implements ITestExecutionService { return 'module.exports = ' + JSON.stringify(config); } - private getDebugService(platform: string): IPlatformDebugService { - let lowerCasedPlatform = platform.toLowerCase(); - if (lowerCasedPlatform === this.$devicePlatformsConstants.iOS.toLowerCase()) { - return this.$iOSDebugService; - } else if (lowerCasedPlatform === this.$devicePlatformsConstants.Android.toLowerCase()) { - return this.$androidDebugService; - } - - throw new Error(`Invalid platform ${platform}. Valid platforms are ${this.$devicePlatformsConstants.iOS} and ${this.$devicePlatformsConstants.Android}`); - } - private getKarmaConfiguration(platform: string, projectData: IProjectData): any { let karmaConfig: any = { browsers: [platform], diff --git a/test/debug.ts b/test/debug.ts index 4a89464944..f004e806ff 100644 --- a/test/debug.ts +++ b/test/debug.ts @@ -24,7 +24,6 @@ function createTestInjector(): IInjector { testInjector.register("options", Options); testInjector.register("devicePlatformsConstants", DevicePlatformsConstants); testInjector.register('childProcess', stubs.ChildProcessStub); - testInjector.register('androidDebugService', stubs.DebugServiceStub); testInjector.register('fs', FileSystem); testInjector.register('errors', stubs.ErrorsStub); testInjector.register('hostInfo', {}); @@ -39,7 +38,7 @@ function createTestInjector(): IInjector { getDeviceInstances: (): any[] => { return []; }, execute: async (): Promise => ({}) }); - testInjector.register("debugLiveSyncService", stubs.LiveSyncServiceStub); + testInjector.register("liveSyncService", stubs.LiveSyncServiceStub); testInjector.register("androidProjectService", AndroidProjectService); testInjector.register("androidToolsInfo", stubs.AndroidToolsInfoStub); testInjector.register("hostInfo", {}); @@ -50,6 +49,7 @@ function createTestInjector(): IInjector { testInjector.register("pluginVariablesService", {}); testInjector.register("deviceAppDataFactory", {}); testInjector.register("projectTemplatesService", {}); + testInjector.register("debugService", {}); testInjector.register("xmlValidator", {}); testInjector.register("npm", {}); testInjector.register("debugDataService", { @@ -341,40 +341,7 @@ describe("debug command tests", () => { testInjector = createTestInjector(); }); - it("Ensures that debugLivesync flag is true when executing debug --watch command", async () => { - const debugCommand = testInjector.resolveCommand("debug|android"); - const options: IOptions = testInjector.resolve("options"); - options.watch = true; - const devicesService = testInjector.resolve("devicesService"); - devicesService.getDeviceInstances = (): Mobile.IDevice[] => { - return [{ deviceInfo: { status: "Connected", platform: "android" } }]; - }; - - const debugLiveSyncService = testInjector.resolve("debugLiveSyncService"); - debugLiveSyncService.liveSync = async (deviceDescriptors: ILiveSyncDeviceInfo[], liveSyncData: ILiveSyncInfo): Promise => { - return null; - }; - - await debugCommand.execute(["android", "--watch"]); - const config: IConfiguration = testInjector.resolve("config"); - assert.isTrue(config.debugLivesync); - }); - - it("Ensures that beforePrepareAllPlugins will not call gradle when livesyncing", async () => { - let config: IConfiguration = testInjector.resolve("config"); - config.debugLivesync = true; - let childProcess: stubs.ChildProcessStub = testInjector.resolve("childProcess"); - let androidProjectService: IPlatformProjectService = testInjector.resolve("androidProjectService"); - let projectData: IProjectData = testInjector.resolve("projectData"); - let spawnFromEventCount = childProcess.spawnFromEventCount; - await androidProjectService.beforePrepareAllPlugins(projectData); - assert.isTrue(spawnFromEventCount === 0); - assert.isTrue(spawnFromEventCount === childProcess.spawnFromEventCount); - }); - it("Ensures that beforePrepareAllPlugins will call gradle with clean option when *NOT* livesyncing", async () => { - let config: IConfiguration = testInjector.resolve("config"); - config.debugLivesync = false; let childProcess: stubs.ChildProcessStub = testInjector.resolve("childProcess"); let androidProjectService: IPlatformProjectService = testInjector.resolve("androidProjectService"); let projectData: IProjectData = testInjector.resolve("projectData"); diff --git a/test/nativescript-cli-lib.ts b/test/nativescript-cli-lib.ts index 3bcc81522c..2168f38da0 100644 --- a/test/nativescript-cli-lib.ts +++ b/test/nativescript-cli-lib.ts @@ -20,7 +20,7 @@ describe("nativescript-cli-lib", () => { deviceLogProvider: null, npm: ["install", "uninstall", "view", "search"], extensibilityService: ["loadExtensions", "loadExtension", "getInstalledExtensions", "installExtension", "uninstallExtension"], - liveSyncService: ["liveSync", "stopLiveSync"], + liveSyncService: ["liveSync", "stopLiveSync", "enableDebugging", "disableDebugging", "attachDebugger"], analyticsService: ["startEqatecMonitor"], debugService: ["debug"] }; diff --git a/test/services/android-debug-service.ts b/test/services/android-debug-service.ts index d7f239136f..478575b9af 100644 --- a/test/services/android-debug-service.ts +++ b/test/services/android-debug-service.ts @@ -9,11 +9,10 @@ class AndroidDebugServiceInheritor extends AndroidDebugService { constructor(protected $devicesService: Mobile.IDevicesService, $errors: IErrors, $logger: ILogger, - $config: IConfiguration, $androidDeviceDiscovery: Mobile.IDeviceDiscovery, $androidProcessService: Mobile.IAndroidProcessService, $net: INet) { - super($devicesService, $errors, $logger, $config, $androidDeviceDiscovery, $androidProcessService, $net); + super({}, $devicesService, $errors, $logger, $androidDeviceDiscovery, $androidProcessService, $net); } public getChromeDebugUrl(debugOptions: IDebugOptions, port: number): string { @@ -26,7 +25,6 @@ const createTestInjector = (): IInjector => { testInjector.register("devicesService", {}); testInjector.register("errors", stubs.ErrorsStub); testInjector.register("logger", stubs.LoggerStub); - testInjector.register("config", {}); testInjector.register("androidDeviceDiscovery", {}); testInjector.register("androidProcessService", {}); testInjector.register("net", {}); diff --git a/test/services/debug-service.ts b/test/services/debug-service.ts index 259550d481..b320178890 100644 --- a/test/services/debug-service.ts +++ b/test/services/debug-service.ts @@ -4,7 +4,7 @@ import * as stubs from "../stubs"; import { assert } from "chai"; import { EventEmitter } from "events"; import * as constants from "../../lib/common/constants"; -import { CONNECTION_ERROR_EVENT_NAME } from "../../lib/constants"; +import { CONNECTION_ERROR_EVENT_NAME, DebugCommandErrors } from "../../lib/constants"; const fakeChromeDebugUrl = "fakeChromeDebugUrl"; class PlatformDebugService extends EventEmitter /* implements IPlatformDebugService */ { @@ -57,7 +57,7 @@ describe("debugService", () => { testInjector.register("devicesService", { getDeviceByIdentifier: (identifier: string): Mobile.IDevice => { return testData.isDeviceFound ? - { + { deviceInfo: testData.deviceInformation.deviceInfo, applicationManager: { @@ -141,7 +141,7 @@ describe("debugService", () => { const testData = getDefaultTestData(); testData.deviceInformation.deviceInfo.platform = "WP8"; - await assertIsRejected(testData, "Unsupported device OS:"); + await assertIsRejected(testData, DebugCommandErrors.UNSUPPORTED_DEVICE_OS_FOR_DEBUGGING); }); it("when trying to debug on iOS Simulator on macOS, debug-brk is passed, but pathToAppPackage is not", async () => { @@ -178,78 +178,6 @@ describe("debugService", () => { }); }); - describe("passes correct args to", () => { - const assertPassedDebugOptions = async (platform: string, userSpecifiedOptions?: IDebugOptions, hostInfo?: { isWindows: boolean, isDarwin: boolean }): Promise => { - const testData = getDefaultTestData(); - testData.deviceInformation.deviceInfo.platform = platform; - if (hostInfo) { - testData.hostInfo = hostInfo; - } - - const testInjector = getTestInjectorForTestConfiguration(testData); - const platformDebugService = testInjector.resolve(`${platform}DebugService`); - let passedDebugOptions: IDebugOptions = null; - platformDebugService.debug = async (debugData: IDebugData, debugOptions: IDebugOptions): Promise => { - passedDebugOptions = debugOptions; - return []; - }; - - const debugService = testInjector.resolve(DebugService); - - const debugData = getDebugData(); - await assert.isFulfilled(debugService.debug(debugData, userSpecifiedOptions)); - assert.isTrue(passedDebugOptions.chrome); - assert.isTrue(passedDebugOptions.start); - - return passedDebugOptions; - }; - - _.each(["android", "iOS"], platform => { - describe(`${platform}DebugService's debug method`, () => { - describe("on macOS", () => { - it("passes chrome and start as true, when no debugOptions are passed to debugService", async () => { - await assertPassedDebugOptions(platform); - }); - - it("when calling debug service with chrome and start set to false, should disregard them and set both to true", async () => { - await assertPassedDebugOptions(platform, { chrome: false, start: false }); - }); - - it("passes other custom options without modification", async () => { - const passedDebugOptions = await assertPassedDebugOptions(platform, { emulator: true, useBundledDevTools: true }); - assert.isTrue(passedDebugOptions.useBundledDevTools); - assert.isTrue(passedDebugOptions.emulator); - }); - }); - - describe("on Windows", () => { - const assertEmulatorOption = (passedDebugOptions: IDebugOptions) => { - if (platform === "iOS") { - assert.isFalse(passedDebugOptions.emulator); - } - }; - - it("passes chrome and start as true, when no debugOptions are passed to debugService", async () => { - const passedDebugOptions = await assertPassedDebugOptions(platform, null, { isWindows: true, isDarwin: false }); - assertEmulatorOption(passedDebugOptions); - }); - - it("when calling debug service with chrome and start set to false, should disregard them and set both to true", async () => { - const passedDebugOptions = await assertPassedDebugOptions(platform, { chrome: false, start: false }, { isWindows: true, isDarwin: false }); - assertEmulatorOption(passedDebugOptions); - }); - - it("passes other custom options without modification", async () => { - const passedDebugOptions = await assertPassedDebugOptions(platform, { debugBrk: true, useBundledDevTools: true }); - assert.isTrue(passedDebugOptions.useBundledDevTools); - assert.isTrue(passedDebugOptions.debugBrk); - }); - }); - - }); - }); - }); - describe(`raises ${CONNECTION_ERROR_EVENT_NAME} event`, () => { _.each(["android", "iOS"], platform => { it(`when ${platform}DebugService raises ${CONNECTION_ERROR_EVENT_NAME} event`, async () => { diff --git a/test/services/ios-debug-service.ts b/test/services/ios-debug-service.ts index 2f4898cfcb..23af05478b 100644 --- a/test/services/ios-debug-service.ts +++ b/test/services/ios-debug-service.ts @@ -10,6 +10,7 @@ class IOSDebugServiceInheritor extends IOSDebugService { $platformService: IPlatformService, $iOSEmulatorServices: Mobile.IEmulatorPlatformServices, $childProcess: IChildProcess, + $hostInfo: IHostInfo, $logger: ILogger, $errors: IErrors, $npmInstallationManager: INpmInstallationManager, @@ -17,7 +18,7 @@ class IOSDebugServiceInheritor extends IOSDebugService { $iOSSocketRequestExecutor: IiOSSocketRequestExecutor, $processService: IProcessService, $socketProxyFactory: ISocketProxyFactory) { - super($devicesService, $platformService, $iOSEmulatorServices, $childProcess, $logger, $errors, + super({}, $devicesService, $platformService, $iOSEmulatorServices, $childProcess, $hostInfo, $logger, $errors, $npmInstallationManager, $iOSNotification, $iOSSocketRequestExecutor, $processService, $socketProxyFactory); } @@ -35,6 +36,7 @@ const createTestInjector = (): IInjector => { testInjector.register("errors", stubs.ErrorsStub); testInjector.register("logger", stubs.LoggerStub); + testInjector.register("hostInfo", {}); testInjector.register("npmInstallationManager", {}); testInjector.register("iOSNotification", {}); testInjector.register("iOSSocketRequestExecutor", {}); diff --git a/test/services/livesync-service.ts b/test/services/livesync-service.ts index 75d2073bec..0dde25150b 100644 --- a/test/services/livesync-service.ts +++ b/test/services/livesync-service.ts @@ -17,6 +17,9 @@ const createTestInjector = (): IInjector => { testInjector.register("nodeModulesDependenciesBuilder", {}); testInjector.register("logger", LoggerStub); testInjector.register("processService", {}); + testInjector.register("debugService", {}); + testInjector.register("errors", {}); + testInjector.register("debugDataService", {}); testInjector.register("hooksService", { executeAfterHooks: (commandName: string, hookArguments?: IDictionary): Promise => Promise.resolve() }); @@ -38,6 +41,9 @@ class LiveSyncServiceInheritor extends LiveSyncService { $processService: IProcessService, $hooksService: IHooksService, $pluginsService: IPluginsService, + $debugService: IDebugService, + $errors: IErrors, + $debugDataService: IDebugDataService, $injector: IInjector) { super( @@ -51,6 +57,9 @@ class LiveSyncServiceInheritor extends LiveSyncService { $processService, $hooksService, $pluginsService, + $debugService, + $errors, + $debugDataService, $injector ); } diff --git a/test/stubs.ts b/test/stubs.ts index 2a9569410e..8c98f21c2f 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -35,6 +35,14 @@ export class LoggerStub implements ILogger { printMarkdown(message: string): void { } } +export class ProcessServiceStub implements IProcessService { + public listenersCount: number; + + public attachToProcessExitSignals(context: any, callback: () => void): void { + return undefined; + } +} + export class FileSystemStub implements IFileSystem { async zipFiles(zipFile: string, files: string[], zipPathCallback: (path: string) => string): Promise { return undefined;