Skip to content

Commit 5353475

Browse files
authored
Always collect type and symbol baselines (microsoft#18621)
* Always generate type & symbol baselines * Accept changed shadowed baselines * Accept brand new type and symbol baselines * Allow `getTypeAtLocation` to return undefined in the type writer * Accept baselines which had missing type information * Bind container for dynamically names enum members so they may be printed * Accept type/symbol baselines for enums with computed members * First pass at reducing typeWriter memory overhead * Use generators to allow for type and symbol baselines with no cache * Accept new baselines for tests whose output was fixed by better newline splitting * Hard cap on number of declarations printed, cache declaration print text * handle differing newlines better still to handle RWC newlines * Lower abridging count, accept abridged baselines * Limit max RWC error output size, limit RWC type and symbol baseline input size * Move skip logic into type and symbol baseliner to streamline error handling * Accept removal of empty baselines * Canonicalize path earlier to handle odd paths in input files * Do canonicalization earlier still, also ensure parallel perf profiles for different targets do not trample one another * No need to pathify again
1 parent 92b7dcf commit 5353475

File tree

8,086 files changed

+672786
-2163
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

8,086 files changed

+672786
-2163
lines changed

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,4 @@ internal/
5959
.idea
6060
yarn.lock
6161
package-lock.json
62-
.parallelperf.json
62+
.parallelperf.*

src/compiler/binder.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -1456,11 +1456,6 @@ namespace ts {
14561456
}
14571457

14581458
function declareSymbolAndAddToSymbolTable(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags): Symbol {
1459-
// Just call this directly so that the return type of this function stays "void".
1460-
return declareSymbolAndAddToSymbolTableWorker(node, symbolFlags, symbolExcludes);
1461-
}
1462-
1463-
function declareSymbolAndAddToSymbolTableWorker(node: Declaration, symbolFlags: SymbolFlags, symbolExcludes: SymbolFlags): Symbol {
14641459
switch (container.kind) {
14651460
// Modules, source files, and classes need specialized handling for how their
14661461
// members are declared (for example, a member of a class will go into a specific
@@ -1683,6 +1678,9 @@ namespace ts {
16831678

16841679
function bindAnonymousDeclaration(node: Declaration, symbolFlags: SymbolFlags, name: __String) {
16851680
const symbol = createSymbol(symbolFlags, name);
1681+
if (symbolFlags & SymbolFlags.EnumMember) {
1682+
symbol.parent = container.symbol;
1683+
}
16861684
addDeclarationToSymbol(symbol, node, symbolFlags);
16871685
}
16881686

src/harness/compilerRunner.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ class CompilerBaselineRunner extends RunnerBase {
177177
return;
178178
}
179179

180-
Harness.Compiler.doTypeAndSymbolBaseline(justName, result, toBeCompiled.concat(otherFiles).filter(file => !!result.program.getSourceFile(file.unitName)));
180+
Harness.Compiler.doTypeAndSymbolBaseline(justName, result.program, toBeCompiled.concat(otherFiles).filter(file => !!result.program.getSourceFile(file.unitName)));
181181
});
182182
});
183183
}

src/harness/harness.ts

+52-62
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ namespace Utils {
148148
});
149149
}
150150

151+
export const canonicalizeForHarness = ts.createGetCanonicalFileName(/*caseSensitive*/ false); // This is done so tests work on windows _and_ linux
152+
151153
export function assertInvariants(node: ts.Node, parent: ts.Node): void {
152154
if (node) {
153155
assert.isFalse(node.pos < 0, "node.pos < 0");
@@ -1446,10 +1448,7 @@ namespace Harness {
14461448
});
14471449
}
14481450

