diff --git a/lib/common b/lib/common index 9ea72d51ec..db060b6471 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit 9ea72d51ec24537f15bd6d8f72de0bc0eb20d0cc +Subproject commit db060b647161fc2cf368be86576d2ff2052c627e diff --git a/lib/constants.ts b/lib/constants.ts index d2824b15e2..73ffa7610c 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -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"; diff --git a/lib/services/karma-execution.ts b/lib/services/karma-execution.ts new file mode 100644 index 0000000000..23ba20f8d1 --- /dev/null +++ b/lib/services/karma-execution.ts @@ -0,0 +1,15 @@ +/// + +"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(); + } +}); diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 6a81215557..9795c809b7 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -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()(); diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index b7d2b33c2e..abdbcdafcc 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -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, @@ -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()) { @@ -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); diff --git a/lib/services/test-execution-service.ts b/lib/services/test-execution-service.ts index 254d6f5eb5..a782816ac3 100644 --- a/lib/services/test-execution-service.ts +++ b/lib/services/test-execution-service.ts @@ -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 { return (() => { + this.platform = platform; this.$options.justlaunch = true; let blockingOperationFuture = new Future(); process.on('message', (launcherConfig: any) => { @@ -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`; @@ -93,37 +99,47 @@ class TestExecutionService implements ITestExecutionService { public startKarmaServer(platform: string): IFuture { 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; + 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(); + } else { + this.liveSyncProject(platform).wait(); + } + }); + }); }).future()(); } @@ -138,8 +154,7 @@ class TestExecutionService implements ITestExecutionService { }).future()(); } - 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]) @@ -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 { + 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()(); + } } $injector.register('testExecutionService', TestExecutionService);