Skip to content

Commit 64683af

Browse files
fix: Test command runs tests twice and often fails on Android
With the new LiveSync with sockets functionality, the `tns test android` command executes the tests twice and often exits with a non-zero exit code. The problem is that the command uses the LiveSync logic to upload the files first, restart the application after that and runs the tests. However, the new LiveSync requires the application to be running on device in order to have a socket for file transfer. When we start the application in order to execute the LiveSync, the tests are run. During the run the LiveSync may end and CLI will restart the app. If this happens during running the tests, Karma will mark them as failure and the next execution may not be triggered. Fix this by creating a temp file on device before starting the LiveSync operation. Runtime will check if the file exists and if it does, it will sleep the application for 30 seconds, i.e. it will not load the JS implementation of the app. During this time CLI will transfer all files, remove the temp file from device and restart the application. Runtime will not find the file and it will load the application.
1 parent 40ecfdf commit 64683af

File tree

7 files changed

+39
-30
lines changed

7 files changed

+39
-30
lines changed

lib/constants.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,6 @@ class ItunesConnectApplicationTypesClass implements IiTunesConnectApplicationTyp
9898
}
9999

100100
export const ItunesConnectApplicationTypes = new ItunesConnectApplicationTypesClass();
101-
export class LiveSyncPaths {
102-
static SYNC_DIR_NAME = "sync";
103-
static REMOVEDSYNC_DIR_NAME = "removedsync";
104-
static FULLSYNC_DIR_NAME = "fullsync";
105-
static IOS_DEVICE_PROJECT_ROOT_PATH = "Library/Application Support/LiveSync";
106-
static IOS_DEVICE_SYNC_ZIP_PATH = "Library/Application Support/LiveSync/sync.zip";
107-
}
108101
export const ANGULAR_NAME = "angular";
109102
export const TYPESCRIPT_NAME = "typescript";
110103
export const BUILD_OUTPUT_EVENT_NAME = "buildOutput";

lib/device-path-provider.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { fromWindowsRelativePathToUnix } from "./common/helpers";
2-
import { APP_FOLDER_NAME, LiveSyncPaths } from "./constants";
2+
import { APP_FOLDER_NAME } from "./constants";
3+
import { LiveSyncPaths } from "./common/constants";
34
import { AndroidDeviceLiveSyncService } from "./services/livesync/android-device-livesync-service";
45
import * as path from "path";
56

