diff --git a/_infrastructure/tests/compile-runner.bat b/_infrastructure/tests/compile-runner.bat index 53e014841..e54803ec8 100644 --- a/_infrastructure/tests/compile-runner.bat +++ b/_infrastructure/tests/compile-runner.bat @@ -1 +1 @@ -tsc runner.ts --out runner.js --module commonjs \ No newline at end of file +tsc runner.ts --target ES5 --out runner.js --module commonjs \ No newline at end of file diff --git a/_infrastructure/tests/runner.js b/_infrastructure/tests/runner.js index 3721f7e11..465446c4e 100644 --- a/_infrastructure/tests/runner.js +++ b/_infrastructure/tests/runner.js @@ -552,6 +552,12 @@ else if (typeof require === "function") else return null; })(); +var __extends = this.__extends || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + __.prototype = b.prototype; + d.prototype = new __(); +}; var DefinitelyTyped; (function (DefinitelyTyped) { /// @@ -564,106 +570,59 @@ var DefinitelyTyped; return str.indexOf(suffix, str.length - suffix.length) !== -1; } - var Iterator = (function () { - function Iterator(list) { - this.list = list; - this.index = -1; - } - Iterator.prototype.next = function () { - this.index++; - return this.list[this.index]; - }; - - Iterator.prototype.hasNext = function () { - return this.list[1 + this.index] != null; - }; - return Iterator; - })(); - var Tsc = (function () { function Tsc() { } - Tsc.run = function (tsfile, callback) { + Tsc.run = function (tsfile, options, callback) { + options = options || {}; + if (typeof options.checkNoImplicitAny === "undefined") { + options.checkNoImplicitAny = true; + } + if (typeof options.useTscParams === "undefined") { + options.useTscParams = true; + } + + if (!IO.fileExists(tsfile)) { + throw new Error(tsfile + " not exists"); + } + var command = 'node ./_infrastructure/tests/typescript/tsc.js --module commonjs '; - if (IO.fileExists(tsfile + '.tscparams')) { + if (options.useTscParams && IO.fileExists(tsfile + '.tscparams')) { command += '@' + tsfile + '.tscparams'; - } else { + } else if (options.checkNoImplicitAny) { command += '--noImplicitAny'; } - Exec.exec(command, [tsfile], function (ExecResult) { - callback(ExecResult); + Exec.exec(command, [tsfile], function (execResult) { + callback(execResult); }); }; return Tsc; })(); var Test = (function () { - function Test(tsfile) { + function Test(suite, tsfile, options) { + this.suite = suite; this.tsfile = tsfile; + this.options = options; } Test.prototype.run = function (callback) { - Tsc.run(this.tsfile, callback); + var _this = this; + Tsc.run(this.tsfile.filePathWithName, this.options, function (execResult) { + var testResult = new TestResult(); + testResult.hostedBy = _this.suite; + testResult.targetFile = _this.tsfile; + testResult.options = _this.options; + + testResult.stdout = execResult.stdout; + testResult.stderr = execResult.stderr; + testResult.exitCode = execResult.exitCode; + + callback(testResult); + }); }; return Test; })(); - var Typing = (function () { - function Typing(name, baseDir) { - this.name = name; - this.fileHandler = new FileHandler(baseDir + '/' + name + '/', /.\.ts/g); - } - return Typing; - })(); - - ///////////////////////////////// - // Given a document root + ts file pattern this class returns: - // all the TS files OR just tests OR just definition files - ///////////////////////////////// - var FileHandler = (function () { - function FileHandler(path, pattern) { - this.path = path; - this.files = []; - this.typings = []; - this.files = IO.dir(path, pattern, { recursive: true }).sort(); - } - FileHandler.prototype.allTS = function () { - return this.files; - }; - - FileHandler.prototype.allTests = function () { - var tests = []; - - for (var i = 0; i < this.files.length; i++) { - if (endsWith(this.files[i].toUpperCase(), '-TESTS.TS')) { - tests.push(this.files[i]); - } - } - - return tests; - }; - - FileHandler.prototype.allTypings = function () { - var typings = {}; - - for (var i = 0; i < this.files.length; i++) { - var file = this.files[i]; - var firName = path.dirname(file.substr(this.path.length + 1)).replace('\\', '/'); - var dir = firName.split('/')[0]; - - if (!typings[dir]) - typings[dir] = true; - } - - var list = []; - for (var attr in typings) { - list.push(attr); - } - - return list; - }; - return FileHandler; - })(); - ///////////////////////////////// // Timer.start starts a timer // Timer.end stops the timer and sets asString to the pretty print value @@ -672,15 +631,6 @@ var DefinitelyTyped; function Timer() { this.time = 0; } - Timer.prettyDate = function (date1, date2) { - var diff = ((date2 - date1) / 1000), day_diff = Math.floor(diff / 86400); - - if (isNaN(day_diff) || day_diff < 0 || day_diff >= 31) - return; - - return (day_diff == 0 && (diff < 60 && (diff + " seconds") || diff < 120 && "1 minute" || diff < 3600 && Math.floor(diff / 60) + " minutes" || diff < 7200 && "1 hour" || diff < 86400 && Math.floor(diff / 3600) + " hours") || day_diff == 1 && "Yesterday" || day_diff < 7 && day_diff + " days" || day_diff < 31 && Math.ceil(day_diff / 7) + " weeks"); - }; - Timer.prototype.start = function () { this.time = 0; this.startTime = this.now(); @@ -694,45 +644,125 @@ var DefinitelyTyped; this.time = (this.now() - this.startTime) / 1000; this.asString = Timer.prettyDate(this.startTime, this.now()); }; + + Timer.prettyDate = function (date1, date2) { + var diff = ((date2 - date1) / 1000), day_diff = Math.floor(diff / 86400); + + if (isNaN(day_diff) || day_diff < 0 || day_diff >= 31) + return; + + return (day_diff == 0 && (diff < 60 && (diff + " seconds") || diff < 120 && "1 minute" || diff < 3600 && Math.floor(diff / 60) + " minutes" || diff < 7200 && "1 hour" || diff < 86400 && Math.floor(diff / 3600) + " hours") || day_diff == 1 && "Yesterday" || day_diff < 7 && day_diff + " days" || day_diff < 31 && Math.ceil(day_diff / 7) + " weeks"); + }; return Timer; })(); + TestManager.Timer = Timer; + + ///////////////////////////////// + // Given a document root + ts file pattern this class returns: + // all the TS files OR just tests OR just definition files + ///////////////////////////////// + var File = (function () { + function File(baseDir, filePathWithName) { + this.baseDir = baseDir; + this.filePathWithName = filePathWithName; + var dirName = path.dirname(this.filePathWithName.substr(this.baseDir.length + 1)).replace('\\', '/'); + this.dir = dirName.split('/')[0]; + this.file = path.basename(this.filePathWithName, '.ts'); + this.ext = path.extname(this.filePathWithName); + } + Object.defineProperty(File.prototype, "formatName", { + get: // From '/complete/path/to/file' to 'specfolder/specfile.d.ts' + function () { + var dirName = path.dirname(this.filePathWithName.substr(this.baseDir.length + 1)).replace('\\', '/'); + return this.dir + ((dirName.split('/').length > 1) ? '/-/' : '/') + this.file + this.ext; + }, + enumerable: true, + configurable: true + }); + return File; + })(); + TestManager.File = File; + + ///////////////////////////////// + // Test results + ///////////////////////////////// + var TestResult = (function () { + function TestResult() { + } + Object.defineProperty(TestResult.prototype, "success", { + get: function () { + return this.exitCode !== 1; + }, + enumerable: true, + configurable: true + }); + return TestResult; + })(); + TestManager.TestResult = TestResult; + + ///////////////////////////////// + // Default test reporter + ///////////////////////////////// + var DefaultTestReporter = (function () { + function DefaultTestReporter(print) { + this.print = print; + } + DefaultTestReporter.prototype.printPositiveCharacter = function (index, testResult) { + this.print.out('\33[36m\33[1m' + '.' + '\33[0m'); + + this.printBreakIfNeeded(index); + }; + + DefaultTestReporter.prototype.printNegativeCharacter = function (index, testResult) { + this.print.out("x"); + + this.printBreakIfNeeded(index); + }; + + DefaultTestReporter.prototype.printBreakIfNeeded = function (index) { + if (index % this.print.WIDTH === 0) { + this.print.printBreak(); + } + }; + return DefaultTestReporter; + })(); ///////////////////////////////// // All the common things that we pring are functions of this class ///////////////////////////////// var Print = (function () { - function Print(version, typings, tsFiles) { + function Print(version, typings, tests, tsFiles) { this.version = version; this.typings = typings; + this.tests = tests; this.tsFiles = tsFiles; + this.WIDTH = 77; } Print.prototype.out = function (s) { process.stdout.write(s); + return this; + }; + + Print.prototype.repeat = function (s, times) { + return new Array(times + 1).join(s); }; Print.prototype.printHeader = function () { this.out('=============================================================================\n'); - this.out(' \33[36m\33[1mDefinitelyTyped test runner 0.3.0\33[0m\n'); + this.out(' \33[36m\33[1mDefinitelyTyped test runner 0.4.0\33[0m\n'); this.out('=============================================================================\n'); this.out(' \33[36m\33[1mTypescript version:\33[0m ' + this.version + '\n'); this.out(' \33[36m\33[1mTypings :\33[0m ' + this.typings + '\n'); + this.out(' \33[36m\33[1mTests :\33[0m ' + this.tests + '\n'); this.out(' \33[36m\33[1mTypeScript files :\33[0m ' + this.tsFiles + '\n'); }; - Print.prototype.printSyntaxCheckingHeader = function () { - this.out('============================ \33[34m\33[1mSyntax checking\33[0m ================================\n'); - }; - - Print.prototype.printTypingTestsHeader = function () { - this.out('============================= \33[34m\33[1mTyping tests\33[0m ==================================\n'); - }; - - Print.prototype.printSuccessCharacter = function () { - this.out('\33[36m\33[1m.\33[0m'); - }; - - Print.prototype.printFailureCharacter = function () { - this.out('x'); + Print.prototype.printSuiteHeader = function (title) { + var left = Math.floor((this.WIDTH - title.length) / 2) - 1; + var right = Math.ceil((this.WIDTH - title.length) / 2) - 1; + this.out(this.repeat("=", left)).out(" \33[34m\33[1m"); + this.out(title); + this.out("\33[0m ").out(this.repeat("=", right)).printBreak(); }; Print.prototype.printDiv = function () { @@ -749,39 +779,19 @@ var DefinitelyTyped; this.out('=============================================================================\n'); }; - Print.prototype.printErrorsForFile = function (file) { - this.out('----------------- For file:' + file.formatName()); - this.printBreak(); - this.out(file.execResult.stderr); - this.printBreak(); - }; - - Print.prototype.printfilesWithSintaxErrorMessage = function () { - this.out(' \33[36m\33[1mFiles with syntax error\33[0m\n'); - }; - - Print.prototype.printFailedTestMessage = function () { - this.out(' \33[36m\33[1mFailed tests\33[0m\n'); - }; - - Print.prototype.printTypingsWithoutTestsMessage = function () { - this.out(' \33[36m\33[1mTyping without tests\33[0m\n'); - }; - - Print.prototype.printTotalMessage = function () { - this.out(' \33[36m\33[1mTotal\33[0m\n'); - }; - - Print.prototype.printErrorFile = function (file) { - this.out(' - ' + file + '\n'); - }; - - Print.prototype.printTypingsWithoutTest = function (file) { - this.out(' - \33[33m\33[1m' + file + '\33[0m\n'); + Print.prototype.printErrorsForFile = function (testResult) { + this.out('----------------- For file:' + testResult.targetFile.formatName); + this.printBreak().out(testResult.stderr).printBreak(); }; Print.prototype.printBreak = function () { this.out('\n'); + return this; + }; + + Print.prototype.clearCurrentLine = function () { + this.out("\r\33[K"); + return this; }; Print.prototype.printSuccessCount = function (current, total) { @@ -792,370 +802,278 @@ var DefinitelyTyped; this.out(' \33[36m\33[1mFailure :\33[0m \33[31m\33[1m' + ((current / total) * 100).toFixed(2) + '% (' + current + '/' + total + ')\33[0m\n'); }; + Print.prototype.printTypingsWithoutTestsMessage = function () { + this.out(' \33[36m\33[1mTyping without tests\33[0m\n'); + }; + + Print.prototype.printTotalMessage = function () { + this.out(' \33[36m\33[1mTotal\33[0m\n'); + }; + Print.prototype.printElapsedTime = function (time, s) { this.out(' \33[36m\33[1mElapsed time :\33[0m ~' + time + ' (' + s + 's)\n'); }; - Print.prototype.printSyntaxErrorCount = function (current, total) { - this.out(' \33[36m\33[1mSyntax error :\33[0m \33[31m\33[1m' + ((current / total) * 100).toFixed(2) + '% (' + current + '/' + total + ')\33[0m\n'); + Print.prototype.printSuiteErrorCount = function (errorHeadline, current, total, valuesColor) { + if (typeof valuesColor === "undefined") { valuesColor = '\33[31m\33[1m'; } + this.out(' \33[36m\33[1m').out(errorHeadline).out(this.repeat(' ', 16 - errorHeadline.length)); + this.out(':\33[0m ' + valuesColor + ((current / total) * 100).toFixed(2) + '% (' + current + '/' + total + ')\33[0m\n'); }; - Print.prototype.printTestErrorCount = function (current, total) { - this.out(' \33[36m\33[1mFailed tests :\33[0m \33[31m\33[1m' + ((current / total) * 100).toFixed(2) + '% (' + current + '/' + total + ')\33[0m\n'); + Print.prototype.printTypingsWithoutTestName = function (file) { + this.out(' - \33[33m\33[1m' + file + '\33[0m\n'); }; - Print.prototype.printWithoutTestCount = function (current, total) { - this.out(' \33[36m\33[1mWithout tests :\33[0m \33[33m\33[1m' + ((current / total) * 100).toFixed(2) + '% (' + current + '/' + total + ')\33[0m\n'); + Print.prototype.printTypingsWithoutTest = function (withoutTestTypings) { + var _this = this; + if (withoutTestTypings.length > 0) { + this.printTypingsWithoutTestsMessage(); + + this.printDiv(); + withoutTestTypings.forEach(function (t) { + _this.printTypingsWithoutTestName(t); + }); + } }; return Print; })(); - ///////////////////////////// - // Represents the results of a test file execution - ///////////////////////////// - var File = (function () { - function File(baseDir, filePathWithName, execResult) { - this.baseDir = baseDir; - this.filePathWithName = filePathWithName; - this.execResult = execResult; + ///////////////////////////////// + // Base class for test suite + ///////////////////////////////// + var TestSuiteBase = (function () { + function TestSuiteBase(testSuiteName, errorHeadline) { + this.testSuiteName = testSuiteName; + this.errorHeadline = errorHeadline; + this.timer = new Timer(); + this.testResults = []; + this.printErrorCount = true; } - // From '/complete/path/to/file' to 'specfolder/specfile.d.ts' - File.prototype.formatName = function () { - var dirName = path.dirname(this.filePathWithName.substr(this.baseDir.length + 1)).replace('\\', '/'); - var dir = dirName.split('/')[0]; - var file = path.basename(this.filePathWithName, '.ts'); - var ext = path.extname(this.filePathWithName); - - return dir + ((dirName.split('/').length > 1) ? '/-/' : '/') + '\33[36m\33[1m' + file + '\33[0m' + ext; + TestSuiteBase.prototype.filterTargetFiles = function (files) { + throw new Error("please implement this method"); }; - return File; + + TestSuiteBase.prototype.start = function (targetFiles, testCallback, suiteCallback) { + var _this = this; + targetFiles = this.filterTargetFiles(targetFiles); + this.timer.start(); + var count = 0; + + // exec test is async process. serialize. + var executor = function () { + var targetFile = targetFiles[count]; + if (targetFile) { + _this.runTest(targetFile, function (result) { + testCallback(result, count + 1); + count++; + executor(); + }); + } else { + _this.timer.end(); + _this.finish(suiteCallback); + } + }; + executor(); + }; + + TestSuiteBase.prototype.runTest = function (targetFile, callback) { + var _this = this; + new Test(this, targetFile, null).run(function (result) { + _this.testResults.push(result); + callback(result); + }); + }; + + TestSuiteBase.prototype.finish = function (suiteCallback) { + suiteCallback(this); + }; + + Object.defineProperty(TestSuiteBase.prototype, "okTests", { + get: function () { + return this.testResults.filter(function (r) { + return r.success; + }); + }, + enumerable: true, + configurable: true + }); + + Object.defineProperty(TestSuiteBase.prototype, "ngTests", { + get: function () { + return this.testResults.filter(function (r) { + return !r.success; + }); + }, + enumerable: true, + configurable: true + }); + return TestSuiteBase; })(); - ///////////////////////////// - // Determine syntax errors in typings - ///////////////////////////// - var SyntaxChecking = (function () { - function SyntaxChecking(fileHandler, out) { - this.fileHandler = fileHandler; - this.out = out; - this.files = []; - this.timer = new Timer(); + ///////////////////////////////// + // .d.ts syntax inspection + ///////////////////////////////// + var SyntaxChecking = (function (_super) { + __extends(SyntaxChecking, _super); + function SyntaxChecking() { + _super.call(this, "Syntax checking", "Syntax error"); } - SyntaxChecking.prototype.getFailedFiles = function () { - var list = []; - - for (var i = 0; i < this.files.length; i++) { - if (this.files[i].execResult.exitCode) { - list.push(this.files[i]); - } - } - - return list; - }; - - SyntaxChecking.prototype.getSuccessFiles = function () { - var list = []; - - for (var i = 0; i < this.files.length; i++) { - if (!this.files[i].execResult.exitCode) { - list.push(this.files[i]); - } - } - - return list; - }; - - SyntaxChecking.prototype.printStats = function () { - this.out.printDiv(); - this.out.printElapsedTime(this.timer.asString, this.timer.time); - this.out.printSuccessCount(this.getSuccessFiles().length, this.files.length); - this.out.printFailedCount(this.getFailedFiles().length, this.files.length); - }; - - SyntaxChecking.prototype.printFailedFiles = function () { - if (this.getFailedFiles().length > 0) { - this.out.printDiv(); - - this.out.printfilesWithSintaxErrorMessage(); - - this.out.printDiv(); - - for (var i = 0; i < this.getFailedFiles().length; i++) { - var errorFile = this.getFailedFiles()[i]; - this.out.printErrorFile(errorFile.formatName()); - } - } - }; - - SyntaxChecking.prototype.run = function (it, file, len, maxLen, callback) { - var _this = this; - if (!endsWith(file.toUpperCase(), '-TESTS.TS') && endsWith(file.toUpperCase(), '.TS') && file.indexOf('../_infrastructure') < 0) { - new Test(file).run(function (execResult) { - if (execResult.exitCode === 1) { - _this.out.printFailureCharacter(); - len++; - } else { - _this.out.printSuccessCharacter(); - len++; - } - - _this.files.push(new File(_this.fileHandler.path, file, execResult)); - - if (len > maxLen) { - len = 0; - _this.out.printBreak(); - } - - if (it.hasNext()) { - _this.run(it, it.next(), len, maxLen, callback); - } else { - _this.out.printBreak(); - _this.timer.end(); - _this.printFailedFiles(); - _this.printStats(); - - callback(_this.getFailedFiles().length, _this.files.length); - } - }); - } else if (it.hasNext()) { - this.run(it, it.next(), len, maxLen, callback); - } else { - this.out.printBreak(); - this.timer.end(); - this.printStats(); - this.printFailedFiles(); - - callback(this.getFailedFiles().length, this.files.length); - } - }; - - SyntaxChecking.prototype.start = function (callback) { - this.timer.start(); - - var tsFiles = this.fileHandler.allTS(); - - var it = new Iterator(tsFiles); - - var len = 0; - var maxLen = 76; - - if (it.hasNext()) { - this.run(it, it.next(), len, maxLen, callback); - } + SyntaxChecking.prototype.filterTargetFiles = function (files) { + return files.filter(function (file) { + return endsWith(file.formatName.toUpperCase(), '.D.TS'); + }); }; return SyntaxChecking; - })(); + })(TestSuiteBase); - ///////////////////////////// - // Determines errors in typing tests - ///////////////////////////// - var TestEval = (function () { - function TestEval(fileHandler, out) { - this.fileHandler = fileHandler; - this.out = out; - this.files = []; - this.timer = new Timer(); + ///////////////////////////////// + // Compile with *-tests.ts + ///////////////////////////////// + var TestEval = (function (_super) { + __extends(TestEval, _super); + function TestEval() { + _super.call(this, "Typing tests", "Failed tests"); } - TestEval.prototype.getFailedFiles = function () { - var list = []; - - for (var i = 0; i < this.files.length; i++) { - if (this.files[i].execResult.exitCode) { - list.push(this.files[i]); - } - } - - return list; - }; - - TestEval.prototype.getSuccessFiles = function () { - var list = []; - - for (var i = 0; i < this.files.length; i++) { - if (!this.files[i].execResult.exitCode) { - list.push(this.files[i]); - } - } - - return list; - }; - - TestEval.prototype.printStats = function () { - this.out.printDiv(); - this.out.printElapsedTime(this.timer.asString, this.timer.time); - this.out.printSuccessCount(this.getSuccessFiles().length, this.files.length); - this.out.printFailedCount(this.getFailedFiles().length, this.files.length); - }; - - TestEval.prototype.printFailedFiles = function () { - if (this.getFailedFiles().length > 0) { - this.out.printDiv(); - - this.out.printFailedTestMessage(); - - this.out.printDiv(); - - for (var i = 0; i < this.getFailedFiles().length; i++) { - var errorFile = this.getFailedFiles()[i]; - this.out.printErrorFile(errorFile.formatName()); - } - } - }; - - TestEval.prototype.run = function (it, file, len, maxLen, callback) { - var _this = this; - if (endsWith(file.toUpperCase(), '-TESTS.TS')) { - new Test(file).run(function (execResult) { - if (execResult.exitCode === 1) { - _this.out.printFailureCharacter(); - len++; - } else { - _this.out.printSuccessCharacter(); - len++; - } - - _this.files.push(new File(_this.fileHandler.path, file, execResult)); - - if (len > maxLen) { - len = 0; - _this.out.printBreak(); - } - - if (it.hasNext()) { - _this.run(it, it.next(), len, maxLen, callback); - } else { - _this.out.printBreak(); - _this.timer.end(); - _this.printFailedFiles(); - _this.printStats(); - - callback(_this.getFailedFiles().length, _this.files.length); - } - }); - } else if (it.hasNext()) { - this.run(it, it.next(), len, maxLen, callback); - } else { - this.out.printBreak(); - this.timer.end(); - this.printFailedFiles(); - this.printStats(); - - callback(this.getFailedFiles().length, this.files.length); - } - }; - - TestEval.prototype.start = function (callback) { - this.timer.start(); - - var tsFiles = this.fileHandler.allTS(); - - var it = new Iterator(tsFiles); - - var len = 0; - var maxLen = 76; - - if (it.hasNext()) { - this.run(it, it.next(), len, maxLen, callback); - } + TestEval.prototype.filterTargetFiles = function (files) { + return files.filter(function (file) { + return endsWith(file.formatName.toUpperCase(), '-TESTS.TS'); + }); }; return TestEval; - })(); + })(TestSuiteBase); ///////////////////////////////// // The main class to kick things off ///////////////////////////////// var TestRunner = (function () { function TestRunner(dtPath) { - this.dtPath = dtPath; - this.typings = []; - this.fh = new FileHandler(dtPath, /.\.ts/g); - this.out = new Print('0.9.1.1', this.fh.allTypings().length, this.fh.allTS().length); - this.sc = new SyntaxChecking(this.fh, this.out); - this.te = new TestEval(this.fh, this.out); + this.suites = []; + var filesName = IO.dir(dtPath, /.\.ts/g, { recursive: true }).sort(); - var tpgs = this.fh.allTypings(); - for (var i = 0; i < tpgs.length; i++) { - this.typings.push(new Typing(tpgs[i], this.dtPath)); - } + // only includes .d.ts or -tests.ts or -test.ts or .ts + filesName = filesName.filter(function (fileName) { + return fileName.indexOf('../_infrastructure') < 0; + }).filter(function (fileName) { + return !endsWith(fileName, ".tscparams"); + }); + this.files = filesName.map(function (fileName) { + return new File(dtPath, fileName); + }); } - TestRunner.prototype.printTypingsWithoutTest = function () { - var count = 0; - - if (this.typings.length > 0) { - this.out.printDiv(); - - this.out.printTypingsWithoutTestsMessage(); - - this.out.printDiv(); - - for (var i = 0; i < this.typings.length; i++) { - var typing = this.typings[i]; - if (typing.fileHandler.allTests().length == 0) { - if (typing.name != '_infrastructure' && typing.name != '_ReSharper.DefinitelyTyped' && typing.name != 'obj' && typing.name != 'bin' && typing.name != 'Properties') { - this.out.printTypingsWithoutTest(typing.name); - count++; - } - } - } - } - - return count; - }; - - TestRunner.prototype.printErrorsDetected = function () { - var _this = this; - this.out.printErrorsHeader(); - - var printErrorsForFileIfFound = function (file) { - if (file.execResult.exitCode) - _this.out.printErrorsForFile(file); - }; - - // sc errors: - this.sc.files.forEach(printErrorsForFileIfFound); - this.out.printBoldDiv(); - - // te errors: - this.te.files.forEach(printErrorsForFileIfFound); - this.out.printBoldDiv(); + TestRunner.prototype.addSuite = function (suite) { + this.suites.push(suite); }; TestRunner.prototype.run = function () { var _this = this; - var timer = new Timer(); - timer.start(); + this.timer = new Timer(); + this.timer.start(); - this.out.printHeader(); + var syntaxChecking = new SyntaxChecking(); + var testEval = new TestEval(); + this.addSuite(syntaxChecking); + this.addSuite(testEval); - // Run syntax tests - this.out.printSyntaxCheckingHeader(); - this.sc.start(function (syntaxFailedCount, syntaxTotal) { - // Now run typing tests - _this.out.printTypingTestsHeader(); - _this.te.start(function (testFailedCount, testTotal) { - // Get the tests without any typing and simultaneously print their names - var totalTypingsWithoutTest = _this.printTypingsWithoutTest(); + var typings = syntaxChecking.filterTargetFiles(this.files).length; + var testFiles = testEval.filterTargetFiles(this.files).length; + this.print = new Print('0.9.1.1', typings, testFiles, this.files.length); + this.print.printHeader(); - // End total timer and print final messages - timer.end(); + var count = 0; + var executor = function () { + var suite = _this.suites[count]; + if (suite) { + suite.testReporter = suite.testReporter || new DefaultTestReporter(_this.print); - _this.out.printDiv(); - _this.out.printTotalMessage(); - _this.out.printDiv(); + _this.print.printSuiteHeader(suite.testSuiteName); + var targetFiles = suite.filterTargetFiles(_this.files); + suite.start(targetFiles, function (testResult, index) { + _this.testCompleteCallback(testResult, index); + }, function (suite) { + _this.suiteCompleteCallback(suite); + count++; + executor(); + }); + } else { + _this.timer.end(); + _this.allTestCompleteCallback(); + } + }; + executor(); + }; - _this.out.printElapsedTime(timer.asString, timer.time); - _this.out.printSyntaxErrorCount(syntaxFailedCount, syntaxTotal); - _this.out.printTestErrorCount(testFailedCount, testTotal); - _this.out.printWithoutTestCount(totalTypingsWithoutTest, _this.fh.allTypings().length); + TestRunner.prototype.testCompleteCallback = function (testResult, index) { + var reporter = testResult.hostedBy.testReporter; + if (testResult.success) { + reporter.printPositiveCharacter(index, testResult); + } else { + reporter.printNegativeCharacter(index, testResult); + } + }; - _this.out.printDiv(); + TestRunner.prototype.suiteCompleteCallback = function (suite) { + this.print.printBreak(); - if (syntaxFailedCount > 0 || testFailedCount > 0) { - _this.printErrorsDetected(); - process.exit(1); - } + 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.allTestCompleteCallback = function () { + var _this = this; + var testEval = this.suites.filter(function (suite) { + return suite instanceof TestEval; + })[0]; + if (testEval) { + var existsTestTypings = testEval.testResults.map(function (testResult) { + return testResult.targetFile.dir; + }).reduce(function (a, b) { + return a.indexOf(b) < 0 ? a.concat([b]) : a; + }, []); + var typings = this.files.map(function (file) { + return file.dir; + }).reduce(function (a, b) { + return a.indexOf(b) < 0 ? a.concat([b]) : a; + }, []); + var withoutTestTypings = typings.filter(function (typing) { + return existsTestTypings.indexOf(typing) < 0; }); + this.print.printDiv(); + this.print.printTypingsWithoutTest(withoutTestTypings); + } + + this.print.printDiv(); + this.print.printTotalMessage(); + + this.print.printDiv(); + this.print.printElapsedTime(this.timer.asString, this.timer.time); + this.suites.filter(function (suite) { + return suite.printErrorCount; + }).forEach(function (suite) { + _this.print.printSuiteErrorCount(suite.errorHeadline, suite.ngTests.length, suite.testResults.length); }); + if (testEval) { + this.print.printSuiteErrorCount("Without tests", withoutTestTypings.length, typings.length, '\33[33m\33[1m'); + } + + this.print.printDiv(); + if (this.suites.some(function (suite) { + return suite.ngTests.length !== 0; + })) { + this.print.printErrorsHeader(); + + this.suites.filter(function (suite) { + return suite.ngTests.length !== 0; + }).forEach(function (suite) { + suite.ngTests.forEach(function (testResult) { + _this.print.printErrorsForFile(testResult); + }); + _this.print.printBoldDiv(); + }); + + process.exit(1); + } }; return TestRunner; })(); diff --git a/_infrastructure/tests/runner.ts b/_infrastructure/tests/runner.ts index 68682f536..100beab83 100644 --- a/_infrastructure/tests/runner.ts +++ b/_infrastructure/tests/runner.ts @@ -1,627 +1,523 @@ -/// - -/// -/// - -module DefinitelyTyped.TestManager { - - var path = require('path'); - - function endsWith(str, suffix) { - return str.indexOf(suffix, str.length - suffix.length) !== -1; - } - - class Iterator { - index: number = -1; - - constructor(public list: any[]) { } - - public next() { - this.index++; - return this.list[this.index]; - } - - public hasNext() { - return this.list[1 + this.index] != null; - } - } - - interface TscCallback { - (result: ExecResult): void; - } - - class Tsc { - public static run(tsfile: string, callback: TscCallback) { - var command = 'node ./_infrastructure/tests/typescript/tsc.js --module commonjs '; - if (IO.fileExists(tsfile + '.tscparams')) { - command += '@' + tsfile + '.tscparams'; - } else { - command += '--noImplicitAny'; - } - Exec.exec(command, [tsfile], (ExecResult) => { - callback(ExecResult); - }); - } - } - - class Test { - constructor(public tsfile: string) { } - - public run(callback: TscCallback) { - Tsc.run(this.tsfile, callback); - } - } - - class Typing { - public fileHandler: FileHandler; - - constructor(public name: string, baseDir: string) { - this.fileHandler = new FileHandler(baseDir + '/' + name + '/', /.\.ts/g); - } - } - - ///////////////////////////////// - // Given a document root + ts file pattern this class returns: - // all the TS files OR just tests OR just definition files - ///////////////////////////////// - class FileHandler { - public files: string[] = []; - public typings: Typing[] = []; - - constructor(public path: string, pattern: any) { - this.files = IO.dir(path, pattern, { recursive: true }).sort(); - } - - public allTS(): string[] { - return this.files; - } - - public allTests(): string[] { - var tests = []; - - for (var i = 0; i < this.files.length; i++) { - if (endsWith(this.files[i].toUpperCase(), '-TESTS.TS')) { - tests.push(this.files[i]); - } - } - - return tests; - } - - public allTypings(): string[] { - var typings = {}; - - for (var i = 0; i < this.files.length; i++) { - var file = this.files[i]; - var firName = path.dirname(file.substr(this.path.length + 1)).replace('\\', '/'); - var dir = firName.split('/')[0]; - - if (!typings[dir]) typings[dir] = true; - } - - var list = []; - for (var attr in typings) { - list.push(attr); - } - - return list; - } - } - - ///////////////////////////////// - // Timer.start starts a timer - // Timer.end stops the timer and sets asString to the pretty print value - ///////////////////////////////// - class Timer { - public startTime; - public time = 0; - public asString: string; - - private static prettyDate(date1, date2): string { - var diff = ((date2 - date1) / 1000), - day_diff = Math.floor(diff / 86400); - - if (isNaN(day_diff) || day_diff < 0 || day_diff >= 31) - return; - - return (day_diff == 0 && ( - diff < 60 && (diff + " seconds") || - diff < 120 && "1 minute" || - diff < 3600 && Math.floor(diff / 60) + " minutes" || - diff < 7200 && "1 hour" || - diff < 86400 && Math.floor(diff / 3600) + " hours") || - day_diff == 1 && "Yesterday" || - day_diff < 7 && day_diff + " days" || - day_diff < 31 && Math.ceil(day_diff / 7) + " weeks"); - } - - public start() { - this.time = 0; - this.startTime = this.now(); - } - - private now() { - return Date.now(); - } - - public end() { - this.time = (this.now() - this.startTime) / 1000; - this.asString = Timer.prettyDate(this.startTime, this.now()); - } - } - - ///////////////////////////////// - // All the common things that we pring are functions of this class - ///////////////////////////////// - class Print { - constructor(public version: string, public typings: number, public tsFiles: number) { } - - public out(s) { - process.stdout.write(s); - } - - public printHeader() { - this.out('=============================================================================\n'); - this.out(' \33[36m\33[1mDefinitelyTyped test runner 0.3.0\33[0m\n'); - this.out('=============================================================================\n'); - this.out(' \33[36m\33[1mTypescript version:\33[0m ' + this.version + '\n'); - this.out(' \33[36m\33[1mTypings :\33[0m ' + this.typings + '\n'); - this.out(' \33[36m\33[1mTypeScript files :\33[0m ' + this.tsFiles + '\n'); - } - - public printSyntaxCheckingHeader() { - this.out('============================ \33[34m\33[1mSyntax checking\33[0m ================================\n'); - } - - public printTypingTestsHeader() { - this.out('============================= \33[34m\33[1mTyping tests\33[0m ==================================\n'); - } - - public printSuccessCharacter() { - this.out('\33[36m\33[1m.\33[0m'); - } - - public printFailureCharacter() { - this.out('x'); - } - - public printDiv() { - this.out('-----------------------------------------------------------------------------\n'); - } - - public printBoldDiv() { - this.out('=============================================================================\n'); - } - - public printErrorsHeader() { - this.out('=============================================================================\n'); - this.out(' \33[34m\33[1mErrors in files\33[0m \n'); - this.out('=============================================================================\n'); - } - - public printErrorsForFile(file: File) { - this.out('----------------- For file:' + file.formatName() ); - this.printBreak(); - this.out(file.execResult.stderr); - this.printBreak(); - } - - public printfilesWithSintaxErrorMessage() { - this.out(' \33[36m\33[1mFiles with syntax error\33[0m\n'); - } - - public printFailedTestMessage() { - this.out(' \33[36m\33[1mFailed tests\33[0m\n'); - } - - public printTypingsWithoutTestsMessage() { - this.out(' \33[36m\33[1mTyping without tests\33[0m\n'); - } - - public printTotalMessage() { - this.out(' \33[36m\33[1mTotal\33[0m\n'); - } - - public printErrorFile(file: string) { - this.out(' - ' + file + '\n'); - } - - public printTypingsWithoutTest(file) { - this.out(' - \33[33m\33[1m' + file + '\33[0m\n'); - } - - public printBreak() { - this.out('\n'); - } - - public printSuccessCount(current: number, total: number) { - this.out(' \33[36m\33[1mSuccessful :\33[0m \33[32m\33[1m' + ((current / total) * 100).toFixed(2) + '% (' + current + '/' + total + ')\33[0m\n'); - } - - public printFailedCount(current: number, total: number) { - this.out(' \33[36m\33[1mFailure :\33[0m \33[31m\33[1m' + ((current / total) * 100).toFixed(2) + '% (' + current + '/' + total + ')\33[0m\n'); - } - - public printElapsedTime(time, s) { - this.out(' \33[36m\33[1mElapsed time :\33[0m ~' + time + ' (' + s + 's)\n'); - } - - public printSyntaxErrorCount(current: number, total: number) { - this.out(' \33[36m\33[1mSyntax error :\33[0m \33[31m\33[1m' + ((current / total) * 100).toFixed(2) + '% (' + current + '/' + total + ')\33[0m\n'); - } - - public printTestErrorCount(current: number, total: number) { - this.out(' \33[36m\33[1mFailed tests :\33[0m \33[31m\33[1m' + ((current / total) * 100).toFixed(2) + '% (' + current + '/' + total + ')\33[0m\n'); - } - - public printWithoutTestCount(current: number, total: number) { - this.out(' \33[36m\33[1mWithout tests :\33[0m \33[33m\33[1m' + ((current / total) * 100).toFixed(2) + '% (' + current + '/' + total + ')\33[0m\n'); - } - } - - ///////////////////////////// - // Represents the results of a test file execution - ///////////////////////////// - class File { - static baseDir: string; - - constructor(public baseDir: string, public filePathWithName: string, public execResult: ExecResult) { } - - // From '/complete/path/to/file' to 'specfolder/specfile.d.ts' - public formatName(): string { - var dirName = path.dirname(this.filePathWithName.substr(this.baseDir.length + 1)).replace('\\', '/'); - var dir = dirName.split('/')[0]; - var file = path.basename(this.filePathWithName, '.ts'); - var ext = path.extname(this.filePathWithName); - - return dir + ((dirName.split('/').length > 1) ? '/-/' : '/') + '\33[36m\33[1m' + file + '\33[0m' + ext; - } - } - - - ///////////////////////////// - // Determine syntax errors in typings - ///////////////////////////// - class SyntaxChecking { - - private timer: Timer; - - public files: File[] = []; - - private getFailedFiles(): File[] { - var list: File[] = []; - - for (var i = 0; i < this.files.length; i++) { - if (this.files[i].execResult.exitCode) { - list.push(this.files[i]); - } - } - - return list; - } - - private getSuccessFiles(): File[] { - var list: File[] = []; - - for (var i = 0; i < this.files.length; i++) { - if (!this.files[i].execResult.exitCode) { - list.push(this.files[i]); - } - } - - return list; - } - - constructor(public fileHandler: FileHandler, public out: Print) { - this.timer = new Timer(); - } - - private printStats() { - this.out.printDiv(); - this.out.printElapsedTime(this.timer.asString, this.timer.time); - this.out.printSuccessCount(this.getSuccessFiles().length, this.files.length); - this.out.printFailedCount(this.getFailedFiles().length, this.files.length); - } - - private printFailedFiles() { - if (this.getFailedFiles().length > 0) { - this.out.printDiv(); - - this.out.printfilesWithSintaxErrorMessage(); - - this.out.printDiv(); - - for (var i = 0; i < this.getFailedFiles().length; i++) { - var errorFile = this.getFailedFiles()[i]; - this.out.printErrorFile(errorFile.formatName()); - } - } - } - - private run(it, file, len, maxLen, callback: Function) { - if (!endsWith(file.toUpperCase(), '-TESTS.TS') && endsWith(file.toUpperCase(), '.TS') && file.indexOf('../_infrastructure') < 0) { - new Test(file).run((execResult) => { - - if (execResult.exitCode === 1) { - this.out.printFailureCharacter(); - len++; - } else { - this.out.printSuccessCharacter(); - len++; - } - - this.files.push(new File(this.fileHandler.path, file, execResult)); - - if (len > maxLen) { - len = 0; - this.out.printBreak(); - } - - if (it.hasNext()) { - this.run(it, it.next(), len, maxLen, callback); - } else { - this.out.printBreak(); - this.timer.end(); - this.printFailedFiles(); - this.printStats(); - - callback(this.getFailedFiles().length, this.files.length); - } - }); - } else if (it.hasNext()) { - this.run(it, it.next(), len, maxLen, callback); - } else { - this.out.printBreak(); - this.timer.end(); - this.printStats(); - this.printFailedFiles(); - - callback(this.getFailedFiles().length, this.files.length); - } - } - - public start(callback: Function) { - this.timer.start(); - - var tsFiles = this.fileHandler.allTS(); - - var it = new Iterator(tsFiles); - - var len = 0; - var maxLen = 76; - - if (it.hasNext()) { - this.run(it, it.next(), len, maxLen, callback); - } - } - } - - ///////////////////////////// - // Determines errors in typing tests - ///////////////////////////// - class TestEval { - - private timer: Timer; - - public files: File[] = []; - - private getFailedFiles(): File[] { - var list: File[] = []; - - for (var i = 0; i < this.files.length; i++) { - if (this.files[i].execResult.exitCode) { - list.push(this.files[i]); - } - } - - return list; - } - - private getSuccessFiles(): File[] { - var list: File[] = []; - - for (var i = 0; i < this.files.length; i++) { - if (!this.files[i].execResult.exitCode) { - list.push(this.files[i]); - } - } - - return list; - } - - constructor(public fileHandler: FileHandler, public out: Print) { - this.timer = new Timer(); - } - - private printStats() { - this.out.printDiv(); - this.out.printElapsedTime(this.timer.asString, this.timer.time); - this.out.printSuccessCount(this.getSuccessFiles().length, this.files.length); - this.out.printFailedCount(this.getFailedFiles().length, this.files.length); - } - - private printFailedFiles() { - if (this.getFailedFiles().length > 0) { - this.out.printDiv(); - - this.out.printFailedTestMessage(); - - this.out.printDiv(); - - for (var i = 0; i < this.getFailedFiles().length; i++) { - var errorFile = this.getFailedFiles()[i]; - this.out.printErrorFile(errorFile.formatName()); - } - } - } - - private run(it, file, len, maxLen, callback: Function) { - if (endsWith(file.toUpperCase(), '-TESTS.TS')) { - new Test(file).run((execResult) => { - - if (execResult.exitCode === 1) { - this.out.printFailureCharacter(); - len++; - } else { - this.out.printSuccessCharacter(); - len++; - } - - this.files.push(new File(this.fileHandler.path, file, execResult)); - - if (len > maxLen) { - len = 0; - this.out.printBreak(); - } - - if (it.hasNext()) { - this.run(it, it.next(), len, maxLen, callback); - } else { - this.out.printBreak(); - this.timer.end(); - this.printFailedFiles(); - this.printStats(); - - callback(this.getFailedFiles().length, this.files.length); - } - }); - } else if (it.hasNext()) { - this.run(it, it.next(), len, maxLen, callback); - } else { - this.out.printBreak(); - this.timer.end(); - this.printFailedFiles(); - this.printStats(); - - callback(this.getFailedFiles().length, this.files.length); - } - } - - public start(callback: Function) { - this.timer.start(); - - var tsFiles = this.fileHandler.allTS(); - - var it = new Iterator(tsFiles); - - var len = 0; - var maxLen = 76; - - if (it.hasNext()) { - this.run(it, it.next(), len, maxLen, callback); - } - } - } - - ///////////////////////////////// - // The main class to kick things off - ///////////////////////////////// - export class TestRunner { - private fh: FileHandler; - private out: Print; - private sc: SyntaxChecking; - private te: TestEval; - private typings: Typing[] = []; - - private printTypingsWithoutTest() { - var count = 0; - - if (this.typings.length > 0) { - this.out.printDiv(); - - this.out.printTypingsWithoutTestsMessage(); - - this.out.printDiv(); - - for (var i = 0; i < this.typings.length; i++) { - var typing = this.typings[i]; - if (typing.fileHandler.allTests().length == 0) { - if (typing.name != '_infrastructure' - && typing.name != '_ReSharper.DefinitelyTyped' - && typing.name != 'obj' - && typing.name != 'bin' - && typing.name != 'Properties') { - this.out.printTypingsWithoutTest(typing.name); - count++; - } - } - } - } - - return count; - } - - private printErrorsDetected() { - this.out.printErrorsHeader(); - - var printErrorsForFileIfFound = (file: File) => { - if (file.execResult.exitCode) - this.out.printErrorsForFile(file); - }; - - // sc errors: - this.sc.files.forEach(printErrorsForFileIfFound); - this.out.printBoldDiv(); - - // te errors: - this.te.files.forEach(printErrorsForFileIfFound); - this.out.printBoldDiv(); - } - - constructor(public dtPath: string) { - this.fh = new FileHandler(dtPath, /.\.ts/g); - this.out = new Print('0.9.1.1', this.fh.allTypings().length, this.fh.allTS().length); - this.sc = new SyntaxChecking(this.fh, this.out); - this.te = new TestEval(this.fh, this.out); - - var tpgs = this.fh.allTypings(); - for (var i = 0; i < tpgs.length; i++) { - this.typings.push(new Typing(tpgs[i], this.dtPath)); - } - } - - public run() { - var timer = new Timer(); - timer.start(); - - this.out.printHeader(); - - // Run syntax tests - this.out.printSyntaxCheckingHeader(); - this.sc.start((syntaxFailedCount, syntaxTotal) => { - - // Now run typing tests - this.out.printTypingTestsHeader(); - this.te.start((testFailedCount, testTotal) => { - - // Get the tests without any typing and simultaneously print their names - var totalTypingsWithoutTest = this.printTypingsWithoutTest(); - - // End total timer and print final messages - timer.end(); - - this.out.printDiv(); - this.out.printTotalMessage(); - this.out.printDiv(); - - this.out.printElapsedTime(timer.asString, timer.time); - this.out.printSyntaxErrorCount(syntaxFailedCount, syntaxTotal); - this.out.printTestErrorCount(testFailedCount, testTotal); - this.out.printWithoutTestCount(totalTypingsWithoutTest, this.fh.allTypings().length); - - this.out.printDiv(); - - if (syntaxFailedCount > 0 || testFailedCount > 0) { - this.printErrorsDetected(); - process.exit(1); - } - }); - }); - } - } -} - -var dtPath = __dirname + '/../..'; - -var runner = new DefinitelyTyped.TestManager.TestRunner(dtPath); -runner.run(); +/// + +/// +/// + +module DefinitelyTyped.TestManager { + var path = require('path'); + + function endsWith(str:string, suffix:string) { + return str.indexOf(suffix, str.length - suffix.length) !== -1; + } + + export interface TscExecOptions { + useTscParams?:boolean; + checkNoImplicitAny?:boolean; + } + + class Tsc { + public static run(tsfile:string, options:TscExecOptions, callback:(result:ExecResult)=>void) { + options = options || {}; + if (typeof options.checkNoImplicitAny === "undefined") { + options.checkNoImplicitAny = true; + } + if (typeof options.useTscParams === "undefined") { + options.useTscParams = true; + } + + if (!IO.fileExists(tsfile)) { + throw new Error(tsfile + " not exists"); + } + + var command = 'node ./_infrastructure/tests/typescript/tsc.js --module commonjs '; + if (options.useTscParams && IO.fileExists(tsfile + '.tscparams')) { + command += '@' + tsfile + '.tscparams'; + } else if (options.checkNoImplicitAny) { + command += '--noImplicitAny'; + } + Exec.exec(command, [tsfile], (execResult) => { + callback(execResult); + }); + } + } + + class Test { + constructor(public suite:ITestSuite, public tsfile:File, public options?:TscExecOptions) { + } + + public run(callback:(result:TestResult)=>void) { + Tsc.run(this.tsfile.filePathWithName, this.options, (execResult)=> { + var testResult = new TestResult(); + testResult.hostedBy = this.suite; + testResult.targetFile = this.tsfile; + testResult.options = this.options; + + testResult.stdout = execResult.stdout; + testResult.stderr = execResult.stderr; + testResult.exitCode = execResult.exitCode; + + callback(testResult); + }); + } + } + + ///////////////////////////////// + // Timer.start starts a timer + // Timer.end stops the timer and sets asString to the pretty print value + ///////////////////////////////// + export class Timer { + startTime:number; + time = 0; + asString:string; + + public start() { + this.time = 0; + this.startTime = this.now(); + } + + public now():number { + return Date.now(); + } + + public end() { + this.time = (this.now() - this.startTime) / 1000; + this.asString = Timer.prettyDate(this.startTime, this.now()); + } + + public static prettyDate(date1:number, date2:number):string { + var diff = ((date2 - date1) / 1000), + day_diff = Math.floor(diff / 86400); + + if (isNaN(day_diff) || day_diff < 0 || day_diff >= 31) + return; + + return (day_diff == 0 && ( + diff < 60 && (diff + " seconds") || + diff < 120 && "1 minute" || + diff < 3600 && Math.floor(diff / 60) + " minutes" || + diff < 7200 && "1 hour" || + diff < 86400 && Math.floor(diff / 3600) + " hours") || + day_diff == 1 && "Yesterday" || + day_diff < 7 && day_diff + " days" || + day_diff < 31 && Math.ceil(day_diff / 7) + " weeks"); + } + } + + ///////////////////////////////// + // Given a document root + ts file pattern this class returns: + // all the TS files OR just tests OR just definition files + ///////////////////////////////// + export class File { + dir:string; + file:string; + ext:string; + + constructor(public baseDir:string, public filePathWithName:string) { + var dirName = path.dirname(this.filePathWithName.substr(this.baseDir.length + 1)).replace('\\', '/'); + this.dir = dirName.split('/')[0]; + this.file = path.basename(this.filePathWithName, '.ts'); + this.ext = path.extname(this.filePathWithName); + } + + // From '/complete/path/to/file' to 'specfolder/specfile.d.ts' + public get formatName():string { + var dirName = path.dirname(this.filePathWithName.substr(this.baseDir.length + 1)).replace('\\', '/'); + return this.dir + ((dirName.split('/').length > 1) ? '/-/' : '/') + this.file + this.ext; + } + } + + ///////////////////////////////// + // Test results + ///////////////////////////////// + export class TestResult { + hostedBy:ITestSuite; + targetFile:File; + options:TscExecOptions; + + stdout:string; + stderr:string; + exitCode:number; + + public get success():boolean { + return this.exitCode !== 1; + } + } + + ///////////////////////////////// + // The interface for test suite + ///////////////////////////////// + export interface ITestSuite { + testSuiteName:string; + errorHeadline:string; + filterTargetFiles(files:File[]):File[]; + + start(targetFiles:File[], testCallback:(result:TestResult, index:number)=>void, suiteCallback:(suite:ITestSuite)=>void):void; + + testResults:TestResult[]; + okTests:TestResult[]; + ngTests:TestResult[]; + timer:Timer; + + testReporter:ITestReporter; + printErrorCount:boolean; + } + + ///////////////////////////////// + // Test reporter interface + // for example, . and x + ///////////////////////////////// + export interface ITestReporter { + printPositiveCharacter(index:number, testResult:TestResult):void; + printNegativeCharacter(index:number, testResult:TestResult):void; + } + + ///////////////////////////////// + // Default test reporter + ///////////////////////////////// + class DefaultTestReporter implements ITestReporter { + constructor(public print:Print) { + } + + public printPositiveCharacter(index:number, testResult:TestResult) { + this.print.out('\33[36m\33[1m' + '.' + '\33[0m'); + + this.printBreakIfNeeded(index); + } + + public printNegativeCharacter(index:number, testResult:TestResult) { + this.print.out("x"); + + this.printBreakIfNeeded(index); + } + + private printBreakIfNeeded(index:number) { + if (index % this.print.WIDTH === 0) { + this.print.printBreak(); + } + } + } + + ///////////////////////////////// + // All the common things that we pring are functions of this class + ///////////////////////////////// + class Print { + + WIDTH = 77; + + constructor(public version:string, public typings:number, public tests:number, public tsFiles:number) { + } + + public out(s:any):Print { + process.stdout.write(s); + return this; + } + + public repeat(s:string, times:number):string { + return new Array(times + 1).join(s); + } + + public printHeader() { + this.out('=============================================================================\n'); + this.out(' \33[36m\33[1mDefinitelyTyped test runner 0.4.0\33[0m\n'); + this.out('=============================================================================\n'); + this.out(' \33[36m\33[1mTypescript version:\33[0m ' + this.version + '\n'); + this.out(' \33[36m\33[1mTypings :\33[0m ' + this.typings + '\n'); + this.out(' \33[36m\33[1mTests :\33[0m ' + this.tests + '\n'); + this.out(' \33[36m\33[1mTypeScript files :\33[0m ' + this.tsFiles + '\n'); + } + + public printSuiteHeader(title:string) { + var left = Math.floor((this.WIDTH - title.length ) / 2) - 1; + var right = Math.ceil((this.WIDTH - title.length ) / 2) - 1; + this.out(this.repeat("=", left)).out(" \33[34m\33[1m"); + this.out(title); + this.out("\33[0m ").out(this.repeat("=", right)).printBreak(); + } + + public printDiv() { + this.out('-----------------------------------------------------------------------------\n'); + } + + public printBoldDiv() { + this.out('=============================================================================\n'); + } + + public printErrorsHeader() { + this.out('=============================================================================\n'); + this.out(' \33[34m\33[1mErrors in files\33[0m \n'); + this.out('=============================================================================\n'); + } + + public printErrorsForFile(testResult:TestResult) { + this.out('----------------- For file:' + testResult.targetFile.formatName); + this.printBreak().out(testResult.stderr).printBreak(); + } + + public printBreak():Print { + this.out('\n'); + return this; + } + + public clearCurrentLine():Print { + this.out("\r\33[K"); + return this; + } + + public printSuccessCount(current:number, total:number) { + this.out(' \33[36m\33[1mSuccessful :\33[0m \33[32m\33[1m' + ((current / total) * 100).toFixed(2) + '% (' + current + '/' + total + ')\33[0m\n'); + } + + public printFailedCount(current:number, total:number) { + this.out(' \33[36m\33[1mFailure :\33[0m \33[31m\33[1m' + ((current / total) * 100).toFixed(2) + '% (' + current + '/' + total + ')\33[0m\n'); + } + + public printTypingsWithoutTestsMessage() { + this.out(' \33[36m\33[1mTyping without tests\33[0m\n'); + } + + public printTotalMessage() { + this.out(' \33[36m\33[1mTotal\33[0m\n'); + } + + public printElapsedTime(time:string, s:number) { + this.out(' \33[36m\33[1mElapsed time :\33[0m ~' + time + ' (' + s + 's)\n'); + } + + public printSuiteErrorCount(errorHeadline:string, current:number, total:number, valuesColor = '\33[31m\33[1m') { + this.out(' \33[36m\33[1m').out(errorHeadline).out(this.repeat(' ', 16 - errorHeadline.length)); + this.out(':\33[0m ' + valuesColor + ((current / total) * 100).toFixed(2) + '% (' + current + '/' + total + ')\33[0m\n'); + } + + public printTypingsWithoutTestName(file:string) { + this.out(' - \33[33m\33[1m' + file + '\33[0m\n'); + } + + public printTypingsWithoutTest(withoutTestTypings:string[]) { + if (withoutTestTypings.length > 0) { + this.printTypingsWithoutTestsMessage(); + + this.printDiv(); + withoutTestTypings.forEach(t=> { + this.printTypingsWithoutTestName(t); + }); + } + } + } + + ///////////////////////////////// + // Base class for test suite + ///////////////////////////////// + class TestSuiteBase implements ITestSuite { + timer:Timer = new Timer(); + testResults:TestResult[] = []; + testReporter:ITestReporter; + printErrorCount = true; + + constructor(public testSuiteName:string, public errorHeadline:string) { + } + + public filterTargetFiles(files:File[]):File[] { + throw new Error("please implement this method"); + } + + public start(targetFiles:File[], testCallback:(result:TestResult, index:number)=>void, suiteCallback:(suite:ITestSuite)=>void):void { + targetFiles = this.filterTargetFiles(targetFiles); + this.timer.start(); + var count = 0; + // exec test is async process. serialize. + var executor = () => { + var targetFile = targetFiles[count]; + if (targetFile) { + this.runTest(targetFile, (result)=> { + testCallback(result, count + 1); + count++; + executor(); + }); + } else { + this.timer.end(); + this.finish(suiteCallback); + } + }; + executor(); + } + + public runTest(targetFile:File, callback:(result:TestResult)=>void):void { + new Test(this, targetFile, null).run(result=> { + this.testResults.push(result); + callback(result); + }); + } + + public finish(suiteCallback:(suite:ITestSuite)=>void) { + suiteCallback(this); + } + + public get okTests():TestResult[] { + return this.testResults.filter(r=>r.success); + } + + public get ngTests():TestResult[] { + return this.testResults.filter(r=>!r.success); + } + } + + ///////////////////////////////// + // .d.ts syntax inspection + ///////////////////////////////// + class SyntaxChecking extends TestSuiteBase { + + constructor() { + super("Syntax checking", "Syntax error"); + } + + public filterTargetFiles(files:File[]):File[] { + return files.filter(file => endsWith(file.formatName.toUpperCase(), '.D.TS')); + } + } + + ///////////////////////////////// + // Compile with *-tests.ts + ///////////////////////////////// + class TestEval extends TestSuiteBase { + + constructor() { + super("Typing tests", "Failed tests"); + } + + public filterTargetFiles(files:File[]):File[] { + return files.filter(file => endsWith(file.formatName.toUpperCase(), '-TESTS.TS')); + } + } + + ///////////////////////////////// + // The main class to kick things off + ///////////////////////////////// + export class TestRunner { + files:File[]; + timer:Timer; + suites:ITestSuite[] = []; + private print:Print; + + constructor(dtPath:string) { + var filesName = IO.dir(dtPath, /.\.ts/g, { recursive: true }).sort(); + // only includes .d.ts or -tests.ts or -test.ts or .ts + filesName = filesName + .filter(fileName => fileName.indexOf('../_infrastructure') < 0) + .filter(fileName => !endsWith(fileName, ".tscparams")); + this.files = filesName.map(fileName => new File(dtPath, fileName)); + } + + public addSuite(suite:ITestSuite) { + this.suites.push(suite); + } + + public run() { + this.timer = new Timer(); + this.timer.start(); + + var syntaxChecking = new SyntaxChecking(); + var testEval = new TestEval(); + this.addSuite(syntaxChecking); + this.addSuite(testEval); + + var typings = syntaxChecking.filterTargetFiles(this.files).length; + var testFiles = testEval.filterTargetFiles(this.files).length; + this.print = new Print('0.9.1.1', typings, testFiles, this.files.length); + this.print.printHeader(); + + var count = 0; + var executor = () => { + var suite = this.suites[count]; + if (suite) { + suite.testReporter = suite.testReporter || new DefaultTestReporter(this.print); + + this.print.printSuiteHeader(suite.testSuiteName); + var targetFiles = suite.filterTargetFiles(this.files); + suite.start( + targetFiles, + (testResult, index) => { + this.testCompleteCallback(testResult, index); + }, + suite=> { + this.suiteCompleteCallback(suite); + count++; + executor(); + }); + } else { + this.timer.end(); + this.allTestCompleteCallback(); + } + }; + executor(); + } + + private testCompleteCallback(testResult:TestResult, index:number) { + var reporter = testResult.hostedBy.testReporter; + if (testResult.success) { + reporter.printPositiveCharacter(index, testResult); + } else { + reporter.printNegativeCharacter(index, testResult); + } + } + + private suiteCompleteCallback(suite:ITestSuite) { + 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 allTestCompleteCallback() { + var testEval = this.suites.filter(suite=>suite instanceof TestEval)[0]; + if (testEval) { + var existsTestTypings:string[] = testEval.testResults + .map(testResult=>testResult.targetFile.dir) + .reduce((a, b)=> a.indexOf(b) < 0 ? a.concat([b]) : a, []); + var typings:string[] = this.files + .map(file=>file.dir) + .reduce((a, b)=> a.indexOf(b) < 0 ? a.concat([b]) : a, []); + var withoutTestTypings:string[] = typings + .filter(typing=>existsTestTypings.indexOf(typing) < 0); + this.print.printDiv(); + this.print.printTypingsWithoutTest(withoutTestTypings); + } + + this.print.printDiv(); + this.print.printTotalMessage(); + + this.print.printDiv(); + this.print.printElapsedTime(this.timer.asString, this.timer.time); + this.suites + .filter(suite=>suite.printErrorCount) + .forEach(suite=> { + this.print.printSuiteErrorCount(suite.errorHeadline, suite.ngTests.length, suite.testResults.length); + }); + if (testEval) { + this.print.printSuiteErrorCount("Without tests", withoutTestTypings.length, typings.length, '\33[33m\33[1m'); + } + + this.print.printDiv(); + if (this.suites.some(suite=>suite.ngTests.length !== 0)) { + this.print.printErrorsHeader(); + + this.suites + .filter(suite=>suite.ngTests.length !== 0) + .forEach(suite=> { + suite.ngTests.forEach(testResult => { + this.print.printErrorsForFile(testResult); + }); + this.print.printBoldDiv(); + }); + + process.exit(1); + } + } + } +} + +var dtPath = __dirname + '/../..'; + +var runner = new DefinitelyTyped.TestManager.TestRunner(dtPath); +runner.run();