11import { expect } from 'chai' ;
22import * as path from 'path' ;
33import * as fs from 'fs-extra' ;
4+ import * as os from 'os' ;
45import * as sinon from 'sinon' ;
56import { LocalQueryInfo , InitialQueryInfo , interpretResultsSarif } from '../../query-results' ;
67import { QueryWithResults } from '../../run-queries-shared' ;
@@ -11,6 +12,7 @@ import { tmpDir } from '../../helpers';
1112import { slurpQueryHistory , splatQueryHistory } from '../../query-serialization' ;
1213import { formatLegacyMessage , QueryInProgress } from '../../legacy-query-server/run-queries' ;
1314import { EvaluationResult , QueryResultType } from '../../pure/legacy-messages' ;
15+ import Sinon = require( 'sinon' ) ;
1416
1517describe ( 'query-results' , ( ) => {
1618 let disposeSpy : sinon . SinonSpy ;
@@ -155,68 +157,213 @@ describe('query-results', () => {
155157 } ) ;
156158 } ) ;
157159
158- it ( 'should interpretResultsSarif' , async ( ) => {
159- const spy = sandbox . mock ( ) ;
160- spy . returns ( { a : '1234' } ) ;
161- const mockServer = {
162- interpretBqrsSarif : spy
163- } as unknown as CodeQLCliServer ;
164-
165- const interpretedResultsPath = path . join ( tmpDir . name , 'interpreted.json' ) ;
166- const resultsPath = '123' ;
167- const sourceInfo = { } ;
160+ describe ( 'interpretResultsSarif' , ( ) => {
161+ let mockServer : CodeQLCliServer ;
162+ let spy : Sinon . SinonExpectation ;
168163 const metadata = {
169164 kind : 'my-kind' ,
170165 id : 'my-id' as string | undefined ,
171166 scored : undefined
172167 } ;
173- const results1 = await interpretResultsSarif (
174- mockServer ,
175- metadata ,
176- {
177- resultsPath, interpretedResultsPath
178- } ,
179- sourceInfo as SourceInfo
180- ) ;
168+ const resultsPath = '123' ;
169+ const interpretedResultsPath = path . join ( tmpDir . name , 'interpreted.json' ) ;
170+ const sourceInfo = { } ;
181171
182- expect ( results1 ) . to . deep . eq ( { a : '1234' , t : 'SarifInterpretationData' } ) ;
183- expect ( spy ) . to . have . been . calledWith (
184- metadata ,
185- resultsPath , interpretedResultsPath , sourceInfo
186- ) ;
172+ beforeEach ( ( ) => {
173+ spy = sandbox . mock ( ) ;
174+ spy . returns ( { a : '1234' } ) ;
187175
188- // Try again, but with no id
189- spy . reset ( ) ;
190- spy . returns ( { a : '1234' } ) ;
191- delete metadata . id ;
192- const results2 = await interpretResultsSarif (
193- mockServer ,
194- metadata ,
195- {
196- resultsPath, interpretedResultsPath
197- } ,
198- sourceInfo as SourceInfo
199- ) ;
200- expect ( results2 ) . to . deep . eq ( { a : '1234' , t : 'SarifInterpretationData' } ) ;
201- expect ( spy ) . to . have . been . calledWith (
202- { kind : 'my-kind' , id : 'dummy-id' , scored : undefined } ,
203- resultsPath , interpretedResultsPath , sourceInfo
204- ) ;
176+ mockServer = {
177+ interpretBqrsSarif : spy
178+ } as unknown as CodeQLCliServer ;
179+ } ) ;
205180
206- // try a third time, but this time we get from file
207- spy . reset ( ) ;
208- fs . writeFileSync ( interpretedResultsPath , JSON . stringify ( {
209- a : 6
210- } ) , 'utf8' ) ;
211- const results3 = await interpretResultsSarif (
212- mockServer ,
213- metadata ,
214- {
215- resultsPath, interpretedResultsPath
216- } ,
217- sourceInfo as SourceInfo
218- ) ;
219- expect ( results3 ) . to . deep . eq ( { a : 6 , t : 'SarifInterpretationData' } ) ;
181+ afterEach ( async ( ) => {
182+ sandbox . restore ( ) ;
183+ safeDel ( interpretedResultsPath ) ;
184+ } ) ;
185+
186+ it ( 'should interpretResultsSarif' , async function ( ) {
187+ // up to 2 minutes per test
188+ this . timeout ( 2 * 60 * 1000 ) ;
189+
190+ const results = await interpretResultsSarif (
191+ mockServer ,
192+ metadata ,
193+ {
194+ resultsPath, interpretedResultsPath
195+ } ,
196+ sourceInfo as SourceInfo
197+ ) ;
198+
199+ expect ( results ) . to . deep . eq ( { a : '1234' , t : 'SarifInterpretationData' } ) ;
200+ expect ( spy ) . to . have . been . calledWith (
201+ metadata ,
202+ resultsPath , interpretedResultsPath , sourceInfo
203+ ) ;
204+ } ) ;
205+
206+ it ( 'should interpretBqrsSarif without ID' , async function ( ) {
207+ // up to 2 minutes per test
208+ this . timeout ( 2 * 60 * 1000 ) ;
209+
210+ delete metadata . id ;
211+ const results = await interpretResultsSarif (
212+ mockServer ,
213+ metadata ,
214+ {
215+ resultsPath, interpretedResultsPath
216+ } ,
217+ sourceInfo as SourceInfo
218+ ) ;
219+ expect ( results ) . to . deep . eq ( { a : '1234' , t : 'SarifInterpretationData' } ) ;
220+ expect ( spy ) . to . have . been . calledWith (
221+ { kind : 'my-kind' , id : 'dummy-id' , scored : undefined } ,
222+ resultsPath , interpretedResultsPath , sourceInfo
223+ ) ;
224+ } ) ;
225+
226+ it ( 'should use sarifParser on a valid small SARIF file' , async function ( ) {
227+ // up to 2 minutes per test
228+ this . timeout ( 2 * 60 * 1000 ) ;
229+
230+ fs . writeFileSync ( interpretedResultsPath , JSON . stringify ( {
231+ runs : [ { results : [ ] } ] // A run needs results to succeed.
232+ } ) , 'utf8' ) ;
233+ const results = await interpretResultsSarif (
234+ mockServer ,
235+ metadata ,
236+ {
237+ resultsPath, interpretedResultsPath
238+ } ,
239+ sourceInfo as SourceInfo
240+ ) ;
241+ // We do not re-interpret if we are reading from a SARIF file.
242+ expect ( spy ) . to . not . have . been . called ;
243+
244+ expect ( results ) . to . have . property ( 't' , 'SarifInterpretationData' ) ;
245+ expect ( results ) . to . have . nested . property ( 'runs[0].results' ) ;
246+ } ) ;
247+
248+ it ( 'should throw an error on an invalid small SARIF file' , async function ( ) {
249+ // up to 2 minutes per test
250+ this . timeout ( 2 * 60 * 1000 ) ;
251+
252+ fs . writeFileSync ( interpretedResultsPath , JSON . stringify ( {
253+ a : '6' // Invalid: no runs or results
254+ } ) , 'utf8' ) ;
255+
256+ await expect (
257+ interpretResultsSarif (
258+ mockServer ,
259+ metadata ,
260+ {
261+ resultsPath, interpretedResultsPath
262+ } ,
263+ sourceInfo as SourceInfo )
264+ ) . to . be . rejectedWith ( 'Parsing output of interpretation failed: Invalid SARIF file: expecting at least one run with result.' ) ;
265+
266+ // We do not attempt to re-interpret if we are reading from a SARIF file.
267+ expect ( spy ) . to . not . have . been . called ;
268+ } ) ;
269+
270+ it ( 'should use sarifParser on a valid large SARIF file' , async function ( ) {
271+ // up to 2 minutes per test
272+ this . timeout ( 2 * 60 * 1000 ) ;
273+
274+ const validSarifStream = fs . createWriteStream ( interpretedResultsPath , { flags : 'w' } ) ;
275+
276+ const finished = new Promise ( ( res , rej ) => {
277+ validSarifStream . addListener ( 'close' , res ) ;
278+ validSarifStream . addListener ( 'error' , rej ) ;
279+ } ) ;
280+
281+ validSarifStream . write ( JSON . stringify ( {
282+ runs : [ { results : [ ] } ] // A run needs results to succeed.
283+ } ) , 'utf8' ) ;
284+
285+ validSarifStream . write ( '[' , 'utf8' ) ;
286+ const iterations = 1_000_000 ;
287+ for ( let i = 0 ; i < iterations ; i ++ ) {
288+ validSarifStream . write ( JSON . stringify ( {
289+ a : '6'
290+ } ) , 'utf8' ) ;
291+ if ( i < iterations - 1 ) {
292+ validSarifStream . write ( ',' ) ;
293+ }
294+ }
295+ validSarifStream . write ( ']' , 'utf8' ) ;
296+ validSarifStream . end ( ) ;
297+ await finished ;
298+
299+ // We need to sleep to wait for MSFT Defender to scan the file
300+ // so that it can be read by our test.
301+ if ( os . platform ( ) === 'win32' ) {
302+ await sleep ( 10_000 ) ;
303+ }
304+
305+ const results = await interpretResultsSarif (
306+ mockServer ,
307+ metadata ,
308+ {
309+ resultsPath, interpretedResultsPath
310+ } ,
311+ sourceInfo as SourceInfo
312+ ) ;
313+ // We do not re-interpret if we are reading from a SARIF file.
314+ expect ( spy ) . to . not . have . been . called ;
315+
316+ expect ( results ) . to . have . property ( 't' , 'SarifInterpretationData' ) ;
317+ expect ( results ) . to . have . nested . property ( 'runs[0].results' ) ;
318+ } ) ;
319+
320+ it ( 'should throw an error on an invalid large SARIF file' , async function ( ) {
321+ // up to 2 minutes per test
322+ this . timeout ( 2 * 60 * 1000 ) ;
323+
324+ // There is a problem on Windows where the file at the prior path isn't able
325+ // to be deleted or written to, so we rename the path for this last test.
326+ const interpretedResultsPath = path . join ( tmpDir . name , 'interpreted-invalid.json' ) ;
327+ const invalidSarifStream = fs . createWriteStream ( interpretedResultsPath , { flags : 'w' } ) ;
328+
329+ const finished = new Promise ( ( res , rej ) => {
330+ invalidSarifStream . addListener ( 'close' , res ) ;
331+ invalidSarifStream . addListener ( 'error' , rej ) ;
332+ } ) ;
333+
334+ invalidSarifStream . write ( '[' , 'utf8' ) ;
335+ const iterations = 1_000_000 ;
336+ for ( let i = 0 ; i < iterations ; i ++ ) {
337+ invalidSarifStream . write ( JSON . stringify ( {
338+ a : '6'
339+ } ) , 'utf8' ) ;
340+ if ( i < iterations - 1 ) {
341+ invalidSarifStream . write ( ',' ) ;
342+ }
343+ }
344+ invalidSarifStream . write ( ']' , 'utf8' ) ;
345+ invalidSarifStream . end ( ) ;
346+ await finished ;
347+
348+ // We need to sleep to wait for MSFT Defender to scan the file
349+ // so that it can be read by our test.
350+ if ( os . platform ( ) === 'win32' ) {
351+ await sleep ( 10_000 ) ;
352+ }
353+
354+ await expect (
355+ interpretResultsSarif (
356+ mockServer ,
357+ metadata ,
358+ {
359+ resultsPath, interpretedResultsPath
360+ } ,
361+ sourceInfo as SourceInfo )
362+ ) . to . be . rejectedWith ( 'Parsing output of interpretation failed: Invalid SARIF file: expecting at least one run with result.' ) ;
363+
364+ // We do not attempt to re-interpret if we are reading from a SARIF file.
365+ expect ( spy ) . to . not . have . been . called ;
366+ } ) ;
220367 } ) ;
221368
222369 describe ( 'splat and slurp' , ( ) => {
@@ -300,6 +447,18 @@ describe('query-results', () => {
300447 } ) ;
301448 } ) ;
302449
450+ function safeDel ( file : string ) {
451+ try {
452+ fs . unlinkSync ( file ) ;
453+ } catch ( e ) {
454+ // ignore
455+ }
456+ }
457+
458+ async function sleep ( ms : number ) {
459+ return new Promise ( resolve => setTimeout ( resolve , ms ) ) ;
460+ }
461+
303462 function createMockQueryWithResults (
304463 queryPath : string ,
305464 didRunSuccessfully = true ,
0 commit comments