From b72bdbc52ae02eface14cbf5ef12ba5a883fb5d0 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Tue, 22 Aug 2017 10:50:18 +0300 Subject: [PATCH 1/2] Fix stopLiveSync method Currently stopLiveSync method does not emit correct events in some cases. For example, when there's only one attached device and its identifier is passed to `stopLiveSync` method, we do not emit `liveSyncStopped` event. Fix the code and add unit tests to assure `liveSyncStopped` event is raised correctly. --- lib/services/livesync/livesync-service.ts | 11 +- test/services/livesync-service.ts | 147 ++++++++++++++++++++++ 2 files changed, 151 insertions(+), 7 deletions(-) create mode 100644 test/services/livesync-service.ts diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 69f112b8af..298288c450 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -17,7 +17,7 @@ const LiveSyncEvents = { export class LiveSyncService extends EventEmitter implements ILiveSyncService { // key is projectDir - private liveSyncProcessesInfo: IDictionary = {}; + protected liveSyncProcessesInfo: IDictionary = {}; constructor(protected $platformService: IPlatformService, private $projectDataService: IProjectDataService, @@ -48,12 +48,10 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { // so we cannot await it as this will cause infinite loop. const shouldAwaitPendingOperation = !stopOptions || stopOptions.shouldAwaitAllActions; - let removedDeviceIdentifiers: string[] = deviceIdentifiers || []; + const deviceIdentifiersToRemove = deviceIdentifiers || _.map(liveSyncProcessInfo.deviceDescriptors, d => d.identifier); - _.each(deviceIdentifiers, deviceId => { - removedDeviceIdentifiers = _.remove(liveSyncProcessInfo.deviceDescriptors, descriptor => descriptor.identifier === deviceId) - .map(deviceDescriptor => deviceDescriptor.identifier); - }); + const removedDeviceIdentifiers = _.remove(liveSyncProcessInfo.deviceDescriptors, descriptor => _.indexOf(deviceIdentifiersToRemove, descriptor.identifier) !== -1) + .map(descriptor => descriptor.identifier); // In case deviceIdentifiers are not passed, we should stop the whole LiveSync. if (!deviceIdentifiers || !deviceIdentifiers.length || !liveSyncProcessInfo.deviceDescriptors || !liveSyncProcessInfo.deviceDescriptors.length) { @@ -72,7 +70,6 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { await liveSyncProcessInfo.actionsChain; } - removedDeviceIdentifiers = _.map(liveSyncProcessInfo.deviceDescriptors, d => d.identifier); liveSyncProcessInfo.deviceDescriptors = []; // Kill typescript watcher diff --git a/test/services/livesync-service.ts b/test/services/livesync-service.ts new file mode 100644 index 0000000000..75d2073bec --- /dev/null +++ b/test/services/livesync-service.ts @@ -0,0 +1,147 @@ +import { Yok } from "../../lib/common/yok"; +import { assert } from "chai"; +import { LiveSyncService } from "../../lib/services/livesync/livesync-service"; +import { LoggerStub } from "../stubs"; + +const createTestInjector = (): IInjector => { + const testInjector = new Yok(); + + testInjector.register("platformService", {}); + testInjector.register("projectDataService", { + getProjectData: (projectDir: string): IProjectData => ({}) + }); + + testInjector.register("devicesService", {}); + testInjector.register("mobileHelper", {}); + testInjector.register("devicePlatformsConstants", {}); + testInjector.register("nodeModulesDependenciesBuilder", {}); + testInjector.register("logger", LoggerStub); + testInjector.register("processService", {}); + testInjector.register("hooksService", { + executeAfterHooks: (commandName: string, hookArguments?: IDictionary): Promise => Promise.resolve() + }); + + testInjector.register("pluginsService", {}); + testInjector.register("injector", testInjector); + + return testInjector; +}; + +class LiveSyncServiceInheritor extends LiveSyncService { + constructor($platformService: IPlatformService, + $projectDataService: IProjectDataService, + $devicesService: Mobile.IDevicesService, + $mobileHelper: Mobile.IMobileHelper, + $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, + $nodeModulesDependenciesBuilder: INodeModulesDependenciesBuilder, + $logger: ILogger, + $processService: IProcessService, + $hooksService: IHooksService, + $pluginsService: IPluginsService, + $injector: IInjector) { + + super( + $platformService, + $projectDataService, + $devicesService, + $mobileHelper, + $devicePlatformsConstants, + $nodeModulesDependenciesBuilder, + $logger, + $processService, + $hooksService, + $pluginsService, + $injector + ); + } + + public liveSyncProcessesInfo: IDictionary = {}; +} + +interface IStopLiveSyncTestCase { + name: string; + currentDeviceIdentifiers: string[]; + expectedDeviceIdentifiers: string[]; + deviceIdentifiersToBeStopped?: string[]; +} + +describe("liveSyncService", () => { + describe("stopLiveSync", () => { + const getLiveSyncProcessInfo = (): ILiveSyncProcessInfo => ({ + actionsChain: Promise.resolve(), + currentSyncAction: Promise.resolve(), + isStopped: false, + timer: setTimeout(() => undefined, 1000), + watcherInfo: { + watcher: { + close: (): any => undefined + }, + pattern: "pattern" + }, + deviceDescriptors: [] + }); + + const getDeviceDescriptor = (identifier: string): ILiveSyncDeviceInfo => ({ + identifier, + outputPath: "", + skipNativePrepare: false, + platformSpecificOptions: null, + buildAction: () => Promise.resolve("") + }); + + const testCases: IStopLiveSyncTestCase[] = [ + { + name: "stops LiveSync operation for all devices and emits liveSyncStopped for all of them when stopLiveSync is called without deviceIdentifiers", + currentDeviceIdentifiers: ["device1", "device2", "device3"], + expectedDeviceIdentifiers: ["device1", "device2", "device3"] + }, + { + name: "stops LiveSync operation for all devices and emits liveSyncStopped for all of them when stopLiveSync is called without deviceIdentifiers (when a single device is attached)", + currentDeviceIdentifiers: ["device1"], + expectedDeviceIdentifiers: ["device1"] + }, + { + name: "stops LiveSync operation for specified devices and emits liveSyncStopped for each of them (when a single device is attached)", + currentDeviceIdentifiers: ["device1"], + expectedDeviceIdentifiers: ["device1"], + deviceIdentifiersToBeStopped: ["device1"] + }, + { + name: "stops LiveSync operation for specified devices and emits liveSyncStopped for each of them", + currentDeviceIdentifiers: ["device1", "device2", "device3"], + expectedDeviceIdentifiers: ["device1", "device3"], + deviceIdentifiersToBeStopped: ["device1", "device3"] + }, + { + name: "does not raise liveSyncStopped event for device, which is not currently being liveSynced", + currentDeviceIdentifiers: ["device1", "device2", "device3"], + expectedDeviceIdentifiers: ["device1"], + deviceIdentifiersToBeStopped: ["device1", "device4"] + } + ]; + + for (const testCase of testCases) { + it(testCase.name, async () => { + const testInjector = createTestInjector(); + const liveSyncService = testInjector.resolve(LiveSyncServiceInheritor); + const projectDir = "projectDir"; + const emittedDeviceIdentifiersForLiveSyncStoppedEvent: string[] = []; + liveSyncService.on("liveSyncStopped", (data: { projectDir: string, deviceIdentifier: string }) => { + assert.equal(data.projectDir, projectDir); + emittedDeviceIdentifiersForLiveSyncStoppedEvent.push(data.deviceIdentifier); + }); + + // Setup liveSyncProcessesInfo for current test + liveSyncService.liveSyncProcessesInfo[projectDir] = getLiveSyncProcessInfo(); + const deviceDescriptors = testCase.currentDeviceIdentifiers.map(d => getDeviceDescriptor(d)); + liveSyncService.liveSyncProcessesInfo[projectDir].deviceDescriptors.push(...deviceDescriptors); + + await liveSyncService.stopLiveSync(projectDir, testCase.deviceIdentifiersToBeStopped); + + assert.deepEqual(emittedDeviceIdentifiersForLiveSyncStoppedEvent, testCase.expectedDeviceIdentifiers); + }); + } + + }); + +}); From 8b7155668b346baefa345bd4cc445c59d55cc8b3 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Tue, 22 Aug 2017 13:09:29 +0300 Subject: [PATCH 2/2] Expose getLiveSyncDeviceDescriptors method Add new public method `getLiveSyncDeviceDescriptors` to `liveSyncService` - its purpose is to get information for current LiveSync operation. --- PublicAPI.md | 23 +++++++++++++++++++++++ lib/definitions/livesync.d.ts | 9 +++++++++ lib/services/livesync/livesync-service.ts | 13 ++++++++++--- test/stubs.ts | 4 ++++ 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/PublicAPI.md b/PublicAPI.md index 6104b7a7c8..dce94a45d4 100644 --- a/PublicAPI.md +++ b/PublicAPI.md @@ -623,6 +623,29 @@ tns.liveSyncService.stopLiveSync(projectDir, deviceIdentifiers) }); ``` +### getLiveSyncDeviceDescriptors +Gives information for currently running LiveSync operation and parameters used to start it on each device. + +* Definition +```TypeScript +/** + * Returns the device information for current LiveSync operation of specified project. + * In case LiveSync has been started on many devices, but stopped for some of them at a later point, + * calling the method after that will return information only for devices for which LiveSync operation is in progress. + * @param {string} projectDir The path to project for which the LiveSync operation is executed + * @returns {ILiveSyncDeviceInfo[]} Array of elements describing parameters used to start LiveSync on each device. +*/ +getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[]; +``` + +* Usage +```JavaScript +const projectDir = "myProjectDir"; +const deviceIdentifiers = [ "4df18f307d8a8f1b", "12318af23ebc0e25" ]; +const currentlyRunningDescriptors = tns.liveSyncService.getLiveSyncDeviceDescriptors(projectDir); +console.log(`LiveSync for ${projectDir} is currently running on the following devices: ${currentlyRunningDescriptors.map(descriptor => descriptor.identifier)}`); +``` + ### Events `liveSyncService` raises several events in order to provide information for current state of the operation. * liveSyncStarted - raised whenever CLI starts a LiveSync operation for specific device. When `liveSync` method is called, the initial LiveSync operation will emit `liveSyncStarted` for each specified device. After that the event will be emitted only in case when liveSync method is called again with different device instances. The event is raised with the following data: diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index ec89823dbe..4216a9bbeb 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -185,6 +185,15 @@ interface ILiveSyncService { * @returns {Promise} */ stopLiveSync(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise; + + /** + * Returns the device information for current LiveSync operation of specified project. + * In case LiveSync has been started on many devices, but stopped for some of them at a later point, + * calling the method after that will return information only for devices for which LiveSync operation is in progress. + * @param {string} projectDir The path to project for which the LiveSync operation is executed + * @returns {ILiveSyncDeviceInfo[]} Array of elements describing parameters used to start LiveSync on each device. + */ + getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[]; } /** diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 298288c450..b8b0829979 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -50,7 +50,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { const deviceIdentifiersToRemove = deviceIdentifiers || _.map(liveSyncProcessInfo.deviceDescriptors, d => d.identifier); - const removedDeviceIdentifiers = _.remove(liveSyncProcessInfo.deviceDescriptors, descriptor => _.indexOf(deviceIdentifiersToRemove, descriptor.identifier) !== -1) + const removedDeviceIdentifiers = _.remove(liveSyncProcessInfo.deviceDescriptors, descriptor => _.includes(deviceIdentifiersToRemove, descriptor.identifier)) .map(descriptor => descriptor.identifier); // In case deviceIdentifiers are not passed, we should stop the whole LiveSync. @@ -90,6 +90,12 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { } } + public getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { + const liveSyncProcessesInfo = this.liveSyncProcessesInfo[projectDir] || {}; + const currentDescriptors = liveSyncProcessesInfo.deviceDescriptors; + return currentDescriptors || []; + } + protected async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo): Promise { const platformLiveSyncService = this.getLiveSyncService(liveSyncResultInfo.deviceAppData.platform); try { @@ -124,7 +130,8 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { const isAlreadyLiveSyncing = this.liveSyncProcessesInfo[projectData.projectDir] && !this.liveSyncProcessesInfo[projectData.projectDir].isStopped; // Prevent cases where liveSync is called consecutive times with the same device, for example [ A, B, C ] and then [ A, B, D ] - we want to execute initialSync only for D. - const deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, this.liveSyncProcessesInfo[projectData.projectDir].deviceDescriptors, deviceDescriptorPrimaryKey) : deviceDescriptors; + const currentlyRunningDeviceDescriptors = this.getLiveSyncDeviceDescriptors(projectData.projectDir); + const deviceDescriptorsForInitialSync = isAlreadyLiveSyncing ? _.differenceBy(deviceDescriptors, currentlyRunningDeviceDescriptors, deviceDescriptorPrimaryKey) : deviceDescriptors; this.setLiveSyncProcessInfo(liveSyncData.projectDir, deviceDescriptors); await this.initialSync(projectData, deviceDescriptorsForInitialSync, liveSyncData); @@ -143,7 +150,7 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService { this.liveSyncProcessesInfo[projectDir].currentSyncAction = this.liveSyncProcessesInfo[projectDir].actionsChain; this.liveSyncProcessesInfo[projectDir].isStopped = false; - const currentDeviceDescriptors = this.liveSyncProcessesInfo[projectDir].deviceDescriptors || []; + const currentDeviceDescriptors = this.getLiveSyncDeviceDescriptors(projectDir); this.liveSyncProcessesInfo[projectDir].deviceDescriptors = _.uniqBy(currentDeviceDescriptors.concat(deviceDescriptors), deviceDescriptorPrimaryKey); } diff --git a/test/stubs.ts b/test/stubs.ts index 1a4902a444..cfb77841db 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -500,6 +500,10 @@ export class LiveSyncServiceStub implements ILiveSyncService { public async stopLiveSync(projectDir: string): Promise { return; } + + public getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] { + return []; + } } export class AndroidToolsInfoStub implements IAndroidToolsInfo {