Skip to content

Commit 9d56f7b

Browse files
authored
Merge pull request microsoft#19887 from Microsoft/dt-runner-excludes-ExpectError
DefinitelyTypedRunner skips ExpectErrors
2 parents 5ffcc42 + 5fff717 commit 9d56f7b

File tree

1 file changed

+93
-18
lines changed

1 file changed

+93
-18
lines changed

src/harness/externalCompileRunner.ts

+93-18
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
11
/// <reference path="harness.ts"/>
22
/// <reference path="runnerbase.ts" />
3+
const fs = require("fs");
4+
const path = require("path");
5+
6+
interface ExecResult {
7+
stdout: Buffer;
8+
stderr: Buffer;
9+
status: number;
10+
}
11+
312
abstract class ExternalCompileRunnerBase extends RunnerBase {
413
abstract testDir: string;
5-
public enumerateTestFiles() {
14+
abstract report(result: ExecResult, cwd: string): string;
15+
enumerateTestFiles() {
616
return Harness.IO.getDirectories(this.testDir);
717
}
818
/** Setup the runner's tests so that they are ready to be executed by the harness
919
* The first test should be a describe/it block that sets up the harness's compiler instance appropriately
1020
*/
11-
public initializeTests(): void {
21+
initializeTests(): void {
1222
// Read in and evaluate the test list
1323
const testList = this.tests && this.tests.length ? this.tests : this.enumerateTestFiles();
1424

@@ -21,8 +31,6 @@ abstract class ExternalCompileRunnerBase extends RunnerBase {
2131
private runTest(directoryName: string) {
2232
describe(directoryName, () => {
2333
const cp = require("child_process");
24-
const path = require("path");
25-
const fs = require("fs");
2634

2735
it("should build successfully", () => {
2836
const cwd = path.join(__dirname, "../../", this.testDir, directoryName);
@@ -36,32 +44,99 @@ abstract class ExternalCompileRunnerBase extends RunnerBase {
3644
if (install.status !== 0) throw new Error(`NPM Install for ${directoryName} failed!`);
3745
}
3846
Harness.Baseline.runBaseline(`${this.kind()}/${directoryName}.log`, () => {
39-
const result = cp.spawnSync(`node`, [path.join(__dirname, "tsc.js")], { cwd, timeout, shell: true });
40-
// tslint:disable-next-line:no-null-keyword
41-
return result.status === 0 && !result.stdout.length && !result.stderr.length ? null : `Exit Code: ${result.status}
42-
Standard output:
43-
${result.stdout.toString().replace(/\r\n/g, "\n")}
44-
45-
46-
Standard error:
47-
${result.stderr.toString().replace(/\r\n/g, "\n")}`;
47+
return this.report(cp.spawnSync(`node`, [path.join(__dirname, "tsc.js")], { cwd, timeout, shell: true }), cwd);
4848
});
4949
});
5050
});
5151
}
5252
}
5353

5454
class UserCodeRunner extends ExternalCompileRunnerBase {
55-
public readonly testDir = "tests/cases/user/";
56-
public kind(): TestRunnerKind {
55+
readonly testDir = "tests/cases/user/";
56+
kind(): TestRunnerKind {
5757
return "user";
5858
}
59+
report(result: ExecResult) {
60+
// tslint:disable-next-line:no-null-keyword
61+
return result.status === 0 && !result.stdout.length && !result.stderr.length ? null : `Exit Code: ${result.status}
62+
Standard output:
63+
${result.stdout.toString().replace(/\r\n/g, "\n")}
64+
65+
66+
Standard error:
67+
${result.stderr.toString().replace(/\r\n/g, "\n")}`;
68+
}
5969
}
6070

6171
class DefinitelyTypedRunner extends ExternalCompileRunnerBase {
62-
public readonly testDir = "../DefinitelyTyped/types/";
63-
public workingDirectory = this.testDir;
64-
public kind(): TestRunnerKind {
72+
readonly testDir = "../DefinitelyTyped/types/";
73+
workingDirectory = this.testDir;
74+
kind(): TestRunnerKind {
6575
return "dt";
6676
}
77+
report(result: ExecResult, cwd: string) {
78+
const stdout = removeExpectedErrors(result.stdout.toString(), cwd);
79+
const stderr = result.stderr.toString();
80+
// tslint:disable-next-line:no-null-keyword
81+
return !stdout.length && !stderr.length ? null : `Exit Code: ${result.status}
82+
Standard output:
83+
${stdout.replace(/\r\n/g, "\n")}
84+
85+
86+
Standard error:
87+
${stderr.replace(/\r\n/g, "\n")}`;
88+
}
89+
}
90+
91+
function removeExpectedErrors(errors: string, cwd: string): string {
92+
return ts.flatten(splitBy(errors.split("\n"), s => /^\S+/.test(s)).filter(isUnexpectedError(cwd))).join("\n");
93+
}
94+
/**
95+
* Returns true if the line that caused the error contains '$ExpectError',
96+
* or if the line before that one contains '$ExpectError'.
97+
* '$ExpectError' is a marker used in Definitely Typed tests,
98+
* meaning that the error should not contribute toward our error baslines.
99+
*/
100+
function isUnexpectedError(cwd: string) {
101+
return (error: string[]) => {
102+
ts.Debug.assertGreaterThanOrEqual(error.length, 1);
103+
const match = error[0].match(/(.+\.ts)\((\d+),\d+\): error TS/);
104+
if (!match) {
105+
return true;
106+
}
107+
const [, errorFile, lineNumberString] = match;
108+
const lines = fs.readFileSync(path.join(cwd, errorFile), { encoding: "utf8" }).split("\n");
109+
const lineNumber = parseInt(lineNumberString);
110+
ts.Debug.assertGreaterThanOrEqual(lineNumber, 0);
111+
ts.Debug.assertLessThan(lineNumber, lines.length);
112+
const previousLine = lineNumber - 1 > 0 ? lines[lineNumber - 1] : "";
113+
return !ts.stringContains(lines[lineNumber], "$ExpectError") && !ts.stringContains(previousLine, "$ExpectError");
114+
};
115+
}
116+
/**
117+
* Split an array into multiple arrays whenever `isStart` returns true.
118+
* @example
119+
* splitBy([1,2,3,4,5,6], isOdd)
120+
* ==> [[1, 2], [3, 4], [5, 6]]
121+
* where
122+
* const isOdd = n => !!(n % 2)
123+
*/
124+
function splitBy<T>(xs: T[], isStart: (x: T) => boolean): T[][] {
125+
const result = [];
126+
let group: T[] = [];
127+
for (const x of xs) {
128+
if (isStart(x)) {
129+
if (group.length) {
130+
result.push(group);
131+
}
132+
group = [x];
133+
}
134+
else {
135+
group.push(x);
136+
}
137+
}
138+
if (group.length) {
139+
result.push(group);
140+
}
141+
return result;
67142
}

0 commit comments

Comments
 (0)