Skip to content

Commit a47ea31

Browse files
Fix unit test runner
Fix the following issues in unit test runner: * Tests cannot start when application is not installed on device * Karma does not start when there are no .js files in app dir (initial state of TypeScript projects). * `tns test <platform> --watch` is not working for TypeScript projects * `tns test <platform> --debug-brk` does not debug. Change the way unit-tests are started. The current code was: 1) Start karma server 2) When start method of `karma-nativescript-launcher` is called, it spawns new CLI process (calls `tns dev-test` command with some arguments). 3) The new CLI process writes down some files in `node_modules/nativescript-unit-test-runner` 4) The new CLI process prepares the project. 5) The new CLI process changes the entry point of the application to point to nativescript-unit-test-runner's main-page. 6) The new CLI process calls livesync-base which should restart the application. 7) In case `--watch` option is used, karma launcher will listen for `file_list_modified` event and spawn new CLI process to run the tests. Problems were in all the steps. New way: 1) When `tns test <platform>` is called, first step is to prepare the project. 2) Initialize devices service, so we are sure all devices are detected. 3) Prepare livesync data - here we set canExecuteFastSync to false, so any change will restart the application and tests will be started again. 4) Fork new process, which should start karma server. 5) When `karma-nativescript-launcher`'s start method is called in the forked process, it will send required information to current CLI process. 6) CLI process receives the data and writes the required files in `node_modules/nativescript-unit-test-runner`. 7) CLI process calls livesync. In case --debug-brk is specified, debugService is called instead. 8) karma-nativescript-launcher no longer listens for `file-list-modified` event. CLI is alreday doing this (livesync logic). 9) Entry point of application is change by `nativescript-unit-test-runner` via after-prepare hook. The new behavior depends on the livesync - when app is not installed on the device, it will be installed. In case `--watch` is used, livesync will detect changes, prepare the project and restart the app - this will start the tests again. With this change `tns dev-test` command will stop working. As I consider it not-usable, I've deceided to skip its fixing for later.
1 parent 48a1a4e commit a47ea31

File tree

2 files changed

+85
-9
lines changed

2 files changed

+85
-9
lines changed

lib/services/karma-execution.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
///<reference path="../.d.ts"/>
2+
3+
"use strict";
4+
5+
import * as path from "path";
6+
7+
process.on("message", (data: any) => {
8+
if(data.karmaConfig) {
9+
let pathToKarma = path.join(data.karmaConfig.projectDir, 'node_modules/karma'),
10+
KarmaServer = require(path.join(pathToKarma, 'lib/server')),
11+
karma = new KarmaServer(data.karmaConfig);
12+
13+
karma.start();
14+
}
15+
});

lib/services/test-execution-service.ts

+70-9
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,16 @@ class TestExecutionService implements ITestExecutionService {
3232
private $options: IOptions,
3333
private $pluginsService: IPluginsService,
3434
private $errors: IErrors,
35+
private $androidDebugService:IDebugService,
36+
private $iOSDebugService: IDebugService,
3537
private $devicesService: Mobile.IDevicesService) {
3638
}
3739

