From 4f99c0eb3c990a5e4e90e1b741d8c49a22d902be Mon Sep 17 00:00:00 2001 From: Bart van der Schoor Date: Sat, 1 Mar 2014 02:03:01 +0100 Subject: [PATCH] tester refactored & handle removed files abort test if a referred file is removed reworked main promise flow moved code from runner to index.ts moved code from runner to print.ts added logic to handle file removal to index.ts expanded state in index.ts dropped property File::formatName --- _infrastructure/tests/runner.js | 443 ++++++++++-------- _infrastructure/tests/runner.ts | 215 ++------- _infrastructure/tests/src/changes.ts | 10 +- _infrastructure/tests/src/file.ts | 12 +- _infrastructure/tests/src/index.ts | 130 ++++- _infrastructure/tests/src/printer.ts | 92 +++- _infrastructure/tests/src/suite/syntax.ts | 2 +- _infrastructure/tests/src/suite/testEval.ts | 2 +- _infrastructure/tests/src/suite/tscParams.ts | 4 +- .../tests/typings/lazy.js/lazy.js.d.ts | 4 +- 10 files changed, 512 insertions(+), 402 deletions(-) diff --git a/_infrastructure/tests/runner.js b/_infrastructure/tests/runner.js index e3eca657f..c6ab61c8a 100644 --- a/_infrastructure/tests/runner.js +++ b/_infrastructure/tests/runner.js @@ -52,7 +52,6 @@ var DT; this.ext = path.extname(this.filePathWithName); this.file = path.basename(this.filePathWithName, this.ext); this.dir = path.dirname(this.filePathWithName); - this.formatName = path.join(this.dir, this.file + this.ext); this.fullPath = path.join(this.baseDir, this.dir, this.file + this.ext); // lock it (shallow) (needs `use strict` in each file to work) // Object.freeze(this); @@ -202,6 +201,7 @@ var DT; var fs = require('fs'); var path = require('path'); + var glob = require('glob'); var Lazy = require('lazy.js'); var Promise = require('bluebird'); @@ -211,7 +211,8 @@ var DT; // Track all files in the repo: map full path to File objects ///////////////////////////////// var FileIndex = (function () { - function FileIndex(options) { + function FileIndex(runner, options) { + this.runner = runner; this.options = options; } FileIndex.prototype.hasFile = function (target) { @@ -225,14 +226,76 @@ var DT; return null; }; - FileIndex.prototype.parseFiles = function (files) { + FileIndex.prototype.setFile = function (file) { + if (file.fullPath in this.fileMap) { + throw new Error('cannot overwrite file'); + } + this.fileMap[file.fullPath] = file; + }; + + FileIndex.prototype.readIndex = function () { + var _this = this; + this.fileMap = Object.create(null); + + return Promise.promisify(glob).call(glob, '**/*.ts', { + cwd: this.runner.dtPath + }).then(function (filesNames) { + _this.files = Lazy(filesNames).filter(function (fileName) { + return _this.runner.checkAcceptFile(fileName); + }).map(function (fileName) { + var file = new DT.File(_this.runner.dtPath, fileName); + _this.fileMap[file.fullPath] = file; + return file; + }).toArray(); + }); + }; + + FileIndex.prototype.collectDiff = function (changes) { + var _this = this; + return new Promise(function (resolve) { + // filter changes and bake map for easy lookup + _this.changed = Object.create(null); + _this.removed = Object.create(null); + + Lazy(changes).filter(function (full) { + return _this.runner.checkAcceptFile(full); + }).uniq().each(function (local) { + var full = path.resolve(_this.runner.dtPath, local); + var file = _this.getFile(full); + if (!file) { + // TODO figure out what to do here + // what does it mean? deleted?ss + file = new DT.File(_this.runner.dtPath, local); + _this.setFile(file); + _this.removed[full] = file; + // console.log('not in index? %', file.fullPath); + } else { + _this.changed[full] = file; + } + }); + + // console.log('changed:\n' + Object.keys(this.changed).join('\n')); + // console.log('removed:\n' + Object.keys(this.removed).join('\n')); + resolve(); + }); + }; + + FileIndex.prototype.parseFiles = function () { + var _this = this; + return this.loadReferences(this.files).then(function () { + return _this.getMissingReferences(); + }); + }; + + FileIndex.prototype.getMissingReferences = function () { var _this = this; return Promise.attempt(function () { - _this.fileMap = Object.create(null); - files.forEach(function (file) { - _this.fileMap[file.fullPath] = file; + _this.missing = Object.create(null); + Lazy(_this.removed).keys().each(function (removed) { + if (removed in _this.refMap) { + _this.missing[removed] = _this.refMap[removed]; + } }); - return _this.loadReferences(files); }); }; @@ -262,6 +325,19 @@ var DT; } }; next(); + }).then(function () { + // bake reverse reference map (referenced to referrers) + _this.refMap = Object.create(null); + + Lazy(files).each(function (file) { + Lazy(file.references).each(function (ref) { + if (ref.fullPath in _this.refMap) { + _this.refMap[ref.fullPath].push(file); + } else { + _this.refMap[ref.fullPath] = [file]; + } + }); + }); }); }; @@ -287,11 +363,43 @@ var DT; return file; }); }; + + FileIndex.prototype.collectTargets = function () { + var _this = this; + return new Promise(function (resolve) { + // map out files linked to changes + // - queue holds files touched by a change + // - pre-fill with actually changed files + // - loop queue, if current not seen: + // - add to result + // - from refMap queue all files referring to current + var result = Object.create(null); + var queue = Lazy(_this.changed).values().toArray(); + + while (queue.length > 0) { + var next = queue.shift(); + var fp = next.fullPath; + if (result[fp]) { + continue; + } + result[fp] = next; + if (fp in _this.refMap) { + var arr = _this.refMap[fp]; + for (var i = 0, ii = arr.length; i < ii; i++) { + // just add it and skip expensive checks + queue.push(arr[i]); + } + } + } + resolve(Lazy(result).values().toArray()); + }); + }; return FileIndex; })(); DT.FileIndex = FileIndex; })(DT || (DT = {})); /// +/// var DT; (function (DT) { 'use strict'; @@ -302,11 +410,10 @@ var DT; var Promise = require('bluebird'); var GitChanges = (function () { - function GitChanges(baseDir) { - this.baseDir = baseDir; + function GitChanges(runner) { + this.runner = runner; this.options = {}; - this.paths = []; - var dir = path.join(baseDir, '.git'); + var dir = path.join(this.runner.dtPath, '.git'); if (!fs.existsSync(dir)) { throw new Error('cannot locate git-dir: ' + dir); } @@ -315,12 +422,11 @@ var DT; this.git = new Git(this.options); this.git.exec = Promise.promisify(this.git.exec); } - GitChanges.prototype.getChanges = function () { - var _this = this; + GitChanges.prototype.readChanges = function () { var opts = {}; var args = ['--name-only HEAD~1']; return this.git.exec('diff', opts, args).then(function (msg) { - _this.paths = msg.replace(/^\s+/, '').replace(/\s+$/, '').split(/\r?\n/g); + return msg.replace(/^\s+/, '').replace(/\s+$/, '').split(/\r?\n/g); }); }; return GitChanges; @@ -393,7 +499,7 @@ var DT; }; Print.prototype.printErrorsForFile = function (testResult) { - this.out('----------------- For file:' + testResult.targetFile.formatName); + this.out('----------------- For file:' + testResult.targetFile.filePathWithName); this.printBreak().out(testResult.stderr).printBreak(); }; @@ -471,6 +577,101 @@ var DT; }); } }; + + Print.prototype.printTestComplete = function (testResult, index) { + var reporter = testResult.hostedBy.testReporter; + if (testResult.success) { + reporter.printPositiveCharacter(index, testResult); + } else { + reporter.printNegativeCharacter(index, testResult); + } + }; + + Print.prototype.printSuiteComplete = function (suite) { + this.printBreak(); + + this.printDiv(); + this.printElapsedTime(suite.timer.asString, suite.timer.time); + this.printSuccessCount(suite.okTests.length, suite.testResults.length); + this.printFailedCount(suite.ngTests.length, suite.testResults.length); + }; + + Print.prototype.printTests = function (adding) { + var _this = this; + this.printDiv(); + this.printSubHeader('Testing'); + this.printDiv(); + + Object.keys(adding).sort().map(function (src) { + _this.printLine(adding[src].filePathWithName); + return adding[src]; + }); + }; + + Print.prototype.printFiles = function (files) { + var _this = this; + this.printDiv(); + this.printSubHeader('Files:'); + this.printDiv(); + + files.forEach(function (file) { + _this.printLine(file.filePathWithName); + file.references.forEach(function (file) { + _this.printElement(file.filePathWithName); + }); + }); + }; + + Print.prototype.printMissing = function (index, refMap) { + var _this = this; + this.printDiv(); + this.printSubHeader('Missing references'); + this.printDiv(); + + Object.keys(refMap).sort().forEach(function (src) { + var ref = index.getFile(src); + _this.printLine('\33[31m\33[1m' + ref.filePathWithName + '\33[0m'); + refMap[src].forEach(function (file) { + _this.printElement(file.filePathWithName); + }); + }); + }; + + Print.prototype.printAllChanges = function (paths) { + var _this = this; + this.printSubHeader('All changes'); + this.printDiv(); + + paths.sort().forEach(function (line) { + _this.printLine(line); + }); + }; + + Print.prototype.printRelChanges = function (changeMap) { + var _this = this; + this.printDiv(); + this.printSubHeader('Relevant changes'); + this.printDiv(); + + Object.keys(changeMap).sort().forEach(function (src) { + _this.printLine(changeMap[src].filePathWithName); + }); + }; + + Print.prototype.printRefMap = function (index, refMap) { + var _this = this; + this.printDiv(); + this.printSubHeader('Referring'); + this.printDiv(); + + Object.keys(refMap).sort().forEach(function (src) { + var ref = index.getFile(src); + _this.printLine(ref.filePathWithName); + refMap[src].forEach(function (file) { + _this.printLine(' - ' + file.filePathWithName); + }); + }); + }; return Print; })(); DT.Print = Print; @@ -607,7 +808,7 @@ var DT; } SyntaxChecking.prototype.filterTargetFiles = function (files) { return Promise.cast(files.filter(function (file) { - return endDts.test(file.formatName); + return endDts.test(file.filePathWithName); })); }; return SyntaxChecking; @@ -634,7 +835,7 @@ var DT; } TestEval.prototype.filterTargetFiles = function (files) { return Promise.cast(files.filter(function (file) { - return endTestDts.test(file.formatName); + return endTestDts.test(file.filePathWithName); })); }; return TestEval; @@ -664,7 +865,7 @@ var DT; this.testReporter = { printPositiveCharacter: function (index, testResult) { - _this.print.clearCurrentLine().printTypingsWithoutTestName(testResult.targetFile.formatName); + _this.print.clearCurrentLine().printTypingsWithoutTestName(testResult.targetFile.filePathWithName); }, printNegativeCharacter: function (index, testResult) { } @@ -680,7 +881,7 @@ var DT; FindNotRequiredTscparams.prototype.runTest = function (targetFile) { var _this = this; - this.print.clearCurrentLine().out(targetFile.formatName); + this.print.clearCurrentLine().out(targetFile.filePathWithName); return new DT.Test(this, targetFile, { tscVersion: this.options.tscVersion, @@ -729,7 +930,6 @@ var DT; var fs = require('fs'); var path = require('path'); - var glob = require('glob'); var assert = require('assert'); var tsExp = /\.ts$/; @@ -792,8 +992,9 @@ var DT; this.suites = []; this.options.findNotRequiredTscparams = !!this.options.findNotRequiredTscparams; - this.index = new DT.FileIndex(this.options); - this.changes = new DT.GitChanges(this.dtPath); + this.index = new DT.FileIndex(this, this.options); + this.changes = new DT.GitChanges(this); + this.print = new DT.Print(this.options.tscVersion); } TestRunner.prototype.addSuite = function (suite) { @@ -813,102 +1014,37 @@ var DT; this.timer = new DT.Timer(); this.timer.start(); + this.print.printChangeHeader(); + // only includes .d.ts or -tests.ts or -test.ts or .ts - return Promise.promisify(glob).call(glob, '**/*.ts', { - cwd: dtPath - }).then(function (filesNames) { - _this.files = Lazy(filesNames).filter(function (fileName) { - return _this.checkAcceptFile(fileName); - }).map(function (fileName) { - return new DT.File(dtPath, fileName); - }).toArray(); - - _this.print.printChangeHeader(); - - return _this.changes.getChanges().then(function () { - _this.printAllChanges(_this.changes.paths); - }); + return this.index.readIndex().then(function () { + return _this.changes.readChanges(); + }).then(function (changes) { + _this.print.printAllChanges(changes); + return _this.index.collectDiff(changes); }).then(function () { - return _this.index.parseFiles(_this.files).then(function () { - // this.printFiles(this.files); - }); + return _this.index.parseFiles(); }).then(function () { - return _this.collectTargets(_this.files, _this.changes.paths); - }).then(function (files) { - return _this.runTests(files); - }).then(function () { - return !_this.suites.some(function (suite) { - return suite.ngTests.length !== 0; - }); - }); - }; + // this.print.printRefMap(this.index, this.index.refMap); + if (Lazy(_this.index.missing).some(function (arr) { + return arr.length > 0; + })) { + _this.print.printMissing(_this.index, _this.index.missing); + _this.print.printBoldDiv(); - TestRunner.prototype.collectTargets = function (files, changes) { - var _this = this; - return new Promise(function (resolve) { - // filter changes and bake map for easy lookup - var changeMap = Object.create(null); - - Lazy(changes).filter(function (full) { - return _this.checkAcceptFile(full); - }).map(function (local) { - return path.resolve(_this.dtPath, local); - }).each(function (full) { - var file = _this.index.getFile(full); - if (!file) { - // TODO figure out what to do here - // what does it mean? deleted? - console.log('not in index: ' + full); - return; - } - changeMap[full] = file; - }); - - _this.printRelChanges(changeMap); - - // bake reverse reference map (referenced to referrers) - var refMap = Object.create(null); - - Lazy(files).each(function (file) { - Lazy(file.references).each(function (ref) { - if (ref.fullPath in refMap) { - refMap[ref.fullPath].push(file); - } else { - refMap[ref.fullPath] = [file]; - } - }); - }); - - // this.printRefMap(refMap); - // map out files linked to changes - // - queue holds files touched by a change - // - pre-fill with actually changed files - // - loop queue, if current not seen: - // - add to result - // - from refMap queue all files referring to current - var adding = Object.create(null); - var queue = Lazy(changeMap).values().toArray(); - - while (queue.length > 0) { - var next = queue.shift(); - var fp = next.fullPath; - if (adding[fp]) { - continue; - } - adding[fp] = next; - if (fp in refMap) { - var arr = refMap[fp]; - for (var i = 0, ii = arr.length; i < ii; i++) { - // just add it and skip expensive checks - queue.push(arr[i]); - } - } + // bail + return Promise.delay(500).return(false); } - _this.printTests(adding); - - var result = Lazy(adding).values().toArray(); - resolve(result); + // this.print.printFiles(this.files); + return _this.index.collectTargets().then(function (files) { + // this.print.printTests(files); + return _this.runTests(files); + }).then(function () { + return !_this.suites.some(function (suite) { + return suite.ngTests.length !== 0; + }); + }); }); }; @@ -944,10 +1080,10 @@ var DT; return suite.filterTargetFiles(files).then(function (targetFiles) { return suite.start(targetFiles, function (testResult, index) { - _this.printTestComplete(testResult, index); + _this.print.printTestComplete(testResult, index); }); }).then(function (suite) { - _this.printSuiteComplete(suite); + _this.print.printSuiteComplete(suite); return count++; }); }, 0); @@ -957,85 +1093,6 @@ var DT; }); }; - TestRunner.prototype.printTests = function (adding) { - var _this = this; - this.print.printDiv(); - this.print.printSubHeader('Testing'); - this.print.printDiv(); - - Object.keys(adding).sort().map(function (src) { - _this.print.printLine(adding[src].formatName); - return adding[src]; - }); - }; - TestRunner.prototype.printFiles = function (files) { - var _this = this; - this.print.printDiv(); - this.print.printSubHeader('Files:'); - this.print.printDiv(); - - files.forEach(function (file) { - _this.print.printLine(file.filePathWithName); - file.references.forEach(function (file) { - _this.print.printElement(file.filePathWithName); - }); - }); - }; - - TestRunner.prototype.printAllChanges = function (paths) { - var _this = this; - this.print.printSubHeader('All changes'); - this.print.printDiv(); - - paths.sort().forEach(function (line) { - _this.print.printLine(line); - }); - }; - - TestRunner.prototype.printRelChanges = function (changeMap) { - var _this = this; - this.print.printDiv(); - this.print.printSubHeader('Relevant changes'); - this.print.printDiv(); - - Object.keys(changeMap).sort().forEach(function (src) { - _this.print.printLine(changeMap[src].formatName); - }); - }; - - TestRunner.prototype.printRefMap = function (refMap) { - var _this = this; - this.print.printDiv(); - this.print.printSubHeader('Referring'); - this.print.printDiv(); - - Object.keys(refMap).sort().forEach(function (src) { - var ref = _this.index.getFile(src); - _this.print.printLine(ref.formatName); - refMap[src].forEach(function (file) { - _this.print.printLine(' - ' + file.formatName); - }); - }); - }; - - TestRunner.prototype.printTestComplete = function (testResult, index) { - var reporter = testResult.hostedBy.testReporter; - if (testResult.success) { - reporter.printPositiveCharacter(index, testResult); - } else { - reporter.printNegativeCharacter(index, testResult); - } - }; - - TestRunner.prototype.printSuiteComplete = function (suite) { - this.print.printBreak(); - - this.print.printDiv(); - this.print.printElapsedTime(suite.timer.asString, suite.timer.time); - this.print.printSuccessCount(suite.okTests.length, suite.testResults.length); - this.print.printFailedCount(suite.ngTests.length, suite.testResults.length); - }; - TestRunner.prototype.finaliseTests = function (files) { var _this = this; var testEval = Lazy(this.suites).filter(function (suite) { diff --git a/_infrastructure/tests/runner.ts b/_infrastructure/tests/runner.ts index 8dfcc7b4c..126b824b2 100644 --- a/_infrastructure/tests/runner.ts +++ b/_infrastructure/tests/runner.ts @@ -27,7 +27,6 @@ module DT { var fs = require('fs'); var path = require('path'); - var glob = require('glob'); var assert = require('assert'); var tsExp = /\.ts$/; @@ -79,31 +78,23 @@ module DT { findNotRequiredTscparams?:boolean; } - export interface FileDict { - [fullPath:string]: File; - } - - export interface FileArrDict { - [fullPath:string]: File[]; - } - ///////////////////////////////// // The main class to kick things off ///////////////////////////////// export class TestRunner { - private files: File[]; private timer: Timer; private suites: ITestSuite[] = []; - private index: FileIndex; - private changes: GitChanges; - private print: Print; + public changes: GitChanges; + public index: FileIndex; + public print: Print; constructor(public dtPath: string, public options: ITestRunnerOptions = {tscVersion: DT.DEFAULT_TSC_VERSION}) { this.options.findNotRequiredTscparams = !!this.options.findNotRequiredTscparams; - this.index = new FileIndex(this.options); - this.changes = new GitChanges(this.dtPath); + this.index = new FileIndex(this, this.options); + this.changes = new GitChanges(this); + this.print = new Print(this.options.tscVersion); } @@ -111,7 +102,7 @@ module DT { this.suites.push(suite); } - private checkAcceptFile(fileName: string): boolean { + public checkAcceptFile(fileName: string): boolean { var ok = tsExp.test(fileName); ok = ok && fileName.indexOf('_infrastructure') < 0; ok = ok && fileName.indexOf('node_modules/') < 0; @@ -123,104 +114,35 @@ module DT { this.timer = new Timer(); this.timer.start(); + this.print.printChangeHeader(); + // only includes .d.ts or -tests.ts or -test.ts or .ts - return Promise.promisify(glob).call(glob, '**/*.ts', { - cwd: dtPath - }).then((filesNames: string[]) => { - this.files = Lazy(filesNames).filter((fileName) => { - return this.checkAcceptFile(fileName); - }).map((fileName: string) => { - return new File(dtPath, fileName); - }).toArray(); - - this.print.printChangeHeader(); - - return this.changes.getChanges().then(() => { - this.printAllChanges(this.changes.paths); - }); + return this.index.readIndex().then(() => { + return this.changes.readChanges(); + }).then((changes: string[]) => { + this.print.printAllChanges(changes); + return this.index.collectDiff(changes); }).then(() => { - return this.index.parseFiles(this.files).then(() => { - // this.printFiles(this.files); - }); + return this.index.parseFiles(); }).then(() => { - return this.collectTargets(this.files, this.changes.paths); - }).then((files) => { - return this.runTests(files); - }).then(() => { - return !this.suites.some((suite) => { - return suite.ngTests.length !== 0 - }); - }); - } + // this.print.printRefMap(this.index, this.index.refMap); - private collectTargets(files: File[], changes: string[]): Promise { - return new Promise((resolve) => { + if (Lazy(this.index.missing).some((arr: any[]) => arr.length > 0)) { + this.print.printMissing(this.index, this.index.missing); + this.print.printBoldDiv(); + // bail + return Promise.delay(500).return(false); + } + // this.print.printFiles(this.files); + return this.index.collectTargets().then((files) => { + // this.print.printTests(files); - // filter changes and bake map for easy lookup - var changeMap: FileDict = Object.create(null); - - Lazy(changes).filter((full) => { - return this.checkAcceptFile(full); - }).map((local) => { - return path.resolve(this.dtPath, local); - }).each((full) => { - var file = this.index.getFile(full); - if (!file) { - // TODO figure out what to do here - // what does it mean? deleted? - console.log('not in index: ' + full); - return; - } - changeMap[full] = file; - }); - - this.printRelChanges(changeMap); - - // bake reverse reference map (referenced to referrers) - var refMap: FileArrDict = Object.create(null); - - Lazy(files).each((file) => { - Lazy(file.references).each((ref) => { - if (ref.fullPath in refMap) { - refMap[ref.fullPath].push(file); - } - else { - refMap[ref.fullPath] = [file]; - } + return this.runTests(files); + }).then(() => { + return !this.suites.some((suite) => { + return suite.ngTests.length !== 0 }); }); - - // this.printRefMap(refMap); - - // map out files linked to changes - // - queue holds files touched by a change - // - pre-fill with actually changed files - // - loop queue, if current not seen: - // - add to result - // - from refMap queue all files referring to current - var adding: FileDict = Object.create(null); - var queue = Lazy(changeMap).values().toArray(); - - while (queue.length > 0) { - var next = queue.shift(); - var fp = next.fullPath; - if (adding[fp]) { - continue; - } - adding[fp] = next; - if (fp in refMap) { - var arr = refMap[fp]; - for (var i = 0, ii = arr.length; i < ii; i++) { - // just add it and skip expensive checks - queue.push(arr[i]); - } - } - } - - this.printTests(adding); - - var result: File[] = Lazy(adding).values().toArray(); - resolve(result); }); } @@ -255,10 +177,10 @@ module DT { return suite.filterTargetFiles(files).then((targetFiles) => { return suite.start(targetFiles, (testResult, index) => { - this.printTestComplete(testResult, index); + this.print.printTestComplete(testResult, index); }); }).then((suite) => { - this.printSuiteComplete(suite); + this.print.printSuiteComplete(suite); return count++; }); }, 0); @@ -268,81 +190,6 @@ module DT { }); } - private printTests(adding: FileDict): void { - this.print.printDiv(); - this.print.printSubHeader('Testing'); - this.print.printDiv(); - - Object.keys(adding).sort().map((src) => { - this.print.printLine(adding[src].formatName); - return adding[src]; - }); - } - private printFiles(files: File[]): void { - this.print.printDiv(); - this.print.printSubHeader('Files:'); - this.print.printDiv(); - - files.forEach((file) => { - this.print.printLine(file.filePathWithName); - file.references.forEach((file) => { - this.print.printElement(file.filePathWithName); - }); - }); - } - - private printAllChanges(paths: string[]): void { - this.print.printSubHeader('All changes'); - this.print.printDiv(); - - paths.sort().forEach((line) => { - this.print.printLine(line); - }); - } - - private printRelChanges(changeMap: FileDict): void { - this.print.printDiv(); - this.print.printSubHeader('Relevant changes'); - this.print.printDiv(); - - Object.keys(changeMap).sort().forEach((src) => { - this.print.printLine(changeMap[src].formatName); - }); - } - - private printRefMap(refMap: FileArrDict): void { - this.print.printDiv(); - this.print.printSubHeader('Referring'); - this.print.printDiv(); - - Object.keys(refMap).sort().forEach((src) => { - var ref = this.index.getFile(src); - this.print.printLine(ref.formatName); - refMap[src].forEach((file) => { - this.print.printLine(' - ' + file.formatName); - }); - }); - } - - private printTestComplete(testResult: TestResult, index: number): void { - var reporter = testResult.hostedBy.testReporter; - if (testResult.success) { - reporter.printPositiveCharacter(index, testResult); - } - else { - reporter.printNegativeCharacter(index, testResult); - } - } - - private printSuiteComplete(suite: ITestSuite): void { - this.print.printBreak(); - - this.print.printDiv(); - this.print.printElapsedTime(suite.timer.asString, suite.timer.time); - this.print.printSuccessCount(suite.okTests.length, suite.testResults.length); - this.print.printFailedCount(suite.ngTests.length, suite.testResults.length); - } - private finaliseTests(files: File[]): void { var testEval: TestEval = Lazy(this.suites).filter((suite) => { return suite instanceof TestEval; diff --git a/_infrastructure/tests/src/changes.ts b/_infrastructure/tests/src/changes.ts index f3ec26208..a56c4029e 100644 --- a/_infrastructure/tests/src/changes.ts +++ b/_infrastructure/tests/src/changes.ts @@ -1,4 +1,5 @@ /// +/// module DT { 'use strict'; @@ -12,10 +13,9 @@ module DT { git; options = {}; - paths: string[] = []; - constructor(public baseDir: string) { - var dir = path.join(baseDir, '.git'); + constructor(private runner: TestRunner) { + var dir = path.join(this.runner.dtPath, '.git'); if (!fs.existsSync(dir)) { throw new Error('cannot locate git-dir: ' + dir); } @@ -25,11 +25,11 @@ module DT { this.git.exec = Promise.promisify(this.git.exec); } - getChanges(): Promise { + public readChanges(): Promise { var opts = {}; var args = ['--name-only HEAD~1']; return this.git.exec('diff', opts, args).then((msg: string) => { - this.paths = msg.replace(/^\s+/, '').replace(/\s+$/, '').split(/\r?\n/g); + return msg.replace(/^\s+/, '').replace(/\s+$/, '').split(/\r?\n/g); }); } } diff --git a/_infrastructure/tests/src/file.ts b/_infrastructure/tests/src/file.ts index 389936b49..2c69f8f88 100644 --- a/_infrastructure/tests/src/file.ts +++ b/_infrastructure/tests/src/file.ts @@ -5,6 +5,14 @@ module DT { var path = require('path'); + export interface FileDict { + [fullPath:string]: File; + } + + export interface FileArrDict { + [fullPath:string]: File[]; + } + ///////////////////////////////// // Given a document root + ts file pattern this class returns: // all the TS files OR just tests OR just definition files @@ -15,7 +23,6 @@ module DT { dir: string; file: string; ext: string; - formatName: string; fullPath: string; references: File[] = []; @@ -26,14 +33,13 @@ module DT { this.ext = path.extname(this.filePathWithName); this.file = path.basename(this.filePathWithName, this.ext); this.dir = path.dirname(this.filePathWithName); - this.formatName = path.join(this.dir, this.file + this.ext); this.fullPath = path.join(this.baseDir, this.dir, this.file + this.ext); // lock it (shallow) (needs `use strict` in each file to work) // Object.freeze(this); } - toString() { + toString(): string { return '[File ' + this.filePathWithName + ']'; } } diff --git a/_infrastructure/tests/src/index.ts b/_infrastructure/tests/src/index.ts index 183c1e64d..7f5878dcb 100644 --- a/_infrastructure/tests/src/index.ts +++ b/_infrastructure/tests/src/index.ts @@ -7,7 +7,8 @@ module DT { var fs = require('fs'); var path = require('path'); - var Lazy = require('lazy.js'); + var glob = require('glob'); + var Lazy: LazyJS.LazyStatic = require('lazy.js'); var Promise: typeof Promise = require('bluebird'); var readFile = Promise.promisify(fs.readFile); @@ -17,31 +18,95 @@ module DT { ///////////////////////////////// export class FileIndex { - fileMap: {[fullPath:string]:File}; + files: File[]; + fileMap: FileDict; + refMap: FileArrDict; options: ITestRunnerOptions; + changed: FileDict; + removed: FileDict; + missing: FileArrDict; - constructor(options: ITestRunnerOptions) { + constructor(private runner: TestRunner, options: ITestRunnerOptions) { this.options = options; } - hasFile(target: string): boolean { + public hasFile(target: string): boolean { return target in this.fileMap; } - getFile(target: string): File { + public getFile(target: string): File { if (target in this.fileMap) { return this.fileMap[target]; } return null; } - parseFiles(files: File[]): Promise { - return Promise.attempt(() => { - this.fileMap = Object.create(null); - files.forEach((file) => { + public setFile(file: File): void { + if (file.fullPath in this.fileMap) { + throw new Error('cannot overwrite file'); + } + this.fileMap[file.fullPath] = file; + } + + public readIndex(): Promise { + this.fileMap = Object.create(null); + + return Promise.promisify(glob).call(glob, '**/*.ts', { + cwd: this.runner.dtPath + }).then((filesNames: string[]) => { + this.files = Lazy(filesNames).filter((fileName) => { + return this.runner.checkAcceptFile(fileName); + }).map((fileName: string) => { + var file = new File(this.runner.dtPath, fileName); this.fileMap[file.fullPath] = file; + return file; + }).toArray(); + }); + } + + public collectDiff(changes: string[]): Promise { + return new Promise((resolve) => { + // filter changes and bake map for easy lookup + this.changed = Object.create(null); + this.removed = Object.create(null); + + Lazy(changes).filter((full) => { + return this.runner.checkAcceptFile(full); + }).uniq().each((local) => { + var full = path.resolve(this.runner.dtPath, local); + var file = this.getFile(full); + if (!file) { + // TODO figure out what to do here + // what does it mean? deleted?ss + file = new File(this.runner.dtPath, local); + this.setFile(file); + this.removed[full] = file; + // console.log('not in index? %', file.fullPath); + } + else { + this.changed[full] = file; + } + }); + // console.log('changed:\n' + Object.keys(this.changed).join('\n')); + // console.log('removed:\n' + Object.keys(this.removed).join('\n')); + resolve(); + }); + } + + public parseFiles(): Promise { + return this.loadReferences(this.files).then(() => { + return this.getMissingReferences(); + }); + } + + private getMissingReferences(): Promise { + return Promise.attempt(() => { + this.missing = Object.create(null); + Lazy(this.removed).keys().each((removed) => { + if (removed in this.refMap) { + this.missing[removed] = this.refMap[removed]; + } }); - return this.loadReferences(files); }); } @@ -70,6 +135,20 @@ module DT { } }; next(); + }).then(() => { + // bake reverse reference map (referenced to referrers) + this.refMap = Object.create(null); + + Lazy(files).each((file) => { + Lazy(file.references).each((ref) => { + if (ref.fullPath in this.refMap) { + this.refMap[ref.fullPath].push(file); + } + else { + this.refMap[ref.fullPath] = [file]; + } + }); + }); }); } @@ -94,5 +173,36 @@ module DT { return file; }); } + + public collectTargets(): Promise { + return new Promise((resolve) => { + // map out files linked to changes + // - queue holds files touched by a change + // - pre-fill with actually changed files + // - loop queue, if current not seen: + // - add to result + // - from refMap queue all files referring to current + + var result: FileDict = Object.create(null); + var queue = Lazy(this.changed).values().toArray(); + + while (queue.length > 0) { + var next = queue.shift(); + var fp = next.fullPath; + if (result[fp]) { + continue; + } + result[fp] = next; + if (fp in this.refMap) { + var arr = this.refMap[fp]; + for (var i = 0, ii = arr.length; i < ii; i++) { + // just add it and skip expensive checks + queue.push(arr[i]); + } + } + } + resolve(Lazy(result).values().toArray()); + }); + } } } diff --git a/_infrastructure/tests/src/printer.ts b/_infrastructure/tests/src/printer.ts index 613251b82..9be9a79aa 100644 --- a/_infrastructure/tests/src/printer.ts +++ b/_infrastructure/tests/src/printer.ts @@ -72,7 +72,7 @@ module DT { } public printErrorsForFile(testResult: TestResult) { - this.out('----------------- For file:' + testResult.targetFile.formatName); + this.out('----------------- For file:' + testResult.targetFile.filePathWithName); this.printBreak().out(testResult.stderr).printBreak(); } @@ -149,5 +149,95 @@ module DT { }); } } + + public printTestComplete(testResult: TestResult, index: number): void { + var reporter = testResult.hostedBy.testReporter; + if (testResult.success) { + reporter.printPositiveCharacter(index, testResult); + } + else { + reporter.printNegativeCharacter(index, testResult); + } + } + + public printSuiteComplete(suite: ITestSuite): void { + this.printBreak(); + + this.printDiv(); + this.printElapsedTime(suite.timer.asString, suite.timer.time); + this.printSuccessCount(suite.okTests.length, suite.testResults.length); + this.printFailedCount(suite.ngTests.length, suite.testResults.length); + } + + public printTests(adding: FileDict): void { + this.printDiv(); + this.printSubHeader('Testing'); + this.printDiv(); + + Object.keys(adding).sort().map((src) => { + this.printLine(adding[src].filePathWithName); + return adding[src]; + }); + } + + public printFiles(files: File[]): void { + this.printDiv(); + this.printSubHeader('Files:'); + this.printDiv(); + + files.forEach((file) => { + this.printLine(file.filePathWithName); + file.references.forEach((file) => { + this.printElement(file.filePathWithName); + }); + }); + } + + public printMissing(index: FileIndex, refMap: FileArrDict): void { + this.printDiv(); + this.printSubHeader('Missing references'); + this.printDiv(); + + Object.keys(refMap).sort().forEach((src) => { + var ref = index.getFile(src); + this.printLine('\33[31m\33[1m' + ref.filePathWithName + '\33[0m'); + refMap[src].forEach((file) => { + this.printElement(file.filePathWithName); + }); + }); + } + + public printAllChanges(paths: string[]): void { + this.printSubHeader('All changes'); + this.printDiv(); + + paths.sort().forEach((line) => { + this.printLine(line); + }); + } + + public printRelChanges(changeMap: FileDict): void { + this.printDiv(); + this.printSubHeader('Relevant changes'); + this.printDiv(); + + Object.keys(changeMap).sort().forEach((src) => { + this.printLine(changeMap[src].filePathWithName); + }); + } + + public printRefMap(index: FileIndex, refMap: FileArrDict): void { + this.printDiv(); + this.printSubHeader('Referring'); + this.printDiv(); + + Object.keys(refMap).sort().forEach((src) => { + var ref = index.getFile(src); + this.printLine(ref.filePathWithName); + refMap[src].forEach((file) => { + this.printLine(' - ' + file.filePathWithName); + }); + }); + } } } diff --git a/_infrastructure/tests/src/suite/syntax.ts b/_infrastructure/tests/src/suite/syntax.ts index 41808b2a2..2e0d0b71a 100644 --- a/_infrastructure/tests/src/suite/syntax.ts +++ b/_infrastructure/tests/src/suite/syntax.ts @@ -19,7 +19,7 @@ module DT { public filterTargetFiles(files: File[]): Promise { return Promise.cast(files.filter((file) => { - return endDts.test(file.formatName); + return endDts.test(file.filePathWithName); })); } } diff --git a/_infrastructure/tests/src/suite/testEval.ts b/_infrastructure/tests/src/suite/testEval.ts index bc842549f..95044daa7 100644 --- a/_infrastructure/tests/src/suite/testEval.ts +++ b/_infrastructure/tests/src/suite/testEval.ts @@ -19,7 +19,7 @@ module DT { public filterTargetFiles(files: File[]): Promise { return Promise.cast(files.filter((file) => { - return endTestDts.test(file.formatName); + return endTestDts.test(file.filePathWithName); })); } } diff --git a/_infrastructure/tests/src/suite/tscParams.ts b/_infrastructure/tests/src/suite/tscParams.ts index cef23d622..bb064dd81 100644 --- a/_infrastructure/tests/src/suite/tscParams.ts +++ b/_infrastructure/tests/src/suite/tscParams.ts @@ -22,7 +22,7 @@ module DT { printPositiveCharacter: (index: number, testResult: TestResult) => { this.print .clearCurrentLine() - .printTypingsWithoutTestName(testResult.targetFile.formatName); + .printTypingsWithoutTestName(testResult.targetFile.filePathWithName); }, printNegativeCharacter: (index: number, testResult: TestResult) => { } @@ -38,7 +38,7 @@ module DT { } public runTest(targetFile: File): Promise { - this.print.clearCurrentLine().out(targetFile.formatName); + this.print.clearCurrentLine().out(targetFile.filePathWithName); return new Test(this, targetFile, { tscVersion: this.options.tscVersion, diff --git a/_infrastructure/tests/typings/lazy.js/lazy.js.d.ts b/_infrastructure/tests/typings/lazy.js/lazy.js.d.ts index b9367b73b..b2909dfce 100644 --- a/_infrastructure/tests/typings/lazy.js/lazy.js.d.ts +++ b/_infrastructure/tests/typings/lazy.js/lazy.js.d.ts @@ -6,12 +6,12 @@ declare module LazyJS { interface LazyStatic { - (value: string):StringLikeSequence; (value: T[]):ArrayLikeSequence; (value: any[]):ArrayLikeSequence; (value: Object):ObjectLikeSequence; (value: Object):ObjectLikeSequence; + (value: string):StringLikeSequence; strict():LazyStatic; @@ -200,7 +200,7 @@ declare module LazyJS { functions(): Sequence; get(property: string): ObjectLikeSequence; invert(): ObjectLikeSequence; - keys(): StringLikeSequence; + keys(): Sequence; omit(properties: string[]): ObjectLikeSequence; pairs(): Sequence; pick(properties: string[]): ObjectLikeSequence;