1import * as Playback from "./_namespaces/Playback"; 2import * as Harness from "./_namespaces/Harness"; 3import * as compiler from "./_namespaces/compiler"; 4import * as ts from "./_namespaces/ts"; 5import * as vpath from "./_namespaces/vpath"; 6 7// In harness baselines, null is different than undefined. See `generateActual` in `harness.ts`. 8function runWithIOLog(ioLog: Playback.IoLog, fn: (oldIO: Harness.IO) => void) { 9 const oldIO = Harness.IO; 10 11 const wrappedIO = Playback.wrapIO(oldIO); 12 wrappedIO.startReplayFromData(ioLog); 13 Harness.setHarnessIO(wrappedIO); 14 15 try { 16 fn(oldIO); 17 } 18 finally { 19 wrappedIO.endReplay(); 20 Harness.setHarnessIO(oldIO); 21 } 22} 23 24export function runRWCTest(jsonPath: string) { 25 describe("Testing a rwc project: " + jsonPath, () => { 26 let inputFiles: Harness.Compiler.TestFile[] = []; 27 let otherFiles: Harness.Compiler.TestFile[] = []; 28 let tsconfigFiles: Harness.Compiler.TestFile[] = []; 29 let compilerResult: compiler.CompilationResult; 30 let compilerOptions: ts.CompilerOptions; 31 const baselineOpts: Harness.Baseline.BaselineOptions = { 32 Subfolder: "rwc", 33 Baselinefolder: "internal/baselines" 34 }; 35 const baseName = ts.getBaseFileName(jsonPath); 36 let currentDirectory: string; 37 let useCustomLibraryFile: boolean; 38 let caseSensitive: boolean; 39 after(() => { 40 // Mocha holds onto the closure environment of the describe callback even after the test is done. 41 // Therefore we have to clean out large objects after the test is done. 42 inputFiles = []; 43 otherFiles = []; 44 tsconfigFiles = []; 45 compilerResult = undefined!; 46 compilerOptions = undefined!; 47 currentDirectory = undefined!; 48 // useCustomLibraryFile is a flag specified in the json object to indicate whether to use built/local/lib.d.ts 49 // or to use lib.d.ts inside the json object. If the flag is true, use the lib.d.ts inside json file 50 // otherwise use the lib.d.ts from built/local 51 useCustomLibraryFile = false; 52 caseSensitive = false; 53 }); 54 55 it("can compile", function (this: Mocha.Context) { 56 this.timeout(800_000); // Allow long timeouts for RWC compilations 57 let opts!: ts.ParsedCommandLine; 58 59 const ioLog: Playback.IoLog = Playback.newStyleLogIntoOldStyleLog(JSON.parse(Harness.IO.readFile(`internal/cases/rwc/${jsonPath}/test.json`)!), Harness.IO, `internal/cases/rwc/${baseName}`); 60 currentDirectory = ioLog.currentDirectory; 61 useCustomLibraryFile = !!ioLog.useCustomLibraryFile; 62 runWithIOLog(ioLog, () => { 63 opts = ts.parseCommandLine(ioLog.arguments, fileName => Harness.IO.readFile(fileName)); 64 assert.equal(opts.errors.length, 0); 65 66 // To provide test coverage of output javascript file, 67 // we will set noEmitOnError flag to be false. 68 opts.options.noEmitOnError = false; 69 }); 70 let fileNames = opts.fileNames; 71 72 runWithIOLog(ioLog, () => { 73 const tsconfigFile = ts.forEach(ioLog.filesRead, f => vpath.isTsConfigFile(f.path) ? f : undefined); 74 if (tsconfigFile) { 75 const tsconfigFileContents = getHarnessCompilerInputUnit(tsconfigFile.path); 76 tsconfigFiles.push({ unitName: tsconfigFile.path, content: tsconfigFileContents.content }); 77 const parsedTsconfigFileContents = ts.parseJsonText(tsconfigFile.path, tsconfigFileContents.content); 78 const configParseHost: ts.ParseConfigHost = { 79 useCaseSensitiveFileNames: Harness.IO.useCaseSensitiveFileNames(), 80 fileExists: Harness.IO.fileExists, 81 readDirectory: Harness.IO.readDirectory, 82 readFile: Harness.IO.readFile 83 }; 84 const configParseResult = ts.parseJsonSourceFileConfigFileContent(parsedTsconfigFileContents, configParseHost, ts.getDirectoryPath(tsconfigFile.path)); 85 fileNames = configParseResult.fileNames; 86 opts.options = ts.extend(opts.options, configParseResult.options); 87 ts.setConfigFileInOptions(opts.options, configParseResult.options.configFile); 88 } 89 90 // Deduplicate files so they are only printed once in baselines (they are deduplicated within the compiler already) 91 const uniqueNames = new ts.Map<string, true>(); 92 for (const fileName of fileNames) { 93 // Must maintain order, build result list while checking map 94 const normalized = ts.normalizeSlashes(Harness.IO.resolvePath(fileName)!); 95 if (!uniqueNames.has(normalized)) { 96 uniqueNames.set(normalized, true); 97 // Load the file 98 inputFiles.push(getHarnessCompilerInputUnit(fileName)); 99 } 100 } 101 102 // Add files to compilation 103 for (const fileRead of ioLog.filesRead) { 104 const unitName = ts.normalizeSlashes(Harness.IO.resolvePath(fileRead.path)!); 105 if (!uniqueNames.has(unitName) && !Harness.isDefaultLibraryFile(fileRead.path)) { 106 uniqueNames.set(unitName, true); 107 otherFiles.push(getHarnessCompilerInputUnit(fileRead.path)); 108 } 109 else if (!opts.options.noLib && Harness.isDefaultLibraryFile(fileRead.path) && !uniqueNames.has(unitName) && useCustomLibraryFile) { 110 // If useCustomLibraryFile is true, we will use lib.d.ts from json object 111 // otherwise use the lib.d.ts from built/local 112 // Majority of RWC code will be using built/local/lib.d.ts instead of 113 // lib.d.ts inside json file. However, some RWC cases will still use 114 // their own version of lib.d.ts because they have customized lib.d.ts 115 uniqueNames.set(unitName, true); 116 inputFiles.push(getHarnessCompilerInputUnit(fileRead.path)); 117 } 118 } 119 }); 120 121 if (useCustomLibraryFile) { 122 // do not use lib since we already read it in above 123 opts.options.lib = undefined; 124 opts.options.noLib = true; 125 } 126 127 caseSensitive = ioLog.useCaseSensitiveFileNames || false; 128 // Emit the results 129 compilerResult = Harness.Compiler.compileFiles( 130 inputFiles, 131 otherFiles, 132 { useCaseSensitiveFileNames: "" + caseSensitive }, 133 opts.options, 134 // Since each RWC json file specifies its current directory in its json file, we need 135 // to pass this information in explicitly instead of acquiring it from the process. 136 currentDirectory); 137 compilerOptions = compilerResult.options; 138 139 function getHarnessCompilerInputUnit(fileName: string): Harness.Compiler.TestFile { 140 const unitName = ts.normalizeSlashes(Harness.IO.resolvePath(fileName)!); 141 let content: string; 142 try { 143 content = Harness.IO.readFile(unitName)!; 144 } 145 catch (e) { 146 content = Harness.IO.readFile(fileName)!; 147 } 148 return { unitName, content }; 149 } 150 }); 151 152 153 it("has the expected emitted code", function (this: Mocha.Context) { 154 this.timeout(100_000); // Allow longer timeouts for RWC js verification 155 Harness.Baseline.runMultifileBaseline(baseName, "", () => { 156 return Harness.Compiler.iterateOutputs(compilerResult.js.values()); 157 }, baselineOpts, [".js", ".jsx"]); 158 }); 159 160 it("has the expected declaration file content", () => { 161 Harness.Baseline.runMultifileBaseline(baseName, "", () => { 162 if (!compilerResult.dts.size) { 163 return null; // eslint-disable-line no-null/no-null 164 } 165 166 return Harness.Compiler.iterateOutputs(compilerResult.dts.values()); 167 }, baselineOpts, [".d.ts"]); 168 }); 169 170 it("has the expected source maps", () => { 171 Harness.Baseline.runMultifileBaseline(baseName, "", () => { 172 if (!compilerResult.maps.size) { 173 return null; // eslint-disable-line no-null/no-null 174 } 175 176 return Harness.Compiler.iterateOutputs(compilerResult.maps.values()); 177 }, baselineOpts, [".map"]); 178 }); 179 180 it("has the expected errors", () => { 181 Harness.Baseline.runMultifileBaseline(baseName, ".errors.txt", () => { 182 if (compilerResult.diagnostics.length === 0) { 183 return null; // eslint-disable-line no-null/no-null 184 } 185 // Do not include the library in the baselines to avoid noise 186 const baselineFiles = tsconfigFiles.concat(inputFiles, otherFiles).filter(f => !Harness.isDefaultLibraryFile(f.unitName)); 187 const errors = compilerResult.diagnostics.filter(e => !e.file || !Harness.isDefaultLibraryFile(e.file.fileName)); 188 return Harness.Compiler.iterateErrorBaseline(baselineFiles, errors, { caseSensitive, currentDirectory }); 189 }, baselineOpts); 190 }); 191 192 // Ideally, a generated declaration file will have no errors. But we allow generated 193 // declaration file errors as part of the baseline. 194 it("has the expected errors in generated declaration files", () => { 195 if (compilerOptions.declaration && !compilerResult.diagnostics.length) { 196 Harness.Baseline.runMultifileBaseline(baseName, ".dts.errors.txt", () => { 197 if (compilerResult.diagnostics.length === 0) { 198 return null; // eslint-disable-line no-null/no-null 199 } 200 201 const declContext = Harness.Compiler.prepareDeclarationCompilationContext( 202 inputFiles, otherFiles, compilerResult, /*harnessSettings*/ undefined!, compilerOptions, currentDirectory // TODO: GH#18217 203 ); 204 // Reset compilerResult before calling into `compileDeclarationFiles` so the memory from the original compilation can be freed 205 const links = compilerResult.symlinks; 206 compilerResult = undefined!; 207 const declFileCompilationResult = Harness.Compiler.compileDeclarationFiles(declContext, links)!; 208 209 return Harness.Compiler.iterateErrorBaseline(tsconfigFiles.concat(declFileCompilationResult.declInputFiles, declFileCompilationResult.declOtherFiles), declFileCompilationResult.declResult.diagnostics, { caseSensitive, currentDirectory }); 210 }, baselineOpts); 211 } 212 }); 213 }); 214} 215 216export class RWCRunner extends Harness.RunnerBase { 217 public enumerateTestFiles() { 218 // see also: `enumerateTestFiles` in tests/webTestServer.ts 219 return Harness.IO.getDirectories("internal/cases/rwc/"); 220 } 221 222 public kind(): Harness.TestRunnerKind { 223 return "rwc"; 224 } 225 226 /** Setup the runner's tests so that they are ready to be executed by the harness 227 * The first test should be a describe/it block that sets up the harness's compiler instance appropriately 228 */ 229 public initializeTests(): void { 230 // Read in and evaluate the test list 231 for (const test of this.tests && this.tests.length ? this.tests : this.getTestFiles()) { 232 this.runTest(typeof test === "string" ? test : test.file); 233 } 234 } 235 236 private runTest(jsonFileName: string) { 237 runRWCTest(jsonFileName); 238 } 239} 240