• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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