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+
312abstract 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
5454class 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
6171class 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 ( / ( .+ \. t s ) \( ( \d + ) , \d + \) : e r r o r T S / ) ;
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