1namespace ts { 2 export type TscCompileSystem = fakes.System & { 3 writtenFiles: Set<Path>; 4 baseLine(): { file: string; text: string; }; 5 disableUseFileVersionAsSignature?: boolean; 6 storeFilesChangingSignatureDuringEmit?: boolean; 7 }; 8 9 export const noChangeRun: TestTscEdit = { 10 subScenario: "no-change-run", 11 modifyFs: noop 12 }; 13 export const noChangeWithExportsDiscrepancyRun: TestTscEdit = { 14 ...noChangeRun, 15 discrepancyExplanation: () => [ 16 "Incremental build did not emit and has .ts as signature so exports has all imported modules/referenced files", 17 "Clean build always uses d.ts for signature for testing thus does not contain non exported modules/referenced files that arent needed" 18 ] 19 }; 20 export const noChangeOnlyRuns = [noChangeRun]; 21 export const noChangeWithExportsDiscrepancyOnlyRuns = [noChangeWithExportsDiscrepancyRun]; 22 23 export interface TestTscCompile extends TestTscCompileLikeBase { 24 baselineSourceMap?: boolean; 25 baselineReadFileCalls?: boolean; 26 baselinePrograms?: boolean; 27 baselineDependencies?: boolean; 28 } 29 30 export type CommandLineProgram = [Program, BuilderProgram?]; 31 export interface CommandLineCallbacks { 32 cb: ExecuteCommandLineCallbacks; 33 getPrograms: () => readonly CommandLineProgram[]; 34 } 35 36 function isAnyProgram(program: Program | BuilderProgram | ParsedCommandLine): program is Program | BuilderProgram { 37 return !!(program as Program | BuilderProgram).getCompilerOptions; 38 } 39 export function commandLineCallbacks( 40 sys: TscCompileSystem | tscWatch.WatchedSystem, 41 originalReadCall?: System["readFile"], 42 ): CommandLineCallbacks { 43 let programs: CommandLineProgram[] | undefined; 44 45 return { 46 cb: program => { 47 if (isAnyProgram(program)) { 48 baselineBuildInfo(program.getCompilerOptions(), sys, originalReadCall); 49 (programs || (programs = [])).push(isBuilderProgram(program) ? 50 [program.getProgram(), program] : 51 [program] 52 ); 53 } 54 else { 55 baselineBuildInfo(program.options, sys, originalReadCall); 56 } 57 }, 58 getPrograms: () => { 59 const result = programs || emptyArray; 60 programs = undefined; 61 return result; 62 } 63 }; 64 } 65 export interface TestTscCompileLikeBase extends VerifyTscCompileLike { 66 diffWithInitial?: boolean; 67 modifyFs?: (fs: vfs.FileSystem) => void; 68 disableUseFileVersionAsSignature?: boolean; 69 environmentVariables?: Record<string, string>; 70 } 71 72 export interface TestTscCompileLike extends TestTscCompileLikeBase { 73 compile: (sys: TscCompileSystem) => void; 74 additionalBaseline?: (sys: TscCompileSystem) => void; 75 } 76 /** 77 * Initialize FS, run compile function and save baseline 78 */ 79 export function testTscCompileLike(input: TestTscCompileLike) { 80 const initialFs = input.fs(); 81 const inputFs = initialFs.shadow(); 82 const { 83 scenario, subScenario, diffWithInitial, 84 commandLineArgs, modifyFs, 85 environmentVariables, 86 compile: worker, additionalBaseline, 87 } = input; 88 if (modifyFs) modifyFs(inputFs); 89 inputFs.makeReadonly(); 90 const fs = inputFs.shadow(); 91 92 // Create system 93 const sys = new fakes.System(fs, { executingFilePath: "/lib/tsc", env: environmentVariables }) as TscCompileSystem; 94 if (input.disableUseFileVersionAsSignature) sys.disableUseFileVersionAsSignature = true; 95 sys.storeFilesChangingSignatureDuringEmit = true; 96 sys.write(`${sys.getExecutingFilePath()} ${commandLineArgs.join(" ")}\n`); 97 sys.exit = exitCode => sys.exitCode = exitCode; 98 worker(sys); 99 sys.write(`exitCode:: ExitStatus.${ExitStatus[sys.exitCode as ExitStatus]}\n`); 100 additionalBaseline?.(sys); 101 fs.makeReadonly(); 102 sys.baseLine = () => { 103 const baseFsPatch = diffWithInitial ? 104 inputFs.diff(initialFs, { includeChangedFileWithSameContent: true }) : 105 inputFs.diff(/*base*/ undefined, { baseIsNotShadowRoot: true }); 106 const patch = fs.diff(inputFs, { includeChangedFileWithSameContent: true }); 107 return { 108 file: `${isBuild(commandLineArgs) ? "tsbuild" : "tsc"}/${scenario}/${subScenario.split(" ").join("-")}.js`, 109 text: `Input:: 110${baseFsPatch ? vfs.formatPatch(baseFsPatch) : ""} 111 112Output:: 113${sys.output.join("")} 114 115${patch ? vfs.formatPatch(patch) : ""}` 116 }; 117 }; 118 return sys; 119 } 120 121 function makeSystemReadyForBaseline(sys: TscCompileSystem, versionToWrite?: string) { 122 if (versionToWrite) { 123 fakes.patchHostForBuildInfoWrite(sys, versionToWrite); 124 } 125 else { 126 fakes.patchHostForBuildInfoReadWrite(sys); 127 } 128 const writtenFiles = sys.writtenFiles = new Set(); 129 const originalWriteFile = sys.writeFile; 130 sys.writeFile = (fileName, content, writeByteOrderMark) => { 131 const path = toPathWithSystem(sys, fileName); 132 // When buildinfo is same for two projects, 133 // it gives error and doesnt write buildinfo but because buildInfo is written for one project, 134 // readable baseline will be written two times for those two projects with same contents and is ok 135 Debug.assert(!writtenFiles.has(path) || endsWith(path, "baseline.txt")); 136 writtenFiles.add(path); 137 return originalWriteFile.call(sys, fileName, content, writeByteOrderMark); 138 }; 139 } 140 141 export function createSolutionBuilderHostForBaseline( 142 sys: TscCompileSystem | tscWatch.WatchedSystem, 143 versionToWrite?: string, 144 originalRead?: (TscCompileSystem | tscWatch.WatchedSystem)["readFile"] 145 ) { 146 if (sys instanceof fakes.System) makeSystemReadyForBaseline(sys, versionToWrite); 147 const { cb } = commandLineCallbacks(sys, originalRead); 148 const host = createSolutionBuilderHost(sys, 149 /*createProgram*/ undefined, 150 createDiagnosticReporter(sys, /*pretty*/ true), 151 createBuilderStatusReporter(sys, /*pretty*/ true), 152 ); 153 host.afterProgramEmitAndDiagnostics = cb; 154 host.afterEmitBundle = cb; 155 return host; 156 } 157 158 /** 159 * Initialize Fs, execute command line and save baseline 160 */ 161 export function testTscCompile(input: TestTscCompile) { 162 let actualReadFileMap: MapLike<number> | undefined; 163 let getPrograms: CommandLineCallbacks["getPrograms"] | undefined; 164 return testTscCompileLike({ 165 ...input, 166 compile: commandLineCompile, 167 additionalBaseline 168 }); 169 170 function commandLineCompile(sys: TscCompileSystem) { 171 makeSystemReadyForBaseline(sys); 172 actualReadFileMap = {}; 173 const originalReadFile = sys.readFile; 174 sys.readFile = path => { 175 // Dont record libs 176 if (path.startsWith("/src/")) { 177 actualReadFileMap![path] = (getProperty(actualReadFileMap!, path) || 0) + 1; 178 } 179 return originalReadFile.call(sys, path); 180 }; 181 182 const result = commandLineCallbacks(sys, originalReadFile); 183 executeCommandLine( 184 sys, 185 result.cb, 186 input.commandLineArgs, 187 ); 188 sys.readFile = originalReadFile; 189 getPrograms = result.getPrograms; 190 } 191 192 function additionalBaseline(sys: TscCompileSystem) { 193 const { baselineSourceMap, baselineReadFileCalls, baselinePrograms, baselineDependencies } = input; 194 if (baselinePrograms) { 195 const baseline: string[] = []; 196 tscWatch.baselinePrograms(baseline, getPrograms!, emptyArray, baselineDependencies); 197 sys.write(baseline.join("\n")); 198 } 199 if (baselineReadFileCalls) { 200 sys.write(`readFiles:: ${JSON.stringify(actualReadFileMap, /*replacer*/ undefined, " ")} `); 201 } 202 if (baselineSourceMap) generateSourceMapBaselineFiles(sys); 203 actualReadFileMap = undefined; 204 getPrograms = undefined; 205 } 206 } 207 208 export function verifyTscBaseline(sys: () => { baseLine: TscCompileSystem["baseLine"]; }) { 209 it(`Generates files matching the baseline`, () => { 210 const { file, text } = sys().baseLine(); 211 Harness.Baseline.runBaseline(file, text); 212 }); 213 } 214 export interface VerifyTscCompileLike { 215 scenario: string; 216 subScenario: string; 217 commandLineArgs: readonly string[]; 218 fs: () => vfs.FileSystem; 219 } 220 221 /** 222 * Verify by baselining after initializing FS and custom compile 223 */ 224 export function verifyTscCompileLike<T extends VerifyTscCompileLike>(verifier: (input: T) => { baseLine: TscCompileSystem["baseLine"]; }, input: T) { 225 describe(`tsc ${input.commandLineArgs.join(" ")} ${input.scenario}:: ${input.subScenario}`, () => { 226 describe(input.scenario, () => { 227 describe(input.subScenario, () => { 228 verifyTscBaseline(() => verifier({ 229 ...input, 230 fs: () => input.fs().makeReadonly() 231 })); 232 }); 233 }); 234 }); 235 } 236 237 /** 238 * Verify by baselining after initializing FS and command line compile 239 */ 240 export function verifyTsc(input: TestTscCompile) { 241 verifyTscCompileLike(testTscCompile, input); 242 } 243} 244