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