Skip to content

Fix unit test runner #1545

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 1 commit into from
Feb 26, 2016
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
2 changes: 1 addition & 1 deletion lib/common
Submodule common updated 1 files
+1 −1 child-process.ts
1 change: 1 addition & 0 deletions lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export let DEFAULT_APP_IDENTIFIER_PREFIX = "org.nativescript";
export var LIVESYNC_EXCLUDED_DIRECTORIES = ["app_resources"];
export var TESTING_FRAMEWORKS = ['jasmine', 'mocha', 'qunit'];
export let TEST_RUNNER_NAME = "nativescript-unit-test-runner";
export let LIVESYNC_EXCLUDED_FILE_PATTERNS = ["**/*.js.map", "**/*.ts"];

export class ReleaseType {
static MAJOR = "major";
Expand Down
15 changes: 15 additions & 0 deletions lib/services/karma-execution.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
///<reference path="../.d.ts"/>

"use strict";

import * as path from "path";

process.on("message", (data: any) => {
if(data.karmaConfig) {
let pathToKarma = path.join(data.karmaConfig.projectDir, 'node_modules/karma'),
KarmaServer = require(path.join(pathToKarma, 'lib/server')),
karma = new KarmaServer(data.karmaConfig);

karma.start();
}
});
2 changes: 1 addition & 1 deletion lib/services/livesync/livesync-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class LiveSyncService implements ILiveSyncService {
appIdentifier: this.$projectData.projectId,
projectFilesPath: path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME),
syncWorkingDirectory: path.join(this.$projectData.projectDir, constants.APP_FOLDER_NAME),
excludedProjectDirsAndFiles: ["**/*.js.map", "**/*.ts"]
excludedProjectDirsAndFiles: constants.LIVESYNC_EXCLUDED_FILE_PATTERNS
};
this.$liveSyncServiceBase.sync(liveSyncData).wait();
}).future<void>()();
Expand Down
9 changes: 2 additions & 7 deletions lib/services/platform-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@ import Future = require("fibers/future");
let clui = require("clui");

