1namespace Harness { 2 export const enum CompilerTestType { 3 Conformance, 4 Regressions, 5 Test262, 6 OH 7 } 8 9 interface CompilerFileBasedTest extends FileBasedTest { 10 readonly content?: string; 11 } 12 13 export class CompilerBaselineRunner extends RunnerBase { 14 private basePath = "tests/cases"; 15 private testSuiteName: TestRunnerKind; 16 private emit: boolean; 17 18 public options: string | undefined; 19 20 constructor(public testType: CompilerTestType) { 21 super(); 22 this.emit = true; 23 if (testType === CompilerTestType.Conformance) { 24 this.testSuiteName = "conformance"; 25 } 26 else if (testType === CompilerTestType.Regressions) { 27 this.testSuiteName = "compiler"; 28 } 29 else if (testType === CompilerTestType.Test262) { 30 this.testSuiteName = "test262"; 31 } 32 else if (testType === CompilerTestType.OH) { 33 this.testSuiteName = "compiler-oh"; 34 } 35 else { 36 this.testSuiteName = "compiler"; // default to this for historical reasons 37 } 38 this.basePath += "/" + this.testSuiteName; 39 } 40 41 public kind() { 42 return this.testSuiteName; 43 } 44 45 public enumerateTestFiles() { 46 // see also: `enumerateTestFiles` in tests/webTestServer.ts 47 return this.enumerateFiles(this.basePath, /\.tsx?$/, { recursive: true }).map(CompilerTest.getConfigurations); 48 } 49 50 public initializeTests() { 51 describe(this.testSuiteName + " tests", () => { 52 describe("Setup compiler for compiler baselines", () => { 53 this.parseOptions(); 54 }); 55 56 // this will set up a series of describe/it blocks to run between the setup and cleanup phases 57 const files = this.tests.length > 0 ? this.tests : IO.enumerateTestFiles(this); 58 files.forEach(test => { 59 const file = typeof test === "string" ? test : test.file; 60 this.checkTestCodeOutput(vpath.normalizeSeparators(file), typeof test === "string" ? CompilerTest.getConfigurations(test) : test); 61 }); 62 }); 63 } 64 65 public checkTestCodeOutput(fileName: string, test?: CompilerFileBasedTest) { 66 if (test && ts.some(test.configurations)) { 67 test.configurations.forEach(configuration => { 68 describe(`${this.testSuiteName} tests for ${fileName}${configuration ? ` (${getFileBasedTestConfigurationDescription(configuration)})` : ``}`, () => { 69 this.runSuite(fileName, test, configuration); 70 }); 71 }); 72 } 73 else { 74 describe(`${this.testSuiteName} tests for ${fileName}`, () => { 75 this.runSuite(fileName, test); 76 }); 77 } 78 } 79 80 private runSuite(fileName: string, test?: CompilerFileBasedTest, configuration?: FileBasedTestConfiguration) { 81 // Mocha holds onto the closure environment of the describe callback even after the test is done. 82 // Everything declared here should be cleared out in the "after" callback. 83 let compilerTest!: CompilerTest; 84 before(() => { 85 let payload; 86 if (test && test.content) { 87 const rootDir = test.file.indexOf("conformance") === -1 ? "tests/cases/compiler/" : ts.getDirectoryPath(test.file) + "/"; 88 payload = TestCaseParser.makeUnitsFromTest(test.content, test.file, rootDir); 89 } 90 compilerTest = new CompilerTest(fileName, payload, configuration); 91 }); 92 it(`Correct errors for ${fileName}`, () => { compilerTest.verifyDiagnostics(); }); 93 it(`Correct module resolution tracing for ${fileName}`, () => { compilerTest.verifyModuleResolution(); }); 94 it(`Correct sourcemap content for ${fileName}`, () => { compilerTest.verifySourceMapRecord(); }); 95 it(`Correct JS output for ${fileName}`, () => { if (this.emit) compilerTest.verifyJavaScriptOutput(); }); 96 it(`Correct Sourcemap output for ${fileName}`, () => { compilerTest.verifySourceMapOutput(); }); 97 it(`Correct type/symbol baselines for ${fileName}`, () => { compilerTest.verifyTypesAndSymbols(); }); 98 after(() => { compilerTest = undefined!; }); 99 } 100 101 private parseOptions() { 102 if (this.options && this.options.length > 0) { 103 this.emit = false; 104 105 const opts = this.options.split(","); 106 for (const opt of opts) { 107 switch (opt) { 108 case "emit": 109 this.emit = true; 110 break; 111 default: 112 throw new Error("unsupported flag"); 113 } 114 } 115 } 116 } 117 } 118 119 class CompilerTest { 120 private static varyBy: readonly string[] = [ 121 "module", 122 "target", 123 "jsx", 124 "removeComments", 125 "importHelpers", 126 "importHelpers", 127 "downlevelIteration", 128 "isolatedModules", 129 "strict", 130 "noImplicitAny", 131 "strictNullChecks", 132 "strictFunctionTypes", 133 "strictBindCallApply", 134 "strictPropertyInitialization", 135 "noImplicitThis", 136 "alwaysStrict", 137 "allowSyntheticDefaultImports", 138 "esModuleInterop", 139 "emitDecoratorMetadata", 140 "skipDefaultLibCheck", 141 "preserveConstEnums", 142 "skipLibCheck", 143 ]; 144 private fileName: string; 145 private justName: string; 146 private configuredName: string; 147 private lastUnit: TestCaseParser.TestUnitData; 148 private harnessSettings: TestCaseParser.CompilerSettings; 149 private hasNonDtsFiles: boolean; 150 private result: compiler.CompilationResult; 151 private options: ts.CompilerOptions; 152 private tsConfigFiles: Compiler.TestFile[]; 153 // equivalent to the files that will be passed on the command line 154 private toBeCompiled: Compiler.TestFile[]; 155 // equivalent to other files on the file system not directly passed to the compiler (ie things that are referenced by other files) 156 private otherFiles: Compiler.TestFile[]; 157 158 constructor(fileName: string, testCaseContent?: TestCaseParser.TestCaseContent, configurationOverrides?: TestCaseParser.CompilerSettings) { 159 this.fileName = fileName; 160 this.justName = vpath.basename(fileName); 161 this.configuredName = this.justName; 162 if (configurationOverrides) { 163 let configuredName = ""; 164 const keys = Object 165 .keys(configurationOverrides) 166 .sort(); 167 for (const key of keys) { 168 if (configuredName) { 169 configuredName += ","; 170 } 171 configuredName += `${key.toLowerCase()}=${configurationOverrides[key].toLowerCase()}`; 172 } 173 if (configuredName) { 174 const extname = vpath.extname(this.justName); 175 const basename = vpath.basename(this.justName, extname, /*ignoreCase*/ true); 176 this.configuredName = `${basename}(${configuredName})${extname}`; 177 } 178 } 179 180 const rootDir = fileName.indexOf("conformance") === -1 ? "tests/cases/compiler/" : ts.getDirectoryPath(fileName) + "/"; 181 182 if (testCaseContent === undefined) { 183 testCaseContent = TestCaseParser.makeUnitsFromTest(IO.readFile(fileName)!, fileName, rootDir); 184 } 185 186 if (configurationOverrides) { 187 testCaseContent = { ...testCaseContent, settings: { ...testCaseContent.settings, ...configurationOverrides } }; 188 } 189 190 const units = testCaseContent.testUnitData; 191 this.harnessSettings = testCaseContent.settings; 192 let tsConfigOptions: ts.CompilerOptions | undefined; 193 this.tsConfigFiles = []; 194 if (testCaseContent.tsConfig) { 195 assert.equal(testCaseContent.tsConfig.fileNames.length, 0, `list of files in tsconfig is not currently supported`); 196 assert.equal(testCaseContent.tsConfig.raw.exclude, undefined, `exclude in tsconfig is not currently supported`); 197 198 tsConfigOptions = ts.cloneCompilerOptions(testCaseContent.tsConfig.options); 199 this.tsConfigFiles.push(this.createHarnessTestFile(testCaseContent.tsConfigFileUnitData!, rootDir, ts.combinePaths(rootDir, tsConfigOptions.configFilePath))); 200 } 201 else { 202 const baseUrl = this.harnessSettings.baseUrl; 203 if (baseUrl !== undefined && !ts.isRootedDiskPath(baseUrl)) { 204 this.harnessSettings.baseUrl = ts.getNormalizedAbsolutePath(baseUrl, rootDir); 205 } 206 } 207 208 this.lastUnit = units[units.length - 1]; 209 this.hasNonDtsFiles = units.some(unit => !ts.isDeclarationFileName(unit.name)); 210 // We need to assemble the list of input files for the compiler and other related files on the 'filesystem' (ie in a multi-file test) 211 // If the last file in a test uses require or a triple slash reference we'll assume all other files will be brought in via references, 212 // otherwise, assume all files are just meant to be in the same compilation session without explicit references to one another. 213 this.toBeCompiled = []; 214 this.otherFiles = []; 215 216 if (testCaseContent.settings.noImplicitReferences || /require\(/.test(this.lastUnit.content) || /reference\spath/.test(this.lastUnit.content)) { 217 this.toBeCompiled.push(this.createHarnessTestFile(this.lastUnit, rootDir)); 218 units.forEach(unit => { 219 if (unit.name !== this.lastUnit.name) { 220 this.otherFiles.push(this.createHarnessTestFile(unit, rootDir)); 221 } 222 }); 223 } 224 else { 225 this.toBeCompiled = units.map(unit => { 226 return this.createHarnessTestFile(unit, rootDir); 227 }); 228 } 229 230 if (tsConfigOptions && tsConfigOptions.configFilePath !== undefined) { 231 tsConfigOptions.configFilePath = ts.combinePaths(rootDir, tsConfigOptions.configFilePath); 232 tsConfigOptions.configFile!.fileName = tsConfigOptions.configFilePath; 233 } 234 235 this.result = Compiler.compileFiles( 236 this.toBeCompiled, 237 this.otherFiles, 238 this.harnessSettings, 239 /*options*/ tsConfigOptions, 240 /*currentDirectory*/ this.harnessSettings.currentDirectory, 241 testCaseContent.symlinks 242 ); 243 244 this.options = this.result.options; 245 } 246 247 public static getConfigurations(file: string): CompilerFileBasedTest { 248 // also see `parseCompilerTestConfigurations` in tests/webTestServer.ts 249 const content = IO.readFile(file)!; 250 const settings = TestCaseParser.extractCompilerSettings(content); 251 const configurations = getFileBasedTestConfigurations(settings, CompilerTest.varyBy); 252 return { file, configurations, content }; 253 } 254 255 public verifyDiagnostics() { 256 // check errors 257 Compiler.doErrorBaseline( 258 this.configuredName, 259 this.tsConfigFiles.concat(this.toBeCompiled, this.otherFiles), 260 this.result.diagnostics, 261 !!this.options.pretty); 262 } 263 264 public verifyModuleResolution() { 265 if (this.options.traceResolution) { 266 Baseline.runBaseline(this.configuredName.replace(/\.tsx?$/, ".trace.json"), 267 JSON.stringify(this.result.traces.map(Utils.sanitizeTraceResolutionLogEntry), undefined, 4)); 268 } 269 } 270 271 public verifySourceMapRecord() { 272 if (this.options.sourceMap || this.options.inlineSourceMap || this.options.declarationMap) { 273 const record = Utils.removeTestPathPrefixes(this.result.getSourceMapRecord()!); 274 const baseline = (this.options.noEmitOnError && this.result.diagnostics.length !== 0) || record === undefined 275 // Because of the noEmitOnError option no files are created. We need to return null because baselining isn't required. 276 ? null // eslint-disable-line no-null/no-null 277 : record; 278 Baseline.runBaseline(this.configuredName.replace(/\.tsx?$/, ".sourcemap.txt"), baseline); 279 } 280 } 281 282 public verifyJavaScriptOutput() { 283 if (this.hasNonDtsFiles) { 284 Compiler.doJsEmitBaseline( 285 this.configuredName, 286 this.fileName, 287 this.options, 288 this.result, 289 this.tsConfigFiles, 290 this.toBeCompiled, 291 this.otherFiles, 292 this.harnessSettings); 293 } 294 } 295 296 public verifySourceMapOutput() { 297 Compiler.doSourcemapBaseline( 298 this.configuredName, 299 this.options, 300 this.result, 301 this.harnessSettings); 302 } 303 304 public verifyTypesAndSymbols() { 305 if (this.fileName.indexOf("APISample") >= 0) { 306 return; 307 } 308 309 const noTypesAndSymbols = 310 this.harnessSettings.noTypesAndSymbols && 311 this.harnessSettings.noTypesAndSymbols.toLowerCase() === "true"; 312 if (noTypesAndSymbols) { 313 return; 314 } 315 316 Compiler.doTypeAndSymbolBaseline( 317 this.configuredName, 318 this.result.program!, 319 this.toBeCompiled.concat(this.otherFiles).filter(file => !!this.result.program!.getSourceFile(file.unitName)), 320 /*opts*/ undefined, 321 /*multifile*/ undefined, 322 /*skipTypeBaselines*/ undefined, 323 /*skipSymbolBaselines*/ undefined, 324 !!ts.length(this.result.diagnostics) 325 ); 326 } 327 328 private makeUnitName(name: string, root: string) { 329 const path = ts.toPath(name, root, ts.identity); 330 const pathStart = ts.toPath(IO.getCurrentDirectory(), "", ts.identity); 331 return pathStart ? path.replace(pathStart, "/") : path; 332 } 333 334 private createHarnessTestFile(lastUnit: TestCaseParser.TestUnitData, rootDir: string, unitName?: string): Compiler.TestFile { 335 return { unitName: unitName || this.makeUnitName(lastUnit.name, rootDir), content: lastUnit.content, fileOptions: lastUnit.fileOptions }; 336 } 337 } 338}