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