13
13
//===----------------------------------------------------------------------===//
14
14
15
15
import * as vscode from "vscode" ;
16
- import * as readline from "readline" ;
17
- import { Readable } from "stream" ;
18
- import {
19
- INamedPipeReader ,
20
- UnixNamedPipeReader ,
21
- WindowsNamedPipeReader ,
22
- } from "./TestEventStreamReader" ;
23
16
import { ITestRunState } from "./TestRunState" ;
24
17
import { TestClass } from "../TestDiscovery" ;
25
- import { regexEscapedString , sourceLocationToVSCodeLocation } from "../../utilities/utilities" ;
26
- import { exec } from "child_process" ;
18
+ import { sourceLocationToVSCodeLocation } from "../../utilities/utilities" ;
19
+ import { StringColor } from "../../utilities/ansi" ;
20
+ import { ITestOutputParser } from "./XCTestOutputParser" ;
27
21
28
22
// All events produced by a swift-testing run will be one of these three types.
29
23
// Detailed information about swift-testing's JSON schema is available here:
@@ -162,109 +156,57 @@ export interface SourceLocation {
162
156
column : number ;
163
157
}
164
158
165
- export class SwiftTestingOutputParser {
159
+ export class SwiftTestingOutputParser implements ITestOutputParser {
160
+ public logs : string [ ] = [ ] ;
161
+
166
162
private completionMap = new Map < number , boolean > ( ) ;
167
163
private testCaseMap = new Map < string , Map < string , TestCase > > ( ) ;
168
- private path ?: string ;
164
+ private preambleComplete = false ;
169
165
170
166
constructor (
171
167
public testRunStarted : ( ) => void ,
172
168
public addParameterizedTestCase : ( testClass : TestClass , parentIndex : number ) => void
173
169
) { }
174
170
175
171
/**
176
- * Watches for test events on the named pipe at the supplied path.
177
- * As events are read they are parsed and recorded in the test run state.
178
- */
179
- public async watch (
180
- path : string ,
181
- runState : ITestRunState ,
182
- pipeReader ?: INamedPipeReader
183
- ) : Promise < void > {
184
- this . path = path ;
185
-
186
- // Creates a reader based on the platform unless being provided in a test context.
187
- const reader = pipeReader ?? this . createReader ( path ) ;
188
- const readlinePipe = new Readable ( {
189
- read ( ) { } ,
190
- } ) ;
191
-
192
- // Use readline to automatically chunk the data into lines,
193
- // and then take each line and parse it as JSON.
194
- const rl = readline . createInterface ( {
195
- input : readlinePipe ,
196
- crlfDelay : Infinity ,
197
- } ) ;
198
-
199
- rl . on ( "line" , line => this . parse ( JSON . parse ( line ) , runState ) ) ;
200
-
201
- reader . start ( readlinePipe ) ;
202
- }
203
-
204
- /**
205
- * Closes the FIFO pipe after a test run. This must be called at the
206
- * end of a run regardless of the run's success or failure.
172
+ * Parse test run output looking for both raw output and JSON events.
173
+ * @param output A chunk of stdout emitted during a test run.
174
+ * @param runState The test run state to be updated by the output
175
+ * @param logger A logging function to capture output not associated with a specific test.
207
176
*/
208
- public async close ( ) {
209
- if ( ! this . path ) {
210
- return ;
211
- }
212
-
213
- await new Promise < void > ( resolve => {
214
- exec ( `echo '{}' > ${ this . path } ` , ( ) => {
215
- resolve ( ) ;
216
- } ) ;
217
- } ) ;
218
- }
219
-
220
- /**
221
- * Parses stdout of a test run looking for lines that were not captured by
222
- * a JSON event and injecting them in to the test run output.
223
- * @param chunk A chunk of stdout emitted during a test run.
224
- */
225
- public parseStdout = ( ( ) => {
226
- const values = [
227
- ...Object . values ( TestSymbol )
228
- . filter ( symbol => symbol !== TestSymbol . none )
229
- . map ( symbol =>
230
- regexEscapedString (
231
- // Trim the ANSI reset code from the search since some lines
232
- // are fully colorized from the symbol to the end of line.
233
- SymbolRenderer . eventMessageSymbol ( symbol ) . replace (
234
- SymbolRenderer . resetANSIEscapeCode ,
235
- ""
236
- )
237
- )
238
- ) ,
239
- // It is possible there is no symbol for a line produced by swift-testing,
240
- // for instance if the user has a multi line comment before a failing expectation
241
- // only the first line of the printed comment will have a symbol, but to make the
242
- // indentation consistent the subsequent lines will have three spaces. We don't want
243
- // to treat this as output produced by the user during the test run, so omit these.
244
- // This isn't ideal since this will swallow lines the user prints if they start with
245
- // three spaces, but until we have user output as part of the JSON event stream we have
246
- // this workaround.
247
- " " ,
248
- ] ;
249
-
250
- // Build a regex of all the line beginnings that come out of swift-testing events.
251
- const isSwiftTestingLineBeginning = new RegExp ( `^${ values . join ( "|" ) } ` ) ;
252
-
253
- return ( chunk : string , runState : ITestRunState ) => {
254
- for ( const line of chunk . split ( "\n" ) ) {
255
- // Any line in stdout that fails to match as a swift-testing line is treated
256
- // as a user printed value and recorded to the test run output with no associated test.
257
- if ( line . trim ( ) . length > 0 && isSwiftTestingLineBeginning . test ( line ) === false ) {
258
- runState . recordOutput ( undefined , `${ line } \r\n` ) ;
177
+ parseResult ( output : string , runState : ITestRunState , logger : ( output : string ) => void ) : void {
178
+ this . logs . push ( output ) ;
179
+
180
+ for ( const line of output . split ( "\n" ) ) {
181
+ if ( line . startsWith ( "{" ) ) {
182
+ try {
183
+ // On Windows lines end will end with some ANSI characters, so
184
+ // work around that by trying to parse from the start of the line
185
+ // to the last ' }' character.
186
+ const closingBrace = line . lastIndexOf ( "}" ) ;
187
+ if ( closingBrace === - 1 ) {
188
+ continue ;
189
+ }
190
+
191
+ const maybeJSON = line . substring ( 0 , closingBrace + 1 ) ;
192
+
193
+ // TODO: Validate against the event schema
194
+ const event = JSON . parse ( maybeJSON ) as SwiftTestEvent ;
195
+ this . parse ( event , runState ) ;
196
+ this . preambleComplete = true ;
197
+ continue ;
198
+ } catch {
199
+ // Output wasn't valid JSON, continue and treat it like regular output
259
200
}
260
201
}
261
- } ;
262
- } ) ( ) ;
263
202
264
- private createReader ( path : string ) : INamedPipeReader {
265
- return process . platform === "win32"
266
- ? new WindowsNamedPipeReader ( path )
267
- : new UnixNamedPipeReader ( path ) ;
203
+ // Any line in stdout that fails to match as a swift-testing line is treated
204
+ // as a user printed value and recorded to the test run output with no associated test.
205
+ const trimmed = line . trim ( ) ;
206
+ if ( this . preambleComplete && trimmed . length > 0 ) {
207
+ logger ( `${ trimmed } \r\n` ) ;
208
+ }
209
+ }
268
210
}
269
211
270
212
private testName ( id : string ) : string {
@@ -506,7 +448,7 @@ export class SwiftTestingOutputParser {
506
448
return ;
507
449
}
508
450
509
- this . recordOutput ( runState , item . payload . messages , undefined ) ;
451
+ // this.recordOutput(runState, item.payload.messages, undefined);
510
452
}
511
453
}
512
454
}
@@ -523,14 +465,12 @@ export class MessageRenderer {
523
465
}
524
466
525
467
private static colorize ( symbolType : TestSymbol , message : string ) : string {
526
- const ansiEscapeCodePrefix = "\u{001B}[" ;
527
- const resetANSIEscapeCode = `${ ansiEscapeCodePrefix } 0m` ;
528
468
switch ( symbolType ) {
529
469
case TestSymbol . details :
530
470
case TestSymbol . skip :
531
471
case TestSymbol . difference :
532
472
case TestSymbol . passWithKnownIssue :
533
- return ` ${ ansiEscapeCodePrefix } 90m ${ message } ${ resetANSIEscapeCode } ` ;
473
+ return StringColor . default ( message ) ;
534
474
default :
535
475
return message ;
536
476
}
@@ -548,9 +488,6 @@ export class SymbolRenderer {
548
488
return this . colorize ( symbol , this . symbol ( symbol ) ) ;
549
489
}
550
490
551
- static ansiEscapeCodePrefix = "\u{001B}[" ;
552
- static resetANSIEscapeCode = `${ SymbolRenderer . ansiEscapeCodePrefix } 0m` ;
553
-
554
491
// This is adapted from
555
492
// https://github.com/apple/swift-testing/blob/786ade71421eb1d8a9c1d99c902cf1c93096e7df/Sources/Testing/Events/Recorder/Event.Symbol.swift#L102
556
493
public static symbol ( symbol : TestSymbol ) : string {
@@ -604,13 +541,13 @@ export class SymbolRenderer {
604
541
case TestSymbol . skip :
605
542
case TestSymbol . difference :
606
543
case TestSymbol . passWithKnownIssue :
607
- return ` ${ SymbolRenderer . ansiEscapeCodePrefix } 90m ${ symbol } ${ SymbolRenderer . resetANSIEscapeCode } ` ;
544
+ return StringColor . default ( symbol ) ;
608
545
case TestSymbol . pass :
609
- return ` ${ SymbolRenderer . ansiEscapeCodePrefix } 92m ${ symbol } ${ SymbolRenderer . resetANSIEscapeCode } ` ;
546
+ return StringColor . green ( symbol ) ;
610
547
case TestSymbol . fail :
611
- return ` ${ SymbolRenderer . ansiEscapeCodePrefix } 91m ${ symbol } ${ SymbolRenderer . resetANSIEscapeCode } ` ;
548
+ return StringColor . red ( symbol ) ;
612
549
case TestSymbol . warning :
613
- return ` ${ SymbolRenderer . ansiEscapeCodePrefix } 93m ${ symbol } ${ SymbolRenderer . resetANSIEscapeCode } ` ;
550
+ return StringColor . yellow ( symbol ) ;
614
551
case TestSymbol . none :
615
552
default :
616
553
return symbol ;
0 commit comments