@@ -23,7 +24,7 @@ export class DevicePathProvider implements IDevicePathProvider {
2324
projectRoot = path.join(projectRoot, APP_FOLDER_NAME);
2425
}
2526
} else if (this.$mobileHelper.isAndroidPlatform(device.deviceInfo.platform)) {
26-
projectRoot = `/data/local/tmp/${options.appIdentifier}`;
27+
projectRoot = `${LiveSyncPaths.ANDROID_TMP_DIR_NAME}/${options.appIdentifier}`;
2728
if (!options.getDirname) {
2829
const deviceLiveSyncService = this.$injector.resolve<AndroidDeviceLiveSyncService>(AndroidDeviceLiveSyncService, { device });
2930
const hashService = deviceLiveSyncService.getDeviceHashService(options.appIdentifier);

lib/services/android-debug-service.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { sleep } from "../common/helpers";
22
import { DebugServiceBase } from "./debug-service-base";
3+
import { LiveSyncPaths } from "../common/constants";
34

45
export class AndroidDebugService extends DebugServiceBase implements IPlatformDebugService {
56
private _packageName: string;
@@ -158,22 +159,23 @@ export class AndroidDebugService extends DebugServiceBase implements IPlatformDe
158159
await this.device.applicationManager.stopApplication(appData);
159160

160161
if (debugOptions.debugBrk) {
161-
await this.device.adb.executeShellCommand([`cat /dev/null > /data/local/tmp/${appData.appId}-debugbreak`]);
162+
await this.device.adb.executeShellCommand([`cat /dev/null > ${LiveSyncPaths.ANDROID_TMP_DIR_NAME}/${appData.appId}-debugbreak`]);
162163
}
163164

164-
await this.device.adb.executeShellCommand([`cat /dev/null > /data/local/tmp/${appData.appId}-debugger-started`]);
165+
await this.device.adb.executeShellCommand([`cat /dev/null > ${LiveSyncPaths.ANDROID_TMP_DIR_NAME}/${appData.appId}-debugger-started`]);
165166

166167
await this.device.applicationManager.startApplication(appData);
167168

168169
await this.waitForDebugger(appData.appId);
169170
}
170171

171172
private async waitForDebugger(packageName: String): Promise<void> {
172-
const waitText: string = `0 /data/local/tmp/${packageName}-debugger-started`;
173+
const debuggerStartedFilePath = `${LiveSyncPaths.ANDROID_TMP_DIR_NAME}/${packageName}-debugger-started`;
174+
const waitText: string = `0 ${debuggerStartedFilePath}`;
173175
let maxWait = 12;
174176
let debuggerStarted: boolean = false;
175177
while (maxWait > 0 && !debuggerStarted) {
176-
const forwardsResult = await this.device.adb.executeShellCommand(["ls", "-s", `/data/local/tmp/${packageName}-debugger-started`]);
178+
const forwardsResult = await this.device.adb.executeShellCommand(["ls", "-s", debuggerStartedFilePath]);
177179

178180
maxWait--;
179181

lib/services/android-project-service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as semver from "semver";
55
import * as projectServiceBaseLib from "./platform-project-service-base";
66
import { DeviceAndroidDebugBridge } from "../common/mobile/android/device-android-debug-bridge";
77
import { attachAwaitDetach, isRecommendedAarFile } from "../common/helpers";
8-
import { Configurations } from "../common/constants";
8+
import { Configurations, LiveSyncPaths } from "../common/constants";
99
import { SpawnOptions } from "child_process";
1010

1111
export class AndroidProjectService extends projectServiceBaseLib.PlatformProjectServiceBase implements IPlatformProjectService {
@@ -631,7 +631,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
631631

632632
public async cleanDeviceTempFolder(deviceIdentifier: string, projectData: IProjectData): Promise<void> {
633633
const adb = this.$injector.resolve(DeviceAndroidDebugBridge, { identifier: deviceIdentifier });
634-
const deviceRootPath = `/data/local/tmp/${projectData.projectId}`;
634+
const deviceRootPath = `${LiveSyncPaths.ANDROID_TMP_DIR_NAME}/${projectData.projectId}`;
635635
await adb.executeShellCommand(["rm", "-rf", deviceRootPath]);
636636
}
637637

lib/services/livesync/android-device-livesync-service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { DeviceAndroidDebugBridge } from "../../common/mobile/android/device-and
22
import { AndroidDeviceHashService } from "../../common/mobile/android/android-device-hash-service";
33
import { DeviceLiveSyncServiceBase } from "./device-livesync-service-base";
44
import * as helpers from "../../common/helpers";
5-
import { LiveSyncPaths } from "../../constants";
5+
import { LiveSyncPaths } from "../../common/constants";
66
import { cache } from "../../common/decorators";
77
import * as path from "path";
88
import * as net from "net";

lib/services/livesync/android-device-livesync-sockets-service.ts

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import { DeviceAndroidDebugBridge } from "../../common/mobile/android/device-and
22
import { AndroidDeviceHashService } from "../../common/mobile/android/android-device-hash-service";
33
import { DeviceLiveSyncServiceBase } from "./device-livesync-service-base";
44
import { APP_FOLDER_NAME } from "../../constants";
5+
import { LiveSyncPaths } from "../../common/constants";
56
import { AndroidLivesyncTool } from "./android-livesync-tool";
67
import * as path from "path";
8+
import * as temp from "temp";
79

810
export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBase implements IAndroidNativeScriptDeviceLiveSyncService, INativeScriptDeviceLiveSyncService {
911
private livesyncTool: IAndroidLivesyncTool;
@@ -17,54 +19,65 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa
1719
private $logger: ILogger,
1820
protected device: Mobile.IAndroidDevice,
1921
private $options: ICommonOptions,
20-
private $processService: IProcessService) {
22+
private $processService: IProcessService,
23+
private $fs: IFileSystem) {
2124
super($platformsData, device);
2225
this.livesyncTool = this.$injector.resolve(AndroidLivesyncTool);
2326
}
2427

2528
public async beforeLiveSyncAction(deviceAppData: Mobile.IDeviceAppData): Promise<void> {
2629
const platformData = this.$platformsData.getPlatformData(deviceAppData.platform, this.data);
2730
const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME);
31+
const pathToLiveSyncFile = temp.path({ prefix: "livesync" });
32+
this.$fs.writeFile(pathToLiveSyncFile, "");
33+
await this.device.fileSystem.putFile(pathToLiveSyncFile, this.getPathToLiveSyncFileOnDevice(deviceAppData.appIdentifier), deviceAppData.appIdentifier);
2834
await this.device.applicationManager.startApplication({ appId: deviceAppData.appIdentifier, projectName: this.data.projectName });
2935
await this.connectLivesyncTool(projectFilesPath, this.data.projectId);
3036
}
3137

38+
private getPathToLiveSyncFileOnDevice(appIdentifier: string): string {
39+
return `${LiveSyncPaths.ANDROID_TMP_DIR_NAME}/${appIdentifier}-livesync-in-progress`;
40+
}
41+
3242
public async finalizeSync(liveSyncInfo: ILiveSyncResultInfo) {
3343
await this.doSync(liveSyncInfo);
3444
}
3545

36-
private async doSync(liveSyncInfo: ILiveSyncResultInfo, {doRefresh = false}: {doRefresh?: boolean} = {}): Promise<IAndroidLivesyncSyncOperationResult> {
46+
private async doSync(liveSyncInfo: ILiveSyncResultInfo, { doRefresh = false }: { doRefresh?: boolean } = {}): Promise<IAndroidLivesyncSyncOperationResult> {
3747
const operationId = this.livesyncTool.generateOperationIdentifier();
3848

39-
let result = {operationId, didRefresh: true };
49+
let result = { operationId, didRefresh: true };
4050

4151
if (liveSyncInfo.modifiedFilesData.length) {
4252

4353
const doSyncPromise = this.livesyncTool.sendDoSyncOperation(doRefresh, null, operationId);
4454

45-
const syncInterval : NodeJS.Timer = setInterval(() => {
55+
const syncInterval: NodeJS.Timer = setInterval(() => {
4656
if (this.livesyncTool.isOperationInProgress(operationId)) {
4757
this.$logger.info("Sync operation in progress...");
4858
}
4959
}, AndroidDeviceSocketsLiveSyncService.STATUS_UPDATE_INTERVAL);
5060

51-
const clearSyncInterval = () => {
61+
const actionOnEnd = async () => {
5262
clearInterval(syncInterval);
63+
await this.device.fileSystem.deleteFile(this.getPathToLiveSyncFileOnDevice(liveSyncInfo.deviceAppData.appIdentifier), liveSyncInfo.deviceAppData.appIdentifier);
5364
};
5465

55-
this.$processService.attachToProcessExitSignals(this, clearSyncInterval);
56-
doSyncPromise.then(clearSyncInterval, clearSyncInterval);
66+
this.$processService.attachToProcessExitSignals(this, actionOnEnd);
67+
doSyncPromise.then(actionOnEnd, actionOnEnd);
5768

5869
result = await doSyncPromise;
5970
}
6071

72+
await this.device.fileSystem.deleteFile(this.getPathToLiveSyncFileOnDevice(liveSyncInfo.deviceAppData.appIdentifier), liveSyncInfo.deviceAppData.appIdentifier);
73+
6174
return result;
6275
}
6376

6477
public async refreshApplication(projectData: IProjectData, liveSyncInfo: ILiveSyncResultInfo) {
6578
const canExecuteFastSync = !liveSyncInfo.isFullSync && this.canExecuteFastSyncForPaths(liveSyncInfo.modifiedFilesData, projectData, this.device.deviceInfo.platform);
6679

67-
const syncOperationResult = await this.doSync(liveSyncInfo, {doRefresh: canExecuteFastSync});
80+
const syncOperationResult = await this.doSync(liveSyncInfo, { doRefresh: canExecuteFastSync });
6881

6982
this.livesyncTool.end();
7083

@@ -96,28 +109,28 @@ export class AndroidDeviceSocketsLiveSyncService extends DeviceLiveSyncServiceBa
96109
}
97110

98111
private async _transferDirectory(deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[], projectFilesPath: string): Promise<Mobile.ILocalToDevicePathData[]> {
99-
let transferredLocalToDevicePaths : Mobile.ILocalToDevicePathData[];
112+
let transferredLocalToDevicePaths: Mobile.ILocalToDevicePathData[];
100113
const deviceHashService = this.getDeviceHashService(deviceAppData.appIdentifier);
101114
const currentShasums: IStringDictionary = await deviceHashService.generateHashesFromLocalToDevicePaths(localToDevicePaths);
102115
const oldShasums = await deviceHashService.getShasumsFromDevice();
103116

104117
if (this.$options.force || !oldShasums) {
105118
await this.livesyncTool.sendDirectory(projectFilesPath);
106119
await deviceHashService.uploadHashFileToDevice(currentShasums);
107-
transferredLocalToDevicePaths = localToDevicePaths;
120+
transferredLocalToDevicePaths = localToDevicePaths;
108121
} else {
109122
const changedShasums = deviceHashService.getChangedShasums(oldShasums, currentShasums);
110123
const changedFiles = _.keys(changedShasums);
111124
if (changedFiles.length) {
112125
await this.livesyncTool.sendFiles(changedFiles);
113126
await deviceHashService.uploadHashFileToDevice(currentShasums);
114-
transferredLocalToDevicePaths = localToDevicePaths.filter(localToDevicePathData => changedFiles.indexOf(localToDevicePathData.getLocalPath()) >= 0);
127+
transferredLocalToDevicePaths = localToDevicePaths.filter(localToDevicePathData => changedFiles.indexOf(localToDevicePathData.getLocalPath()) >= 0);
115128
} else {
116-
transferredLocalToDevicePaths = [];
129+
transferredLocalToDevicePaths = [];
117130
}
118131
}
119132

120-
return transferredLocalToDevicePaths ;
133+
return transferredLocalToDevicePaths;
121134
}
122135

123136
private async connectLivesyncTool(projectFilesPath: string, appIdentifier: string) {

0 commit comments

Comments
 (0)