export class PlatformService implements IPlatformService {
private static TNS_MODULES_FOLDER_NAME = "tns_modules";
private static EXCLUDE_FILES_PATTERN = [
"**/*.js.map",
"**/*.ts"
];

constructor(private $devicesService: Mobile.IDevicesService,
private $errors: IErrors,
Expand Down Expand Up @@ -226,7 +221,7 @@ export class PlatformService implements IPlatformService {
this.$xmlValidator.validateXmlFiles(sourceFiles).wait();

// Remove .ts and .js.map files
PlatformService.EXCLUDE_FILES_PATTERN.forEach(pattern => sourceFiles = sourceFiles.filter(file => !minimatch(file, pattern, {nocase: true})));
constants.LIVESYNC_EXCLUDED_FILE_PATTERNS.forEach(pattern => sourceFiles = sourceFiles.filter(file => !minimatch(file, pattern, {nocase: true})));
let copyFileFutures = sourceFiles.map(source => {
let destinationPath = path.join(appDestinationDirectoryPath, path.relative(appSourceDirectoryPath, source));
if (this.$fs.getFsStats(source).wait().isDirectory()) {
Expand All @@ -250,7 +245,7 @@ export class PlatformService implements IPlatformService {
// Process node_modules folder
let appDir = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME);
try {
let tnsModulesDestinationPath = path.join(appDir, PlatformService.TNS_MODULES_FOLDER_NAME);
let tnsModulesDestinationPath = path.join(appDir, constants.TNS_MODULES_FOLDER_NAME);
this.$broccoliBuilder.prepareNodeModules(tnsModulesDestinationPath, platform, lastModifiedTime).wait();
} catch(error) {
this.$logger.debug(error);
Expand Down
141 changes: 108 additions & 33 deletions lib/services/test-execution-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,17 @@ class TestExecutionService implements ITestExecutionService {
private $options: IOptions,
private $pluginsService: IPluginsService,
private $errors: IErrors,
private $devicesService: Mobile.IDevicesService) {
private $androidDebugService:IDebugService,
private $iOSDebugService: IDebugService,
private $devicesService: Mobile.IDevicesService,
private $childProcess: IChildProcess) {
}

public platform: string;

public startTestRunner(platform: string) : IFuture<void> {
return (() => {
this.platform = platform;
this.$options.justlaunch = true;
let blockingOperationFuture = new Future<void>();
process.on('message', (launcherConfig: any) => {
Expand All @@ -50,7 +56,7 @@ class TestExecutionService implements ITestExecutionService {
let configOptions: IKarmaConfigOptions = JSON.parse(launcherConfig);
this.$options.debugBrk = configOptions.debugBrk;
this.$options.debugTransport = configOptions.debugTransport;
let configJs = this.generateConfig(configOptions);
let configJs = this.generateConfig(this.$options.port.toString(), configOptions);
this.$fs.writeFile(path.join(projectDir, TestExecutionService.CONFIG_FILE_NAME), configJs).wait();

let socketIoJsUrl = `http://localhost:${this.$options.port}/socket.io/socket.io.js`;
Expand Down Expand Up @@ -93,37 +99,47 @@ class TestExecutionService implements ITestExecutionService {
public startKarmaServer(platform: string): IFuture<void> {
return (() => {
platform = platform.toLowerCase();
this.$pluginsService.ensureAllDependenciesAreInstalled().wait();
let pathToKarma = path.join(this.$projectData.projectDir, 'node_modules/karma');
let KarmaServer = require(path.join(pathToKarma, 'lib/server'));
if (platform === 'ios' && this.$options.emulator) {
platform = 'ios_simulator';
}
let karmaConfig: any = {
browsers: [platform],
configFile: path.join(this.$projectData.projectDir, 'karma.conf.js'),
_NS: {
log: this.$logger.getLevel(),
path: this.$options.path,
tns: process.argv[1],
node: process.execPath,
options: {
debugTransport: this.$options.debugTransport,
debugBrk: this.$options.debugBrk,
}
},
};
if (this.$config.DEBUG || this.$logger.getLevel() === 'TRACE') {
karmaConfig.logLevel = 'DEBUG';
}
if (!this.$options.watch) {
karmaConfig.singleRun = true;
this.platform = platform;

if(this.$options.debugBrk && this.$options.watch) {
this.$errors.failWithoutHelp("You cannot use --watch and --debug-brk simultaneously. Remove one of the flags and try again.");
}
if (this.$options.debugBrk) {
karmaConfig.browserNoActivityTimeout = 1000000000;

if (!this.$platformService.preparePlatform(platform).wait()) {
this.$errors.failWithoutHelp("Verify that listed files are well-formed and try again the operation.");
}
this.$logger.debug(JSON.stringify(karmaConfig, null, 4));
new KarmaServer(karmaConfig).start();

let projectDir = this.$projectData.projectDir;
this.$devicesService.initialize({ platform: platform, deviceId: this.$options.device }).wait();

let karmaConfig = this.getKarmaConfiguration(platform),
karmaRunner = this.$childProcess.fork(path.join(__dirname, "karma-execution.js"));

karmaRunner.send({karmaConfig: karmaConfig});
karmaRunner.on("message", (karmaData: any) => {
fiberBootstrap.run(() => {
this.$logger.trace("## Unit-testing: Parent process received message", karmaData);
let port: string;
if(karmaData.url) {
port = karmaData.url.port;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using hostname here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need the port for configuration below (next if). However, I've replaced the hard-coded localhost:${port} with host which contains both hostname and port (check next line)

let socketIoJsUrl = `http://${karmaData.url.host}/socket.io/socket.io.js`;
let socketIoJs = this.$httpClient.httpRequest(socketIoJsUrl).wait().body;
this.$fs.writeFile(path.join(projectDir, TestExecutionService.SOCKETIO_JS_FILE_NAME), socketIoJs).wait();
}

if(karmaData.launcherConfig) {
let configOptions: IKarmaConfigOptions = JSON.parse(karmaData.launcherConfig);
let configJs = this.generateConfig(port, configOptions);
this.$fs.writeFile(path.join(projectDir, TestExecutionService.CONFIG_FILE_NAME), configJs).wait();
}

if(this.$options.debugBrk) {
this.getDebugService(platform).debug().wait();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we do not need LiveSync in this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I've understood your point here, but I've moved the generation of LiveSync data and calling LiveSync service in a separate method that is called only in the else statement.

} else {
this.liveSyncProject(platform).wait();
}
});
});
}).future<void>()();
}

Expand All @@ -138,8 +154,7 @@ class TestExecutionService implements ITestExecutionService {
}).future<void>()();
}

private generateConfig(options: any): string {
let port = this.$options.port;
private generateConfig(port: string, options: any): string {
let nics = os.networkInterfaces();
let ips = Object.keys(nics)
.map(nicName => nics[nicName].filter((binding: any) => binding.family === 'IPv4' && !binding.internal)[0])
Expand All @@ -154,5 +169,65 @@ class TestExecutionService implements ITestExecutionService {

return 'module.exports = ' + JSON.stringify(config);
}

private getDebugService(platform: string): IDebugService {
let lowerCasedPlatform = platform.toLowerCase();
if(lowerCasedPlatform === this.$devicePlatformsConstants.iOS.toLowerCase()) {
return this.$iOSDebugService;
} else if(lowerCasedPlatform === this.$devicePlatformsConstants.Android.toLowerCase()) {
return this.$androidDebugService;
}

throw new Error(`Invalid platform ${platform}. Valid platforms are ${this.$devicePlatformsConstants.iOS} and ${this.$devicePlatformsConstants.Android}`);
}

private getKarmaConfiguration(platform: string): any {
let karmaConfig: any = {
browsers: [platform],
configFile: path.join(this.$projectData.projectDir, 'karma.conf.js'),
_NS: {
log: this.$logger.getLevel(),
path: this.$options.path,
tns: process.argv[1],
node: process.execPath,
options: {
debugTransport: this.$options.debugTransport,
debugBrk: this.$options.debugBrk,
}
},
};
if (this.$config.DEBUG || this.$logger.getLevel() === 'TRACE') {
karmaConfig.logLevel = 'DEBUG';
}
if (!this.$options.watch) {
karmaConfig.singleRun = true;
}
if (this.$options.debugBrk) {
karmaConfig.browserNoActivityTimeout = 1000000000;
}

karmaConfig.projectDir = this.$projectData.projectDir;
this.$logger.debug(JSON.stringify(karmaConfig, null, 4));

return karmaConfig;
}

private liveSyncProject(platform: string): IFuture<void> {
return (() => {
let platformData = this.$platformsData.getPlatformData(platform.toLowerCase()),
projectFilesPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME);

let liveSyncData: ILiveSyncData = {
platform: platform,
appIdentifier: this.$projectData.projectId,
projectFilesPath: projectFilesPath,
syncWorkingDirectory: path.join(this.$projectData.projectDir, constants.APP_FOLDER_NAME),
canExecuteFastSync: false, // Always restart the application when change is detected, so tests will be rerun.
excludedProjectDirsAndFiles: constants.LIVESYNC_EXCLUDED_FILE_PATTERNS
};

this.$liveSyncServiceBase.sync(liveSyncData).wait();
}).future<void>()();
}
}
$injector.register('testExecutionService', TestExecutionService);