Skip to content

Fix stopLiveSync method #3077

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions PublicAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
9 changes: 9 additions & 0 deletions lib/definitions/livesync.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,15 @@ interface ILiveSyncService {
* @returns {Promise<void>}
*/
stopLiveSync(projectDir: string, deviceIdentifiers?: string[], stopOptions?: { shouldAwaitAllActions: boolean }): Promise<void>;

/**
* 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[];
}

/**
Expand Down
22 changes: 13 additions & 9 deletions lib/services/livesync/livesync-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const LiveSyncEvents = {

export class LiveSyncService extends EventEmitter implements ILiveSyncService {
// key is projectDir
private liveSyncProcessesInfo: IDictionary<ILiveSyncProcessInfo> = {};
protected liveSyncProcessesInfo: IDictionary<ILiveSyncProcessInfo> = {};

constructor(protected $platformService: IPlatformService,
private $projectDataService: IProjectDataService,
Expand Down Expand Up @@ -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 => _.includes(deviceIdentifiersToRemove, descriptor.identifier))
.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) {
Expand All @@ -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
Expand All @@ -93,6 +90,12 @@ export class LiveSyncService extends EventEmitter implements ILiveSyncService {
}
}

public getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] {
const liveSyncProcessesInfo = this.liveSyncProcessesInfo[projectDir] || <ILiveSyncProcessInfo>{};
const currentDescriptors = liveSyncProcessesInfo.deviceDescriptors;
return currentDescriptors || [];
}

protected async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo): Promise<void> {
const platformLiveSyncService = this.getLiveSyncService(liveSyncResultInfo.deviceAppData.platform);
try {
Expand Down Expand Up @@ -127,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);
Expand All @@ -146,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);
}

Expand Down
147 changes: 147 additions & 0 deletions test/services/livesync-service.ts
Original file line number Diff line number Diff line change
@@ -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 => (<any>{})
});

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<any>): Promise<void> => 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<ILiveSyncProcessInfo> = {};
}

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: <any>{
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>(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);
});
}

});

});
4 changes: 4 additions & 0 deletions test/stubs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,10 @@ export class LiveSyncServiceStub implements ILiveSyncService {
public async stopLiveSync(projectDir: string): Promise<void> {
return;
}

public getLiveSyncDeviceDescriptors(projectDir: string): ILiveSyncDeviceInfo[] {
return [];
}
}

export class AndroidToolsInfoStub implements IAndroidToolsInfo {
Expand Down