diff --git a/Jakefile b/Jakefile index b803f23eae8ac..5336e6058d178 100644 --- a/Jakefile +++ b/Jakefile @@ -123,6 +123,8 @@ function concatenateFiles(destinationFile, sourceFiles) { } var useDebugMode = false; +var host = (process.env.host || process.env.TYPESCRIPT_HOST || "node"); +var compilerFilename = "tsc.js"; /* Compiles a file from a list of sources * @param outFile: the target file name * @param sources: an array of the names of the source files @@ -134,10 +136,9 @@ var useDebugMode = false; function compileFile(outFile, sources, prereqs, prefixes, useBuiltCompiler, noOutFile) { file(outFile, prereqs, function() { var dir = useBuiltCompiler ? builtLocalDirectory : LKGDirectory; - var compilerFilename = "tsc.js"; var options = "-removeComments --module commonjs -noImplicitAny "; //" -propagateEnumConstants " - var cmd = (process.env.host || process.env.TYPESCRIPT_HOST || "node") + " " + dir + compilerFilename + " " + options + " "; + var cmd = host + " " + dir + compilerFilename + " " + options + " "; if (useDebugMode) { cmd = cmd + " " + path.join(harnessDirectory, "external/es5compat.ts") + " " + path.join(harnessDirectory, "external/json2.ts") + " "; } @@ -230,7 +231,7 @@ task("generate-diagnostics", [diagnosticInfoMapTs]) // Local target to build the compiler and services -var tscFile = path.join(builtLocalDirectory, "tsc.js"); +var tscFile = path.join(builtLocalDirectory, compilerFilename); compileFile(tscFile, compilerSources, [builtLocalDirectory, copyright].concat(compilerSources), [copyright], /*useBuiltCompiler:*/ false); var servicesFile = path.join(builtLocalDirectory, "typescriptServices.js"); @@ -312,9 +313,9 @@ function exec(cmd, completeHandler) { complete(); }); ex.addListener("error", function(e, status) { - process.stderr.write(status); - process.stderr.write(e); - complete(); + process.stderr.write(status); + process.stderr.write(e); + complete(); }) try{ ex.run(); @@ -377,9 +378,9 @@ task("runtests", ["tests", builtLocalDirectory], function() { desc("Generates code coverage data via instanbul") task("generate-code-coverage", ["tests", builtLocalDirectory], function () { - var cmd = 'istanbul cover node_modules/mocha/bin/_mocha -- -R min -t ' + testTimeout + ' ' + run; - console.log(cmd); - exec(cmd); + var cmd = 'istanbul cover node_modules/mocha/bin/_mocha -- -R min -t ' + testTimeout + ' ' + run; + console.log(cmd); + exec(cmd); }, { async: true }); // Browser tests @@ -464,7 +465,7 @@ compileFile(webhostJsPath, [webhostPath], [tscFile, webhostPath].concat(libraryT desc("Builds the tsc web host"); task("webhost", [webhostJsPath], function() { - jake.cpR(path.join(builtLocalDirectory, "lib.d.ts"), "tests/webhost/", {silent: true}); + jake.cpR(path.join(builtLocalDirectory, "lib.d.ts"), "tests/webhost/", {silent: true}); }); // Perf compiler @@ -473,3 +474,36 @@ var perftscJsPath = "built/local/perftsc.js"; compileFile(perftscJsPath, [perftscPath], [tscFile, perftscPath, "tests/perfsys.ts"].concat(libraryTargets), [], true); desc("Builds augmented version of the compiler for perf tests"); task("perftsc", [perftscJsPath]); + +// Instrumented compiler +var loggedIOpath = harnessDirectory + 'loggedIO.ts'; +var loggedIOJsPath = builtLocalDirectory + 'loggedIO.js'; +file(loggedIOJsPath, [builtLocalDirectory, loggedIOpath], function() { + var temp = builtLocalDirectory + 'temp'; + jake.mkdirP(temp); + var options = "--outdir " + temp + ' ' + loggedIOpath; + var cmd = host + " " + LKGDirectory + compilerFilename + " " + options + " "; + console.log(cmd + "\n"); + var ex = jake.createExec([cmd]); + ex.addListener("cmdEnd", function() { + fs.renameSync(temp + '/harness/loggedIO.js', loggedIOJsPath); + jake.rmRf(temp); + complete(); + }); + ex.run(); +}, {async: true}); + +var instrumenterPath = harnessDirectory + 'instrumenter.ts'; +var instrumenterJsPath = builtLocalDirectory + 'instrumenter.js'; +compileFile(instrumenterJsPath, [instrumenterPath], [tscFile, instrumenterPath], [], true); + +desc("Builds an instrumented tsc.js"); +task('tsc-instrumented', [loggedIOJsPath, instrumenterJsPath, tscFile], function() { + var cmd = host + ' ' + instrumenterJsPath + ' record iocapture ' + builtLocalDirectory + compilerFilename; + console.log(cmd); + var ex = jake.createExec([cmd]); + ex.addListener("cmdEnd", function() { + complete(); + }); + ex.run(); +}, { async: true }); diff --git a/src/harness/harness.ts b/src/harness/harness.ts index f2488c06b482a..654f240b4b5e8 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -732,8 +732,10 @@ module Harness { var filemap: { [name: string]: ts.SourceFile; } = {}; var register = (file: { unitName: string; content: string; }) => { - var filename = Path.switchToForwardSlashes(file.unitName); - filemap[getCanonicalFileName(filename)] = ts.createSourceFile(filename, file.content, options.target, /*version:*/ "0"); + if (file.content !== undefined) { + var filename = Path.switchToForwardSlashes(file.unitName); + filemap[getCanonicalFileName(filename)] = ts.createSourceFile(filename, file.content, options.target, /*version:*/ "0"); + } }; inputFiles.forEach(register); otherFiles.forEach(register); @@ -824,7 +826,7 @@ module Harness { globalErrors.forEach(err => outputErrorText(err)); // 'merge' the lines of each input file with any errors associated with it - inputFiles.forEach(inputFile => { + inputFiles.filter(f => f.content !== undefined).forEach(inputFile => { // Filter down to the errors in the file var fileErrors = diagnostics.filter(e => { var errFn = e.filename; @@ -1253,7 +1255,7 @@ module Harness { } export function isLibraryFile(filePath: string): boolean { - return filePath.indexOf('lib.d.ts') >= 0 || filePath.indexOf('lib.core.d.ts') >= 0; + return (Path.getFileName(filePath) === 'lib.d.ts') || (Path.getFileName(filePath) === 'lib.core.d.ts'); } if (Error) (Error).stackTraceLimit = 1; diff --git a/src/harness/instrumenter.ts b/src/harness/instrumenter.ts new file mode 100644 index 0000000000000..61f6a8f2ec3ec --- /dev/null +++ b/src/harness/instrumenter.ts @@ -0,0 +1,53 @@ +declare var require: any, process: any; +var fs: any = require('fs'); +var path: any = require('path'); + +function instrumentForRecording(fn: string, tscPath: string) { + instrument(tscPath, 'sys = Playback.wrapSystem(sys); sys.startRecord("' + fn + '");', 'sys.endRecord();'); +} + +function instrumentForReplay(logFilename: string, tscPath: string) { + instrument(tscPath, 'sys = Playback.wrapSystem(sys); sys.startReplay("' + logFilename + '");'); +} + +function instrument(tscPath: string, prepareCode: string, cleanupCode: string = '') { + var bak = tscPath + '.bak'; + fs.exists(bak, (backupExists: boolean) => { + var filename = tscPath; + if (backupExists) { + filename = bak; + } + + fs.readFile(filename, 'utf-8', (err: any, tscContent: string) => { + if (err) throw err; + + fs.writeFile(bak, tscContent, (err: any) => { + if (err) throw err; + + fs.readFile(path.resolve(path.dirname(tscPath) + '/loggedIO.js'), 'utf-8', (err: any, loggerContent: string) => { + if (err) throw err; + + var invocationLine = 'ts.executeCommandLine(sys.args);'; + var index1 = tscContent.indexOf(invocationLine); + var index2 = index1 + invocationLine.length; + var newContent = tscContent.substr(0, index1) + loggerContent + prepareCode + invocationLine + cleanupCode + tscContent.substr(index2) + '\r\n'; + fs.writeFile(tscPath, newContent); + }); + }); + }); + }); +} + +var isJson = (arg: string) => arg.indexOf(".json") > 0; + +var record = process.argv.indexOf('record'); +var tscPath = process.argv[process.argv.length - 1]; +if (record >= 0) { + console.log('Instrumenting ' + tscPath + ' for recording'); + instrumentForRecording(process.argv[record + 1], tscPath); +} else if (process.argv.some(isJson)) { + var filename = process.argv.filter(isJson)[0]; + instrumentForReplay(filename, tscPath); +} + + diff --git a/src/harness/loggedIO.ts b/src/harness/loggedIO.ts index dbc3cdbec2cf5..fc33d08b33434 100644 --- a/src/harness/loggedIO.ts +++ b/src/harness/loggedIO.ts @@ -1,4 +1,6 @@ /// +/// +/// interface FileInformation { contents: string; @@ -93,6 +95,7 @@ module Playback { function createEmptyLog(): IOLog { return { + timestamp: (new Date()).toString(), arguments: [], currentDirectory: '', filesRead: [], @@ -119,6 +122,8 @@ module Playback { }; wrapper.startReplayFromData = log => { replayLog = log; + // Remove non-found files from the log (shouldn't really need them, but we still record them for diganostic purposes) + replayLog.filesRead = replayLog.filesRead.filter(f => f.result.contents !== undefined); }; wrapper.endReplay = () => { @@ -170,22 +175,46 @@ module Playback { } function findResultByPath(wrapper: { resolvePath(s: string): string }, logArray: { path: string; result?: T }[], expectedPath: string, defaultValue?: T): T { - var results = logArray.filter(e => pathsAreEquivalent(e.path, expectedPath, wrapper)); - if (results.length === 0) { - if (defaultValue === undefined) { - throw new Error('No matching result in log array for path: ' + expectedPath); - } else { - return defaultValue; + var normalizedName = Harness.Path.switchToForwardSlashes(expectedPath).toLowerCase(); + // Try to find the result through normal filename + for (var i = 0; i < logArray.length; i++) { + if (Harness.Path.switchToForwardSlashes(logArray[i].path).toLowerCase() === normalizedName) { + return logArray[i].result; } } - return results[0].result; + // Fallback, try to resolve the target paths as well + if (replayLog.pathsResolved.length > 0) { + var normalizedResolvedName = wrapper.resolvePath(expectedPath).toLowerCase(); + for (var i = 0; i < logArray.length; i++) { + if (wrapper.resolvePath(logArray[i].path).toLowerCase() === normalizedResolvedName) { + return logArray[i].result; + } + } + } + // If we got here, we didn't find a match + if (defaultValue === undefined) { + throw new Error('No matching result in log array for path: ' + expectedPath); + } else { + return defaultValue; + } } + var pathEquivCache: any = {}; function pathsAreEquivalent(left: string, right: string, wrapper: { resolvePath(s: string): string }) { + var key = left + '-~~-' + right; function areSame(a: string, b: string) { return Harness.Path.switchToForwardSlashes(a).toLowerCase() === Harness.Path.switchToForwardSlashes(b).toLowerCase(); } - return areSame(left, right) || areSame(wrapper.resolvePath(left), right) || areSame(left, wrapper.resolvePath(right)) || areSame(wrapper.resolvePath(left), wrapper.resolvePath(right)); + function check() { + if (Harness.Path.getFileName(left).toLowerCase() === Harness.Path.getFileName(right).toLowerCase()) { + return areSame(left, right) || areSame(wrapper.resolvePath(left), right) || areSame(left, wrapper.resolvePath(right)) || areSame(wrapper.resolvePath(left), wrapper.resolvePath(right)); + } + } + if (pathEquivCache.hasOwnProperty(key)) { + return pathEquivCache[key]; + } else { + return pathEquivCache[key] = check(); + } } function noOpReplay(name: string) { @@ -258,7 +287,12 @@ module Playback { memoize((path) => findResultByFields(replayLog.pathsResolved, { path: path }, !ts.isRootedDiskPath(ts.normalizeSlashes(path)) && replayLog.currentDirectory ? replayLog.currentDirectory + '/' + path : ts.normalizeSlashes(path)))); wrapper.readFile = recordReplay(wrapper.readFile, underlying)( - (path) => callAndRecord(underlying.readFile(path), recordLog.filesRead, { path: path, codepage: 0 }), + (path) => { + var result = underlying.readFile(path); + var logEntry = { path: path, codepage: 0, result: { contents: result, codepage: 0 } }; + recordLog.filesRead.push(logEntry); + return result; + }, memoize((path) => findResultByPath(wrapper, replayLog.filesRead, path).contents)); wrapper.writeFile = recordReplay(wrapper.writeFile, underlying)( diff --git a/src/harness/rwcRunner.ts b/src/harness/rwcRunner.ts index 1993144cb4758..93d003bf4db98 100644 --- a/src/harness/rwcRunner.ts +++ b/src/harness/rwcRunner.ts @@ -88,7 +88,7 @@ module RWC { } }); - // do not use lib since we shouldnt be reading any files that arent in the ioLog + // do not use lib since we already read it in above opts.options.noLib = true; // Emit the results