1449-
export function doTypeAndSymbolBaseline(baselinePath: string, result: CompilerResult, allFiles: {unitName: string, content: string}[], opts?: Harness.Baseline.BaselineOptions, multifile?: boolean) {
1450-
if (result.errors.length !== 0) {
1451-
return;
1452-
}
1451+
export function doTypeAndSymbolBaseline(baselinePath: string, program: ts.Program, allFiles: {unitName: string, content: string}[], opts?: Harness.Baseline.BaselineOptions, multifile?: boolean, skipTypeAndSymbolbaselines?: boolean) {
14531452
// The full walker simulates the types that you would get from doing a full
14541453
// compile. The pull walker simulates the types you get when you just do
14551454
// a type query for a random node (like how the LS would do it). Most of the
@@ -1465,16 +1464,8 @@ namespace Harness {
14651464
// These types are equivalent, but depend on what order the compiler observed
14661465
// certain parts of the program.
14671466

1468-
const program = result.program;
1469-
14701467
const fullWalker = new TypeWriterWalker(program, /*fullTypeCheck*/ true);
14711468

1472-
const fullResults = ts.createMap<TypeWriterResult[]>();
1473-
1474-
for (const sourceFile of allFiles) {
1475-
fullResults.set(sourceFile.unitName, fullWalker.getTypeAndSymbols(sourceFile.unitName));
1476-
}
1477-
14781469
// Produce baselines. The first gives the types for all expressions.
14791470
// The second gives symbols for all identifiers.
14801471
let typesError: Error, symbolsError: Error;
@@ -1515,76 +1506,77 @@ namespace Harness {
15151506
baselinePath.replace(/\.tsx?/, "") : baselinePath;
15161507

15171508
if (!multifile) {
1518-
const fullBaseLine = generateBaseLine(fullResults, isSymbolBaseLine);
1509+
const fullBaseLine = generateBaseLine(isSymbolBaseLine, skipTypeAndSymbolbaselines);
15191510
Harness.Baseline.runBaseline(outputFileName + fullExtension, () => fullBaseLine, opts);
15201511
}
15211512
else {
15221513
Harness.Baseline.runMultifileBaseline(outputFileName, fullExtension, () => {
1523-
return iterateBaseLine(fullResults, isSymbolBaseLine);
1514+
return iterateBaseLine(isSymbolBaseLine, skipTypeAndSymbolbaselines);
15241515
}, opts);
15251516
}
15261517
}
15271518

1528-
function generateBaseLine(typeWriterResults: ts.Map<TypeWriterResult[]>, isSymbolBaseline: boolean): string {
1519+
function generateBaseLine(isSymbolBaseline: boolean, skipTypeAndSymbolbaselines?: boolean): string {
15291520
let result = "";
1530-
const gen = iterateBaseLine(typeWriterResults, isSymbolBaseline);
1521+
const gen = iterateBaseLine(isSymbolBaseline, skipTypeAndSymbolbaselines);
15311522
for (let {done, value} = gen.next(); !done; { done, value } = gen.next()) {
15321523
const [, content] = value;
15331524
result += content;
15341525
}
1535-
return result;
1526+
/* tslint:disable:no-null-keyword */
1527+
return result || null;
1528+
/* tslint:enable:no-null-keyword */
15361529
}
15371530

1538-
function *iterateBaseLine(typeWriterResults: ts.Map<TypeWriterResult[]>, isSymbolBaseline: boolean): IterableIterator<[string, string]> {
1539-
let typeLines = "";
1540-
const typeMap: { [fileName: string]: { [lineNum: number]: string[]; } } = {};
1531+
function *iterateBaseLine(isSymbolBaseline: boolean, skipTypeAndSymbolbaselines?: boolean): IterableIterator<[string, string]> {
1532+
if (skipTypeAndSymbolbaselines) {
1533+
return;
1534+
}
15411535
const dupeCase = ts.createMap<number>();
15421536

15431537
for (const file of allFiles) {
1544-
const codeLines = file.content.split("\n");
1545-
const key = file.unitName;
1546-
typeWriterResults.get(file.unitName).forEach(result => {
1538+
const { unitName } = file;
1539+
let typeLines = "=== " + unitName + " ===\r\n";
1540+
const codeLines = ts.flatMap(file.content.split(/\r?\n/g), e => e.split(/[\r\u2028\u2029]/g));
1541+
const gen: IterableIterator<TypeWriterResult> = isSymbolBaseline ? fullWalker.getSymbols(unitName) : fullWalker.getTypes(unitName);
1542+
let lastIndexWritten: number | undefined;
1543+
for (let {done, value: result} = gen.next(); !done; { done, value: result } = gen.next()) {
15471544
if (isSymbolBaseline && !result.symbol) {
15481545
return;
15491546
}
1550-
1547+
if (lastIndexWritten === undefined) {
1548+
typeLines += codeLines.slice(0, result.line + 1).join("\r\n") + "\r\n";
1549+
}
1550+
else if (result.line !== lastIndexWritten) {
1551+
if (!((lastIndexWritten + 1 < codeLines.length) && (codeLines[lastIndexWritten + 1].match(/^\s*[{|}]\s*$/) || codeLines[lastIndexWritten + 1].trim() === ""))) {
1552+
typeLines += "\r\n";
1553+
}
1554+
typeLines += codeLines.slice(lastIndexWritten + 1, result.line + 1).join("\r\n") + "\r\n";
1555+
}
1556+
lastIndexWritten = result.line;
15511557
const typeOrSymbolString = isSymbolBaseline ? result.symbol : result.type;
15521558
const formattedLine = result.sourceText.replace(/\r?\n/g, "") + " : " + typeOrSymbolString;
1553-
if (!typeMap[key]) {
1554-
typeMap[key] = {};
1555-
}
1559+
typeLines += ">" + formattedLine + "\r\n";
1560+
}
15561561

1557-
let typeInfo = [formattedLine];
1558-
const existingTypeInfo = typeMap[key][result.line];
1559-
if (existingTypeInfo) {
1560-
typeInfo = existingTypeInfo.concat(typeInfo);
1562+
// Preserve legacy behavior
1563+
if (lastIndexWritten === undefined) {
1564+
for (let i = 0; i < codeLines.length; i++) {
1565+
const currentCodeLine = codeLines[i];
1566+
typeLines += currentCodeLine + "\r\n";
1567+
typeLines += "No type information for this code.";
15611568
}
1562-
typeMap[key][result.line] = typeInfo;
1563-
});
1564-
1565-
typeLines += "=== " + file.unitName + " ===\r\n";
1566-
for (let i = 0; i < codeLines.length; i++) {
1567-
const currentCodeLine = codeLines[i];
1568-
typeLines += currentCodeLine + "\r\n";
1569-
if (typeMap[key]) {
1570-
const typeInfo = typeMap[key][i];
1571-
if (typeInfo) {
1572-
typeInfo.forEach(ty => {
1573-
typeLines += ">" + ty + "\r\n";
1574-
});
1575-
if (i + 1 < codeLines.length && (codeLines[i + 1].match(/^\s*[{|}]\s*$/) || codeLines[i + 1].trim() === "")) {
1576-
}
1577-
else {
1578-
typeLines += "\r\n";
1579-
}
1569+
}
1570+
else {
1571+
if (lastIndexWritten + 1 < codeLines.length) {
1572+
if (!((lastIndexWritten + 1 < codeLines.length) && (codeLines[lastIndexWritten + 1].match(/^\s*[{|}]\s*$/) || codeLines[lastIndexWritten + 1].trim() === ""))) {
1573+
typeLines += "\r\n";
15801574
}
1575+
typeLines += codeLines.slice(lastIndexWritten + 1).join("\r\n");
15811576
}
1582-
else {
1583-
typeLines += "No type information for this code.";
1584-
}
1577+
typeLines += "\r\n";
15851578
}
1586-
yield [checkDuplicatedFileName(file.unitName, dupeCase), typeLines];
1587-
typeLines = "";
1579+
yield [checkDuplicatedFileName(unitName, dupeCase), typeLines];
15881580
}
15891581
}
15901582
}
@@ -1726,7 +1718,7 @@ namespace Harness {
17261718
}
17271719

17281720
function sanitizeTestFilePath(name: string) {
1729-
return ts.normalizeSlashes(name.replace(/[\^<>:"|?*%]/g, "_")).replace(/\.\.\//g, "__dotdot/").toLowerCase();
1721+
return ts.toPath(ts.normalizeSlashes(name.replace(/[\^<>:"|?*%]/g, "_")).replace(/\.\.\//g, "__dotdot/"), "", Utils.canonicalizeForHarness);
17301722
}
17311723

17321724
// This does not need to exist strictly speaking, but many tests will need to be updated if it's removed
@@ -2070,7 +2062,6 @@ namespace Harness {
20702062
export function runMultifileBaseline(relativeFileBase: string, extension: string, generateContent: () => IterableIterator<[string, string, number]> | IterableIterator<[string, string]>, opts?: BaselineOptions, referencedExtensions?: string[]): void {
20712063
const gen = generateContent();
20722064
const writtenFiles = ts.createMap<true>();
2073-
const canonicalize = ts.createGetCanonicalFileName(/*caseSensitive*/ false); // This is done so tests work on windows _and_ linux
20742065
/* tslint:disable-next-line:no-null-keyword */
20752066
const errors: Error[] = [];
20762067
if (gen !== null) {
@@ -2086,8 +2077,7 @@ namespace Harness {
20862077
catch (e) {
20872078
errors.push(e);
20882079
}
2089-
const path = ts.toPath(relativeFileName, "", canonicalize);
2090-
writtenFiles.set(path, true);
2080+
writtenFiles.set(relativeFileName, true);
20912081
}
20922082
}
20932083

@@ -2100,8 +2090,7 @@ namespace Harness {
21002090
const missing: string[] = [];
21012091
for (const name of existing) {
21022092
const localCopy = name.substring(referenceDir.length - relativeFileBase.length);
2103-
const path = ts.toPath(localCopy, "", canonicalize);
2104-
if (!writtenFiles.has(path)) {
2093+
if (!writtenFiles.has(localCopy)) {
21052094
missing.push(localCopy);
21062095
}
21072096
}
@@ -2114,13 +2103,14 @@ namespace Harness {
21142103
if (errors.length || missing.length) {
21152104
let errorMsg = "";
21162105
if (errors.length) {
2117-
errorMsg += `The baseline for ${relativeFileBase} has changed:${"\n " + errors.map(e => e.message).join("\n ")}`;
2106+
errorMsg += `The baseline for ${relativeFileBase} in ${errors.length} files has changed:${"\n " + errors.slice(0, 5).map(e => e.message).join("\n ") + (errors.length > 5 ? "\n" + ` and ${errors.length - 5} more` : "")}`;
21182107
}
21192108
if (errors.length && missing.length) {
21202109
errorMsg += "\n";
21212110
}
21222111
if (missing.length) {
2123-
errorMsg += `Baseline missing files:${"\n " + missing.join("\n ") + "\n"}Written:${"\n " + ts.arrayFrom(writtenFiles.keys()).join("\n ")}`;
2112+
const writtenFilesArray = ts.arrayFrom(writtenFiles.keys());
2113+
errorMsg += `Baseline missing ${missing.length} files:${"\n " + missing.slice(0, 5).join("\n ") + (missing.length > 5 ? "\n" + ` and ${missing.length - 5} more` : "") + "\n"}Written ${writtenFiles.size} files:${"\n " + writtenFilesArray.slice(0, 5).join("\n ") + (writtenFilesArray.length > 5 ? "\n" + ` and ${writtenFilesArray.length - 5} more` : "")}`;
21242114
}
21252115
throw new Error(errorMsg);
21262116
}

src/harness/parallel/host.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@ namespace Harness.Parallel.Host {
2626
text?: string;
2727
}
2828

29-
const perfdataFileName = ".parallelperf.json";
30-
function readSavedPerfData(): {[testHash: string]: number} {
31-
const perfDataContents = Harness.IO.readFile(perfdataFileName);
29+
const perfdataFileNameFragment = ".parallelperf";
30+
function perfdataFileName(target?: string) {
31+
return `${perfdataFileNameFragment}${target ? `.${target}` : ""}.json`;
32+
}
33+
function readSavedPerfData(target?: string): {[testHash: string]: number} {
34+
const perfDataContents = Harness.IO.readFile(perfdataFileName(target));
3235
if (perfDataContents) {
3336
return JSON.parse(perfDataContents);
3437
}
@@ -46,7 +49,7 @@ namespace Harness.Parallel.Host {
4649
const { statSync }: { statSync(path: string): { size: number }; } = require("fs");
4750
let tasks: { runner: TestRunnerKind, file: string, size: number }[] = [];
4851
const newTasks: { runner: TestRunnerKind, file: string, size: number }[] = [];
49-
const perfData = readSavedPerfData();
52+
const perfData = readSavedPerfData(configOption);
5053
let totalCost = 0;
5154
let unknownValue: string | undefined;
5255
for (const runner of runners) {
@@ -290,7 +293,7 @@ namespace Harness.Parallel.Host {
290293
reporter.epilogue();
291294
}
292295

293-
Harness.IO.writeFile(perfdataFileName, JSON.stringify(newPerfData, null, 4)); // tslint:disable-line:no-null-keyword
296+
Harness.IO.writeFile(perfdataFileName(configOption), JSON.stringify(newPerfData, null, 4)); // tslint:disable-line:no-null-keyword
294297

295298
process.exit(errorResults.length);
296299
}

src/harness/runner.ts

+8
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ interface TaskSet {
101101
files: string[];
102102
}
103103

104+
let configOption: string;
104105
function handleTestConfig() {
105106
if (testConfigContent !== "") {
106107
const testConfig = <TestConfig>JSON.parse(testConfigContent);
@@ -136,6 +137,13 @@ function handleTestConfig() {
136137
continue;
137138
}
138139

140+
if (!configOption) {
141+
configOption = option;
142+
}
143+
else {
144+
configOption += "+" + option;
145+
}
146+
139147
switch (option) {
140148
case "compiler":
141149
runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance));

src/harness/rwcRunner.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ namespace RWC {
3939
const baseName = /(.*)\/(.*).json/.exec(ts.normalizeSlashes(jsonPath))[2];
4040
let currentDirectory: string;
4141
let useCustomLibraryFile: boolean;
42+
let skipTypeAndSymbolbaselines = false;
43+
const typeAndSymbolSizeLimit = 10000000;
4244
after(() => {
4345
// Mocha holds onto the closure environment of the describe callback even after the test is done.
4446
// Therefore we have to clean out large objects after the test is done.
@@ -52,6 +54,7 @@ namespace RWC {
5254
// or to use lib.d.ts inside the json object. If the flag is true, use the lib.d.ts inside json file
5355
// otherwise use the lib.d.ts from built/local
5456
useCustomLibraryFile = undefined;
57+
skipTypeAndSymbolbaselines = false;
5558
});
5659

5760
it("can compile", function(this: Mocha.ITestCallbackContext) {
@@ -61,6 +64,7 @@ namespace RWC {
6164
const ioLog: IOLog = JSON.parse(Harness.IO.readFile(jsonPath));
6265
currentDirectory = ioLog.currentDirectory;
6366
useCustomLibraryFile = ioLog.useCustomLibraryFile;
67+
skipTypeAndSymbolbaselines = ioLog.filesRead.reduce((acc, elem) => (elem && elem.result && elem.result.contents) ? acc + elem.result.contents.length : acc, 0) > typeAndSymbolSizeLimit;
6468
runWithIOLog(ioLog, () => {
6569
opts = ts.parseCommandLine(ioLog.arguments, fileName => Harness.IO.readFile(fileName));
6670
assert.equal(opts.errors.length, 0);
@@ -217,10 +221,10 @@ namespace RWC {
217221

218222
it("has the expected types", () => {
219223
// We don't need to pass the extension here because "doTypeAndSymbolBaseline" will append appropriate extension of ".types" or ".symbols"
220-
Harness.Compiler.doTypeAndSymbolBaseline(baseName, compilerResult, inputFiles
224+
Harness.Compiler.doTypeAndSymbolBaseline(baseName, compilerResult.program, inputFiles
221225
.concat(otherFiles)
222226
.filter(file => !!compilerResult.program.getSourceFile(file.unitName))
223-
.filter(e => !Harness.isDefaultLibraryFile(e.unitName)), baselineOpts, /*multifile*/ true);
227+
.filter(e => !Harness.isDefaultLibraryFile(e.unitName)), baselineOpts, /*multifile*/ true, skipTypeAndSymbolbaselines);
224228
});
225229

226230
// Ideally, a generated declaration file will have no errors. But we allow generated

0 commit comments

Comments
 (0)