40+
public platform: string;
41+
3842
public startTestRunner(platform: string) : IFuture<void> {
3943
return (() => {
44+
this.platform = platform;
4045
this.$options.justlaunch = true;
4146
let blockingOperationFuture = new Future<void>();
4247
process.on('message', (launcherConfig: any) => {
@@ -50,7 +55,7 @@ class TestExecutionService implements ITestExecutionService {
5055
let configOptions: IKarmaConfigOptions = JSON.parse(launcherConfig);
5156
this.$options.debugBrk = configOptions.debugBrk;
5257
this.$options.debugTransport = configOptions.debugTransport;
53-
let configJs = this.generateConfig(configOptions);
58+
let configJs = this.generateConfig(this.$options.port.toString(), configOptions);
5459
this.$fs.writeFile(path.join(projectDir, TestExecutionService.CONFIG_FILE_NAME), configJs).wait();
5560

5661
let socketIoJsUrl = `http://localhost:${this.$options.port}/socket.io/socket.io.js`;
@@ -93,12 +98,30 @@ class TestExecutionService implements ITestExecutionService {
9398
public startKarmaServer(platform: string): IFuture<void> {
9499
return (() => {
95100
platform = platform.toLowerCase();
96-
this.$pluginsService.ensureAllDependenciesAreInstalled().wait();
97-
let pathToKarma = path.join(this.$projectData.projectDir, 'node_modules/karma');
98-
let KarmaServer = require(path.join(pathToKarma, 'lib/server'));
99-
if (platform === 'ios' && this.$options.emulator) {
100-
platform = 'ios_simulator';
101+
this.platform = platform;
102+
103+
if(this.$options.debugBrk && this.$options.watch) {
104+
this.$errors.failWithoutHelp("You cannot use --watch and --debug-brk simultaneously. Remove one of the flags and try again.");
105+
}
106+
107+
if (!this.$platformService.preparePlatform(platform).wait()) {
108+
this.$errors.failWithoutHelp("Verify that listed files are well-formed and try again the operation.");
101109
}
110+
111+
let platformData = this.$platformsData.getPlatformData(platform.toLowerCase());
112+
let projectDir = this.$projectData.projectDir;
113+
this.$devicesService.initialize({ platform: platform, deviceId: this.$options.device }).wait();
114+
let projectFilesPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME);
115+
116+
let liveSyncData: ILiveSyncData = {
117+
platform: platform,
118+
appIdentifier: this.$projectData.projectId,
119+
projectFilesPath: projectFilesPath,
120+
syncWorkingDirectory: path.join(projectDir, constants.APP_FOLDER_NAME),
121+
canExecuteFastSync: false, // Always restart the application when change is detected, so tests will be rerun.
122+
excludedProjectDirsAndFiles: ["**/*.js.map", "**/*.ts"]
123+
};
124+
102125
let karmaConfig: any = {
103126
browsers: [platform],
104127
configFile: path.join(this.$projectData.projectDir, 'karma.conf.js'),
@@ -122,8 +145,36 @@ class TestExecutionService implements ITestExecutionService {
122145
if (this.$options.debugBrk) {
123146
karmaConfig.browserNoActivityTimeout = 1000000000;
124147
}
148+
149+
karmaConfig.projectDir = this.$projectData.projectDir;
125150
this.$logger.debug(JSON.stringify(karmaConfig, null, 4));
126-
new KarmaServer(karmaConfig).start();
151+
152+
let karmaRunner = require("child_process").fork(path.join(__dirname, "karma-execution.js"));
153+
karmaRunner.send({karmaConfig: karmaConfig});
154+
karmaRunner.on("message", (karmaData: any) => {
155+
fiberBootstrap.run(() => {
156+
this.$logger.trace("## Unit-testing: Parent process received message", karmaData);
157+
let port: string;
158+
if(karmaData.url) {
159+
port = karmaData.url.port;
160+
let socketIoJsUrl = `http://localhost:${port}/socket.io/socket.io.js`;
161+
let socketIoJs = this.$httpClient.httpRequest(socketIoJsUrl).wait().body;
162+
this.$fs.writeFile(path.join(projectDir, TestExecutionService.SOCKETIO_JS_FILE_NAME), socketIoJs).wait();
163+
}
164+
165+
if(karmaData.launcherConfig) {
166+
let configOptions: IKarmaConfigOptions = JSON.parse(karmaData.launcherConfig);
167+
let configJs = this.generateConfig(port, configOptions);
168+
this.$fs.writeFile(path.join(projectDir, TestExecutionService.CONFIG_FILE_NAME), configJs).wait();
169+
}
170+
171+
if(this.$options.debugBrk) {
172+
this.getDebugService(platform).debug().wait();
173+
} else {
174+
this.$liveSyncServiceBase.sync(liveSyncData).wait();
175+
}
176+
});
177+
});
127178
}).future<void>()();
128179
}
129180

@@ -138,8 +189,7 @@ class TestExecutionService implements ITestExecutionService {
138189
}).future<void>()();
139190
}
140191

141-
private generateConfig(options: any): string {
142-
let port = this.$options.port;
192+
private generateConfig(port: string, options: any): string {
143193
let nics = os.networkInterfaces();
144194
let ips = Object.keys(nics)
145195
.map(nicName => nics[nicName].filter((binding: any) => binding.family === 'IPv4' && !binding.internal)[0])
@@ -154,5 +204,16 @@ class TestExecutionService implements ITestExecutionService {
154204

155205
return 'module.exports = ' + JSON.stringify(config);
156206
}
207+
208+
private getDebugService(platform: string): IDebugService {
209+
let lowerCasedPlatform = platform.toLowerCase();
210+
if(platform.toLowerCase() === this.$devicePlatformsConstants.iOS.toLowerCase()) {
211+
return this.$iOSDebugService;
212+
} else if(lowerCasedPlatform === this.$devicePlatformsConstants.Android.toLowerCase()) {
213+
return this.$androidDebugService;
214+
}
215+
216+
throw new Error(`Invalid platform ${platform}. Valid platforms are ${this.$devicePlatformsConstants.iOS} and ${this.$devicePlatformsConstants.Android}`);
217+
}
157218
}
158219
$injector.register('testExecutionService', TestExecutionService);

0 commit comments

Comments
 (0)