diff --git a/PublicAPI.md b/PublicAPI.md index 44fa4e8e46..a2f251389e 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -58,6 +58,10 @@ const tns = require("nativescript"); * [devicesService](#devicesservice) * [getEmulatorImages](#getemulatorimages) * [startEmulator](#startemulator) + * [startDeviceDetectionInterval](#startdevicedetectioninterval) + * [stopDeviceDetectionInterval](#stopdevicedetectioninterval) + * [startEmulatorDetectionInterval](#startemulatordetectioninterval) + * [stopEmulatorDetectionInterval](#stopemulatordetectioninterval) * [deviceEmitter](#deviceemitter) * [events](#deviceemitterevents) * [previewDevicesService](#previewdevicesservice) @@ -1287,6 +1291,59 @@ tns.devicesService.startEmulator({imageIdentifier: "my emulator imageIdentifier" .then(errors => { }); ``` +### startDeviceDetectionInterval +Starts device detection interval, which is run on specified number of seconds. This allows detection of new attached devices, started emulators/simulators, detection when device/emulator/simulator is disconnected, etc. +> NOTE: The interval is started automatically when you call `devicesService.initialize` without passing `skipDeviceDetectionInterval: true`. + +> NOTE: iOS Device detection interval cannot be stopped, so once started, it will always report connected/disconnected devices. + +* Definition +```TypeScript +startDeviceDetectionInterval({ detectionInterval?: number, platform?: string }): void +``` + +* Usage +```JavaScript +tns.devicesService.startDeviceDetectionInterval({ detectionInterval: 1000 }); +``` + +### stopDeviceDetectionInterval +Stops device detection interval started by `devicesService.initialize` or `devicesService.startDeviceDetectionInterval`. +* Definition +```TypeScript +stopDeviceDetectionInterval(): void +``` + +* Usage +```JavaScript +tns.devicesService.stopDeviceDetectionInterval(); +``` + +### startEmulatorDetectionInterval +Starts emulator images detection interval, which is run on specified number of seconds. This allows detection of new installed emulator/simulator images. + +* Definition +```TypeScript +startEmulatorDetectionInterval({ detectionInterval?: number }): void +``` + +* Usage +```JavaScript +tns.devicesService.startEmulatorDetectionInterval({ detectionInterval: 1000 }); +``` + +### stopEmulatorDetectionInterval +Stops device detection interval started by `devicesService.startEmulatorDetectionInterval`. +* Definition +```TypeScript +stopEmulatorDetectionInterval(): void +``` + +* Usage +```JavaScript +tns.devicesService.stopEmulatorDetectionInterval(); +``` + ## deviceEmitter This module is used to emit information for devices, applications on them, etc. diff --git a/lib/common/bootstrap.ts b/lib/common/bootstrap.ts index a1a94ebd38..78370b281a 100644 --- a/lib/common/bootstrap.ts +++ b/lib/common/bootstrap.ts @@ -6,6 +6,7 @@ $injector.require("errors", "./errors"); $injector.requirePublic("fs", "./file-system"); $injector.require("hostInfo", "./host-info"); $injector.require("osInfo", "./os-info"); +$injector.require("timers", "./timers"); $injector.require("dispatcher", "./dispatchers"); $injector.require("commandDispatcher", "./dispatchers"); diff --git a/lib/common/declarations.d.ts b/lib/common/declarations.d.ts index 5e096b0368..4b27870564 100644 --- a/lib/common/declarations.d.ts +++ b/lib/common/declarations.d.ts @@ -992,6 +992,18 @@ interface ISysInfo { */ getJavaCompilerVersion(): Promise; + /** + * Gets JAVA version based on the executable in PATH. + * @return {Promise} + */ + getJavaVersionFromPath(): Promise; + + /** + * Gets JAVA version based on the JAVA from JAVA_HOME. + * @return {Promise} + */ + getJavaVersionFromJavaHome(): Promise; + /** * Gets all global warnings for the current environment, for example Node.js version compatibility, OS compatibility, etc. * @return {Promise} All warnings. Empty array is returned in case the system is setup correctly. diff --git a/lib/common/definitions/mobile.d.ts b/lib/common/definitions/mobile.d.ts index e3beddaac0..1ce531beef 100644 --- a/lib/common/definitions/mobile.d.ts +++ b/lib/common/definitions/mobile.d.ts @@ -387,11 +387,7 @@ declare module Mobile { /** * Describes options that can be passed to devices service's initialization method. */ - interface IDevicesServicesInitializationOptions { - /** - * The platform for which to initialize. If passed will detect only devices belonging to said platform. - */ - platform?: string; + interface IDevicesServicesInitializationOptions extends Partial { /** * If passed will start an emulator if necesasry. */ @@ -412,10 +408,6 @@ declare module Mobile { * Specifies whether we should skip the emulator starting. */ skipEmulatorStart?: boolean; - /** - * Defines if the initialization should await the whole iOS detection to finish or it can just start the detection. - */ - shouldReturnImmediateResult?: boolean; /** * Currently available only for iOS. Specifies the sdk version of the iOS simulator. * In case when `tns run ios --device "iPhone 6"` command is executed, the user can specify the sdk of the simulator because it is possible to have more than one device with the same name but with different sdk versions. @@ -1029,7 +1021,11 @@ declare module Mobile { resolveProductName(deviceType: string): string; } - interface IDeviceLookingOptions extends IHasEmulatorOption { + interface IHasDetectionInterval { + detectionInterval?: number; + } + + interface IDeviceLookingOptions extends IHasEmulatorOption, IHasDetectionInterval { shouldReturnImmediateResult: boolean; platform: string; } diff --git a/lib/common/definitions/timers.d.ts b/lib/common/definitions/timers.d.ts new file mode 100644 index 0000000000..2b57b8b06f --- /dev/null +++ b/lib/common/definitions/timers.d.ts @@ -0,0 +1,4 @@ +interface ITimers { + setInterval(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer; + clearInterval(intervalId: NodeJS.Timer): void; +} \ No newline at end of file diff --git a/lib/common/mobile/android/android-virtual-device-service.ts b/lib/common/mobile/android/android-virtual-device-service.ts index 3aeb0fbe8f..4cfcf2eb01 100644 --- a/lib/common/mobile/android/android-virtual-device-service.ts +++ b/lib/common/mobile/android/android-virtual-device-service.ts @@ -16,6 +16,7 @@ export class AndroidVirtualDeviceService implements Mobile.IAndroidVirtualDevice private $emulatorHelper: Mobile.IEmulatorHelper, private $fs: IFileSystem, private $hostInfo: IHostInfo, + private $sysInfo: ISysInfo, private $logger: ILogger) { this.androidHome = process.env.ANDROID_HOME; } @@ -152,8 +153,12 @@ export class AndroidVirtualDeviceService implements Mobile.IAndroidVirtualDevice let result: ISpawnResult = null; let devices: Mobile.IDeviceInfo[] = []; let errors: string[] = []; + const canExecuteAvdManagerCommand = await this.canExecuteAvdManagerCommand(); + if (!canExecuteAvdManagerCommand) { + errors = ["Unable to execute avdmanager, ensure JAVA_HOME is set and points to correct directory"]; + } - if (this.pathToAvdManagerExecutable && this.$fs.exists(this.pathToAvdManagerExecutable)) { + if (canExecuteAvdManagerCommand) { result = await this.$childProcess.trySpawnFromCloseEvent(this.pathToAvdManagerExecutable, ["list", "avds"]); } else if (this.pathToAndroidExecutable && this.$fs.exists(this.pathToAndroidExecutable)) { result = await this.$childProcess.trySpawnFromCloseEvent(this.pathToAndroidExecutable, ["list", "avd"]); @@ -169,6 +174,22 @@ export class AndroidVirtualDeviceService implements Mobile.IAndroidVirtualDevice return { devices, errors }; } + @cache() + private async canExecuteAvdManagerCommand(): Promise { + let canExecute = false; + if (this.pathToAvdManagerExecutable && this.$fs.exists(this.pathToAvdManagerExecutable)) { + if (process.env.JAVA_HOME) { + // In case JAVA_HOME is set, but it points to incorrect directory (i.e. there's no java in $JAVA_HOME/bin/java), avdmanager will fail + // no matter if you have correct java in PATH. + canExecute = !!(await this.$sysInfo.getJavaVersionFromJavaHome()); + } else { + canExecute = !!(await this.$sysInfo.getJavaVersionFromPath()); + } + } + + return canExecute; + } + private async getRunningEmulatorData(runningEmulatorId: string, availableEmulators: Mobile.IDeviceInfo[]): Promise { const imageIdentifier = await this.getRunningEmulatorImageIdentifier(runningEmulatorId); const runningEmulator = this.$emulatorHelper.getEmulatorByImageIdentifier(imageIdentifier, availableEmulators); diff --git a/lib/common/mobile/mobile-core/devices-service.ts b/lib/common/mobile/mobile-core/devices-service.ts index 30d04dc344..f91283af99 100644 --- a/lib/common/mobile/mobile-core/devices-service.ts +++ b/lib/common/mobile/mobile-core/devices-service.ts @@ -22,7 +22,8 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi private _data: Mobile.IDevicesServicesInitializationOptions; private _otherDeviceDiscoveries: Mobile.IDeviceDiscovery[] = []; private _allDeviceDiscoveries: Mobile.IDeviceDiscovery[] = []; - private deviceDetectionIntervals: NodeJS.Timer[] = []; + private deviceDetectionInterval: NodeJS.Timer; + private emulatorDetectionInterval: NodeJS.Timer; constructor(private $logger: ILogger, private $errors: IErrors, @@ -37,12 +38,12 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi private $injector: IInjector, private $options: IOptions, private $androidProcessService: Mobile.IAndroidProcessService, - private $processService: IProcessService, private $iOSEmulatorServices: Mobile.IiOSSimulatorService, private $androidEmulatorServices: Mobile.IEmulatorPlatformService, private $androidEmulatorDiscovery: Mobile.IDeviceDiscovery, private $emulatorHelper: Mobile.IEmulatorHelper, - private $prompter: IPrompter) { + private $prompter: IPrompter, + private $timers: ITimers) { super(); this.attachToKnownDeviceDiscoveryEvents(); this.attachToKnownEmulatorDiscoveryEvents(); @@ -261,7 +262,7 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi /** * Starts looking for devices. Any found devices are pushed to "_devices" variable. */ - protected async detectCurrentlyAttachedDevices(deviceInitOpts?: Mobile.IDevicesServicesInitializationOptions): Promise { + protected async detectCurrentlyAttachedDevices(deviceInitOpts?: Mobile.IDeviceLookingOptions): Promise { const options = this.getDeviceLookingOptions(deviceInitOpts); for (const deviceDiscovery of this._allDeviceDiscoveries) { @@ -277,7 +278,7 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi try { await this.$androidEmulatorDiscovery.startLookingForDevices(); } catch (err) { - this.$logger.trace(`Error while checking for android emulators. ${err}`); + this.$logger.trace(`Error while checking for Android emulators. ${err}`); } try { @@ -287,40 +288,39 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi } } - protected async startDeviceDetectionIntervals(deviceInitOpts: Mobile.IDevicesServicesInitializationOptions = {}): Promise { - this.$processService.attachToProcessExitSignals(this, this.clearDeviceDetectionInterval); - - if (this.deviceDetectionIntervals.length) { - this.$logger.trace("Device detection intervals are already started. New intervals will not be started."); - return; - } + @exported("devicesService") + public startDeviceDetectionInterval(deviceInitOpts: Mobile.IDeviceLookingOptions = {}): void { + if (!this.deviceDetectionInterval) { + let isDeviceDetectionIntervalInProgress = false; - let isDeviceDetectionIntervalInProgress = false; - const deviceDetectionInterval = setInterval(async () => { - if (isDeviceDetectionIntervalInProgress) { - return; - } + this.deviceDetectionInterval = this.$timers.setInterval(async () => { + if (isDeviceDetectionIntervalInProgress) { + return; + } - isDeviceDetectionIntervalInProgress = true; + isDeviceDetectionIntervalInProgress = true; - await this.detectCurrentlyAttachedDevices(deviceInitOpts); + await this.detectCurrentlyAttachedDevices(deviceInitOpts); - try { - const trustedDevices = _.filter(this._devices, device => device.deviceInfo.status === constants.CONNECTED_STATUS); - await settlePromises(_.map(trustedDevices, device => device.applicationManager.checkForApplicationUpdates())); - } catch (err) { - this.$logger.trace("Error checking for application updates on devices.", err); - } + try { + const trustedDevices = _.filter(this._devices, device => device.deviceInfo.status === constants.CONNECTED_STATUS); + await settlePromises(_.map(trustedDevices, device => device.applicationManager.checkForApplicationUpdates())); + } catch (err) { + this.$logger.trace("Error checking for application updates on devices.", err); + } - isDeviceDetectionIntervalInProgress = false; + isDeviceDetectionIntervalInProgress = false; - }, DevicesService.DEVICE_LOOKING_INTERVAL); + }, deviceInitOpts.detectionInterval || DevicesService.DEVICE_LOOKING_INTERVAL); - deviceDetectionInterval.unref(); - this.deviceDetectionIntervals.push(deviceDetectionInterval); + this.deviceDetectionInterval.unref(); + } + } + @exported("devicesService") + public startEmulatorDetectionInterval(opts: Mobile.IHasDetectionInterval = {}): void { let isEmulatorDetectionIntervalRunning = false; - const emulatorDetectionInterval = setInterval(async () => { + this.emulatorDetectionInterval = this.$timers.setInterval(async () => { if (isEmulatorDetectionIntervalRunning) { return; } @@ -328,10 +328,23 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi isEmulatorDetectionIntervalRunning = true; await this.detectCurrentlyAvailableEmulators(); isEmulatorDetectionIntervalRunning = false; - }, DevicesService.EMULATOR_IMAGES_DETECTION_INTERVAL); + }, opts.detectionInterval || DevicesService.EMULATOR_IMAGES_DETECTION_INTERVAL); - emulatorDetectionInterval.unref(); - this.deviceDetectionIntervals.push(emulatorDetectionInterval); + this.emulatorDetectionInterval.unref(); + } + + @exported("devicesService") + public stopDeviceDetectionInterval(): void { + if (this.deviceDetectionInterval) { + this.$timers.clearInterval(this.deviceDetectionInterval); + } + } + + @exported("devicesService") + public stopEmulatorDetectionInterval(): void { + if (this.emulatorDetectionInterval) { + this.$timers.clearInterval(this.emulatorDetectionInterval); + } } /** @@ -356,7 +369,7 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi /** * Starts looking for running devices. All found devices are pushed to _devices variable. */ - private async startLookingForDevices(deviceInitOpts?: Mobile.IDevicesServicesInitializationOptions): Promise { + private async startLookingForDevices(deviceInitOpts?: Mobile.IDeviceLookingOptions): Promise { this.$logger.trace("startLookingForDevices; platform is %s", this._platform); if (this._platform) { @@ -366,7 +379,7 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi await this.detectCurrentlyAttachedDevices(deviceInitOpts); await this.detectCurrentlyAvailableEmulators(); - await this.startDeviceDetectionIntervals(deviceInitOpts); + await this.startDeviceDetectionInterval(deviceInitOpts); } /** @@ -517,7 +530,7 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi // are there any running devices this._platform = deviceInitOpts.platform; try { - await this.startLookingForDevices(deviceInitOpts); + await this.startLookingForDevices(deviceInitOpts); } catch (err) { this.$logger.trace("Error while checking for devices.", err); } @@ -606,33 +619,39 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi const platform = deviceInitOpts.platform; const deviceOption = deviceInitOpts.deviceId; + const deviceLookingOptions: Mobile.IDeviceLookingOptions = { + emulator: deviceInitOpts.emulator, + platform: deviceInitOpts.platform, + shouldReturnImmediateResult: deviceInitOpts.shouldReturnImmediateResult, + detectionInterval: deviceInitOpts.detectionInterval + }; if (platform && deviceOption) { this._platform = this.$mobileHelper.validatePlatformName(deviceInitOpts.platform); - await this.startLookingForDevices(deviceInitOpts); + await this.startLookingForDevices(deviceLookingOptions); this._device = await this.getDevice(deviceOption); if (this._device.deviceInfo.platform !== this._platform) { this.$errors.fail(constants.ERROR_CANNOT_RESOLVE_DEVICE); } this.$logger.warn("Your application will be deployed only on the device specified by the provided index or identifier."); } else if (!platform && deviceOption) { - await this.startLookingForDevices(deviceInitOpts); + await this.startLookingForDevices(deviceLookingOptions); this._device = await this.getDevice(deviceOption); this._platform = this._device.deviceInfo.platform; } else if (platform && !deviceOption) { this._platform = this.$mobileHelper.validatePlatformName(platform); - await this.startLookingForDevices(deviceInitOpts); + await this.startLookingForDevices(deviceLookingOptions); } else { // platform and deviceId are not specified if (deviceInitOpts.skipInferPlatform) { if (deviceInitOpts.skipDeviceDetectionInterval) { - await this.detectCurrentlyAttachedDevices(deviceInitOpts); + await this.detectCurrentlyAttachedDevices(deviceLookingOptions); } else { deviceInitOpts.shouldReturnImmediateResult = true; - await this.startLookingForDevices(deviceInitOpts); + await this.startLookingForDevices(deviceLookingOptions); } } else { - await this.startLookingForDevices(deviceInitOpts); + await this.startLookingForDevices(deviceLookingOptions); const devices = this.getDeviceInstances(); const platforms = _(devices) @@ -700,18 +719,6 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi return debuggableViewsPerApp && debuggableViewsPerApp[appIdentifier]; } - private clearDeviceDetectionInterval(): void { - if (this.deviceDetectionIntervals.length) { - for (const interval of this.deviceDetectionIntervals) { - clearInterval(interval); - } - - this.deviceDetectionIntervals.splice(0, this.deviceDetectionIntervals.length); - } else { - this.$logger.trace("Device detection intervals are not started, so it cannot be stopped."); - } - } - private getDebuggableAppsCore(deviceIdentifier: string): Promise { const device = this.getDeviceByIdentifier(deviceIdentifier); return device.applicationManager.getDebuggableApps(); diff --git a/lib/common/test/unit-tests/mobile/android-virtual-device-service.ts b/lib/common/test/unit-tests/mobile/android-virtual-device-service.ts index 9cf9795fe3..14f160a5a3 100644 --- a/lib/common/test/unit-tests/mobile/android-virtual-device-service.ts +++ b/lib/common/test/unit-tests/mobile/android-virtual-device-service.ts @@ -76,6 +76,10 @@ function createTestInjector(data: { avdManagerOutput?: string, avdManagerError?: testInjector.register("emulatorHelper", EmulatorHelper); testInjector.register("hostInfo", {}); testInjector.register("logger", { trace: () => ({}) }); + testInjector.register("sysInfo", { + getJavaVersionFromJavaHome: async () => "1.8.0", + getJavaVersionFromPath: async () => "1.8.0" + }); return testInjector; } diff --git a/lib/common/test/unit-tests/mobile/devices-service.ts b/lib/common/test/unit-tests/mobile/devices-service.ts index fe7df258ff..6008bc6162 100644 --- a/lib/common/test/unit-tests/mobile/devices-service.ts +++ b/lib/common/test/unit-tests/mobile/devices-service.ts @@ -14,6 +14,7 @@ import { CommonLoggerStub, ErrorsStub } from "../stubs"; import { Messages } from "../../../messages/messages"; import * as constants from "../../../constants"; import { DevicePlatformsConstants } from "../../../mobile/device-platforms-constants"; +import { Timers } from "../../../timers"; const helpers = require("../../../helpers"); const originalIsInteractive = helpers.isInteractive; @@ -28,11 +29,11 @@ class DevicesServiceInheritor extends DevicesService { return super.startEmulatorIfNecessary(data); } - public startDeviceDetectionIntervals(deviceInitOpts: Mobile.IDevicesServicesInitializationOptions = {}): Promise { - return super.startDeviceDetectionIntervals(deviceInitOpts); + public startDeviceDetectionInterval(deviceInitOpts: Mobile.IDeviceLookingOptions = {}): void { + return super.startDeviceDetectionInterval(deviceInitOpts); } - public detectCurrentlyAttachedDevices(options?: Mobile.IDevicesServicesInitializationOptions): Promise { + public detectCurrentlyAttachedDevices(options?: Mobile.IDeviceLookingOptions): Promise { return super.detectCurrentlyAttachedDevices(options); } } @@ -185,6 +186,7 @@ function createTestInjector(): IInjector { testInjector.register("androidProcessService", { /* no implementation required */ }); testInjector.register("androidEmulatorDiscovery", AndroidEmulatorDiscoveryStub); testInjector.register("emulatorHelper", {}); + testInjector.register("timers", Timers); return testInjector; } @@ -227,7 +229,10 @@ function mockSetInterval(testCaseCallback?: Function): void { }; - process.nextTick(() => execution()); + /* tslint:disable:no-floating-promises */ + execution(); + /* tslint:enable:no-floating-promises */ + return nodeJsTimer; }; } @@ -238,10 +243,10 @@ function resetDefaultSetInterval(): void { async function assertOnNextTick(assertionFunction: Function): Promise { await new Promise(resolve => { - process.nextTick(() => { + setTimeout(() => { assertionFunction(); resolve(); - }); + }, 2); }); } @@ -1193,7 +1198,7 @@ describe("devicesService", () => { }); }); - describe("startDeviceDetectionIntervals", () => { + describe("startDeviceDetectionInterval", () => { let setIntervalsCalledCount: number; beforeEach(() => { @@ -1212,7 +1217,7 @@ describe("devicesService", () => { hasStartedDeviceDetectionInterval = true; }); - await devicesService.startDeviceDetectionIntervals(); + devicesService.startDeviceDetectionInterval(); assert.isTrue(hasStartedDeviceDetectionInterval); }); @@ -1225,7 +1230,9 @@ describe("devicesService", () => { await callback(); }; - process.nextTick(execution); + /* tslint:disable:no-floating-promises */ + execution(); + /* tslint:enable:no-floating-promises */ return { ref: () => { /* no implementation required */ }, @@ -1236,11 +1243,11 @@ describe("devicesService", () => { }; }; - await devicesService.startDeviceDetectionIntervals(); - await devicesService.startDeviceDetectionIntervals(); - await devicesService.startDeviceDetectionIntervals(); + devicesService.startDeviceDetectionInterval(); + devicesService.startDeviceDetectionInterval(); + devicesService.startDeviceDetectionInterval(); - assert.deepEqual(setIntervalsCalledCount, 2); + assert.deepEqual(setIntervalsCalledCount, 1); }); describe("ios devices check", () => { @@ -1257,7 +1264,7 @@ describe("devicesService", () => { hasCheckedForIosDevices = true; }; - await devicesService.startDeviceDetectionIntervals(); + devicesService.startDeviceDetectionInterval(); assert.isTrue(hasCheckedForIosDevices); }); @@ -1265,7 +1272,14 @@ describe("devicesService", () => { it("should not throw if ios device check fails throws an exception.", async () => { ($iOSDeviceDiscovery).checkForDevices = throwErrorFunction; - await assert.isFulfilled(devicesService.startDeviceDetectionIntervals()); + let hasUnhandledRejection = false; + process.on("unhandledRejection", (reason: any, promise: Promise) => { + hasUnhandledRejection = true; + }); + + devicesService.startDeviceDetectionInterval(); + + await assertOnNextTick(() => assert.isFalse(hasUnhandledRejection)); }); }); @@ -1284,14 +1298,20 @@ describe("devicesService", () => { }; mockSetInterval(); - await devicesService.startDeviceDetectionIntervals(); + devicesService.startDeviceDetectionInterval(); await assertOnNextTick(() => assert.isTrue(hasCheckedForAndroidDevices)); }); it("should not throw if android device check fails throws an exception.", async () => { $androidDeviceDiscovery.startLookingForDevices = throwErrorFunction; + let hasUnhandledRejection = false; + process.on("unhandledRejection", (reason: any, promise: Promise) => { + hasUnhandledRejection = true; + }); + + devicesService.startDeviceDetectionInterval(); - await assert.isFulfilled(devicesService.startDeviceDetectionIntervals()); + await assertOnNextTick(() => assert.isFalse(hasUnhandledRejection)); }); }); @@ -1314,7 +1334,14 @@ describe("devicesService", () => { it("should not throw if ios simulator check fails throws an exception.", async () => { ($iOSSimulatorDiscovery).checkForDevices = throwErrorFunction; - await assert.isFulfilled(devicesService.startDeviceDetectionIntervals()); + let hasUnhandledRejection = false; + process.on("unhandledRejection", (reason: any, promise: Promise) => { + hasUnhandledRejection = true; + }); + + devicesService.startDeviceDetectionInterval(); + + await assertOnNextTick(() => assert.isFalse(hasUnhandledRejection)); }); }); @@ -1334,7 +1361,7 @@ describe("devicesService", () => { }; mockSetInterval(); - await devicesService.startDeviceDetectionIntervals(); + devicesService.startDeviceDetectionInterval(); await assertOnNextTick(() => assert.isTrue(hasCheckedForDevices)); }); @@ -1342,7 +1369,14 @@ describe("devicesService", () => { it("should not throw if device check fails throws an exception.", async () => { customDeviceDiscovery.startLookingForDevices = throwErrorFunction; - await assert.isFulfilled(devicesService.startDeviceDetectionIntervals()); + let hasUnhandledRejection = false; + process.on("unhandledRejection", (reason: any, promise: Promise) => { + hasUnhandledRejection = true; + }); + + devicesService.startDeviceDetectionInterval(); + + await assertOnNextTick(() => assert.isFalse(hasUnhandledRejection)); }); }); @@ -1371,7 +1405,7 @@ describe("devicesService", () => { }); it("should check for application updates for all connected devices.", async () => { - await devicesService.startDeviceDetectionIntervals(); + devicesService.startDeviceDetectionInterval(); await assertOnNextTick(() => { assert.isTrue(hasCheckedForAndroidAppUpdates); @@ -1382,14 +1416,14 @@ describe("devicesService", () => { it("should check for application updates if the check on one device throws an exception.", async () => { iOSDevice.applicationManager.checkForApplicationUpdates = throwErrorFunction; - await devicesService.startDeviceDetectionIntervals(); + devicesService.startDeviceDetectionInterval(); await assertOnNextTick(() => assert.isTrue(hasCheckedForAndroidAppUpdates)); }); it("should check for application updates only on devices with status Connected", async () => { androidDevice.deviceInfo.status = constants.UNREACHABLE_STATUS; - await devicesService.startDeviceDetectionIntervals(); + devicesService.startDeviceDetectionInterval(); await assertOnNextTick(() => { assert.isFalse(hasCheckedForAndroidAppUpdates); @@ -1402,7 +1436,7 @@ describe("devicesService", () => { androidDevice.applicationManager.checkForApplicationUpdates = throwErrorFunction; const callback = () => { - devicesService.startDeviceDetectionIntervals.call(devicesService); + devicesService.startDeviceDetectionInterval.call(devicesService); }; assert.doesNotThrow(callback); diff --git a/lib/common/timers.ts b/lib/common/timers.ts new file mode 100644 index 0000000000..2325eb7481 --- /dev/null +++ b/lib/common/timers.ts @@ -0,0 +1,25 @@ +export class Timers implements ITimers { + private pendingIntervalIds: NodeJS.Timer[] = []; + + constructor(private $processService: IProcessService) { + this.$processService.attachToProcessExitSignals(this, () => { + _.each(this.pendingIntervalIds, clearInterval); + }); + } + + public setInterval(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer { + const timer = setInterval(callback, ms, args); + + this.pendingIntervalIds.push(timer); + + return timer; + } + + public clearInterval(intervalId: NodeJS.Timer): void { + clearInterval(intervalId); + + _.remove(this.pendingIntervalIds, id => id === intervalId); + } +} + +$injector.register("timers", Timers); diff --git a/lib/sys-info.ts b/lib/sys-info.ts index 6a3a35714e..9129cdf62d 100644 --- a/lib/sys-info.ts +++ b/lib/sys-info.ts @@ -34,6 +34,14 @@ export class SysInfo implements ISysInfo { return sysInfo.getJavaCompilerVersion(); } + public getJavaVersionFromPath(): Promise { + return sysInfo.getJavaVersionFromPath(); + } + + public getJavaVersionFromJavaHome(): Promise { + return sysInfo.getJavaVersionFromJavaHome(); + } + @exported("sysInfo") public async getSystemWarnings(): Promise { const warnings: ISystemWarning[] = []; diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 8c3f83c9f4..65a6cd21ec 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -5605,9 +5605,9 @@ } }, "nativescript-doctor": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/nativescript-doctor/-/nativescript-doctor-1.8.1.tgz", - "integrity": "sha512-Ke19jTb3Gz/3QHF1hE2iFoQ0N0693paEbtNgfeMFszKCJLw0BfOHRwbABjNhb9pxfvvi86fvFexDcmWJWpJo0w==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/nativescript-doctor/-/nativescript-doctor-1.9.1.tgz", + "integrity": "sha512-qdluBILhzAQhnIg8Y83syyjy63rTt5pvx0SFpbtwj7kE+LsXJJRhHNa+1KEtznxh7jcTfdLEZjxxWDTIAIK5oA==", "requires": { "osenv": "0.1.3", "semver": "5.5.1", diff --git a/package.json b/package.json index 6817f93334..ca52a2833a 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "mkdirp": "0.5.1", "mute-stream": "0.0.5", "nativescript-dev-xcode": "0.1.0", - "nativescript-doctor": "1.8.1", + "nativescript-doctor": "1.9.1", "nativescript-preview-sdk": "0.3.3", "open": "0.0.5", "ora": "2.0.0", diff --git a/test/ios-project-service.ts b/test/ios-project-service.ts index e209933a38..62a02b836b 100644 --- a/test/ios-project-service.ts +++ b/test/ios-project-service.ts @@ -160,6 +160,7 @@ function createTestInjector(projectPath: string, projectName: string, xcode?: IX removeExtensions: () => { /* */ }, addExtensionsFromPath: () => Promise.resolve() }); + testInjector.register("timers", {}); return testInjector; } diff --git a/test/nativescript-cli-lib.ts b/test/nativescript-cli-lib.ts index 24cbf988f0..9238cfcc61 100644 --- a/test/nativescript-cli-lib.ts +++ b/test/nativescript-cli-lib.ts @@ -37,11 +37,17 @@ describe("nativescript-cli-lib", () => { "getDebuggableApps", "getDebuggableViews", "getDevices", + "getEmulatorImages", "getInstalledApplications", "initialize", "isAppInstalledOnDevices", "mapAbstractToTcpPort", - "setLogLevel" + "setLogLevel", + "startDeviceDetectionInterval", + "stopDeviceDetectionInterval", + "startEmulator", + "startEmulatorDetectionInterval", + "stopEmulatorDetectionInterval", ], assetsGenerationService: [ "generateIcons",