Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c13319d
Use sarif parser for reopened results
angelapwen Aug 9, 2022
b3d7e78
Bump unit test timeout to 3 min
angelapwen Aug 15, 2022
c37c5bf
Bump unit test timeout to 5 min
angelapwen Aug 15, 2022
f0d0017
Merge branch 'main' into handle-sarif-reopen
angelapwen Oct 11, 2022
c5a816d
Use sarif parser for reopened results
angelapwen Aug 9, 2022
c1b3ee1
Remove extra import statement
angelapwen Oct 11, 2022
2b6dd6b
Exit parser when invalid SARIF is parsed
angelapwen Oct 11, 2022
169a88a
Reset test timeout to 2 min
angelapwen Oct 11, 2022
fc32971
Add tests for valid and invalid small SARIF files
angelapwen Oct 11, 2022
ca8f930
Add large SARIF file tests
angelapwen Oct 12, 2022
8c92a19
Delete large valid SARIF before invalid test
angelapwen Oct 13, 2022
438607d
Use `del` rather than `unlink`
angelapwen Oct 13, 2022
a07e549
Update imports
angelapwen Oct 13, 2022
9fe8fd8
Close write stream
angelapwen Oct 13, 2022
d656db0
Improve SARIF parser and interpreter tests
angelapwen Oct 19, 2022
dcc51cc
Move interpretedResultsPath to before hook
angelapwen Oct 19, 2022
badbed9
Use safeDel rather than del
angelapwen Oct 19, 2022
6bd649a
Write valid JSON to large valid SARIF test
angelapwen Oct 20, 2022
05c6efe
Remove file deletion in after block
angelapwen Oct 20, 2022
f75f0b7
Use `safeDel` in after hook
angelapwen Oct 20, 2022
f7dc7b7
Attempt 1000 iterations for large tests
angelapwen Oct 21, 2022
50b3109
Sleep 10ms in tests on Windows
angelapwen Oct 21, 2022
fbe0b98
Add missing import
angelapwen Oct 21, 2022
93bd94c
Change stream listeners to close rather than finish
angelapwen Oct 21, 2022
86eaf9d
Write 1mil times for SARIF files
angelapwen Oct 21, 2022
b849fa9
Write to a new path for last test
angelapwen Oct 21, 2022
262fbee
Refactor timeout to before hook
angelapwen Oct 21, 2022
75c8aa3
Revert "Refactor timeout to before hook"
angelapwen Oct 21, 2022
f37bf65
Rename invalid results path
angelapwen Oct 21, 2022
ca0c863
Update results path var name
angelapwen Oct 24, 2022
671f0e2
Add comment explanation
angelapwen Oct 24, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions extensions/ql-vscode/src/query-results.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { QueryStatus } from './query-status';
import { RemoteQueryHistoryItem } from './remote-queries/remote-query-history-item';
import { QueryEvaluationInfo, QueryWithResults } from './run-queries-shared';
import { formatLegacyMessage } from './legacy-query-server/run-queries';
import { sarifParser } from './sarif-parser';

/**
* query-results.ts
Expand Down Expand Up @@ -160,10 +161,12 @@ export async function interpretResultsSarif(
sourceInfo?: cli.SourceInfo
): Promise<SarifInterpretationData> {
const { resultsPath, interpretedResultsPath } = resultsPaths;
let res;
if (await fs.pathExists(interpretedResultsPath)) {
return { ...JSON.parse(await fs.readFile(interpretedResultsPath, 'utf8')), t: 'SarifInterpretationData' };
res = await sarifParser(interpretedResultsPath);
Comment thread
angelapwen marked this conversation as resolved.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a thought....if this function throws an error because the file is not parseable, should we delete the file and re-run cli.interpretBqrsSarif?

Possibly, we can do this as a followup.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, that's a good point 👍 I'll leave this comment up for now, while we work out the last invalid SARIF test issue..

} else {
res = await cli.interpretBqrsSarif(ensureMetadataIsComplete(metadata), resultsPath, interpretedResultsPath, sourceInfo);
}
const res = await cli.interpretBqrsSarif(ensureMetadataIsComplete(metadata), resultsPath, interpretedResultsPath, sourceInfo);
return { ...res, t: 'SarifInterpretationData' };
}

Expand Down
22 changes: 12 additions & 10 deletions extensions/ql-vscode/src/sarif-parser.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
import * as Sarif from 'sarif';
import * as fs from 'fs-extra';
import { parser } from 'stream-json';
import { pick } from 'stream-json/filters/Pick';
import Assembler = require('stream-json/Assembler');
import { chain } from 'stream-chain';
import { getErrorMessage } from './pure/helpers-pure';
import Pick = require('stream-json/filters/Pick');
Comment thread
angelapwen marked this conversation as resolved.
Outdated

const DUMMY_TOOL: Sarif.Tool = { driver: { name: '' } };

export async function sarifParser(interpretedResultsPath: string): Promise<Sarif.Log> {
try {
// Parse the SARIF file into token streams, filtering out only the results array.
const p = parser();
const pipeline = chain([
fs.createReadStream(interpretedResultsPath),
p,
pick({ filter: 'runs.0.results' })
]);
const pipeline = fs.createReadStream(interpretedResultsPath).pipe(Pick.withParser({ filter: 'runs.0.results' }));

// Creates JavaScript objects from the token stream
const asm = Assembler.connectTo(pipeline);

// Returns a constructed Log object with the results or an empty array if no results were found.
// Returns a constructed Log object with the results of an empty array if no results were found.
// If the parser fails for any reason, it will reject the promise.
return await new Promise((resolve, reject) => {
let alreadyDone = false;
pipeline.on('error', (error) => {
reject(error);
});

// If the parser pipeline completes before the assembler, we've reached end of file and have not found any results.
pipeline.on('end', () => {
if (!alreadyDone) {
reject(new Error('Invalid SARIF file: expecting at least one run with result.'));
}
});

asm.on('done', (asm) => {

const log: Sarif.Log = {
Expand All @@ -41,6 +42,7 @@ export async function sarifParser(interpretedResultsPath: string): Promise<Sarif
};

resolve(log);
alreadyDone = true;
});
});
} catch (e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,10 @@ describe('query-results', () => {
});
});

it('should interpretResultsSarif', async () => {
it('should interpretResultsSarif', async function() {
// up to 2 minutes per test
this.timeout(2 * 60 * 1000);

const spy = sandbox.mock();
spy.returns({ a: '1234' });
const mockServer = {
Expand Down Expand Up @@ -203,10 +206,10 @@ describe('query-results', () => {
resultsPath, interpretedResultsPath, sourceInfo
);

// try a third time, but this time we get from file
// try a third time, but this time we get from a valid small SARIF file
spy.reset();
fs.writeFileSync(interpretedResultsPath, JSON.stringify({
a: 6
runs: [{ results: [] }] // A run needs results to succeed.
}), 'utf8');
const results3 = await interpretResultsSarif(
mockServer,
Expand All @@ -216,7 +219,81 @@ describe('query-results', () => {
},
sourceInfo as SourceInfo
);
expect(results3).to.deep.eq({ a: 6, t: 'SarifInterpretationData' });
// We do not re-interpret if we are reading from a SARIF file.
expect(spy).to.not.have.been.called;

expect(results3).to.have.property('t', 'SarifInterpretationData');
expect(results3).to.have.nested.property('runs[0].results');

// try a fourth time, but this time we use an invalid small SARIF file
spy.reset();
fs.writeFileSync(interpretedResultsPath, JSON.stringify({
a: '6' // Invalid: no runs or results
}), 'utf8');

await expect(
interpretResultsSarif(
mockServer,
metadata,
{
resultsPath, interpretedResultsPath
},
sourceInfo as SourceInfo)
).to.be.rejectedWith('Parsing output of interpretation failed: Invalid SARIF file: expecting at least one run with result.');

// We do not attempt to re-interpret if we are reading from a SARIF file.
expect(spy).to.not.have.been.called;

// Try a fifth time with a valid large SARIF file
spy.reset();
const validSarifStream = fs.createWriteStream(interpretedResultsPath, { flags: 'w' });

validSarifStream.write(JSON.stringify({
runs: [{ results: [] }] // A run needs results to succeed.
}), 'utf8');

for (let i = 0; i < 10000000; i++) {
validSarifStream.write(JSON.stringify({
a: '6'
}), 'utf8');
}
validSarifStream.end();

const results5 = await interpretResultsSarif(
mockServer,
metadata,
{
resultsPath, interpretedResultsPath
},
sourceInfo as SourceInfo
);
// We do not re-interpret if we are reading from a SARIF file.
expect(spy).to.not.have.been.called;

expect(results5).to.have.property('t', 'SarifInterpretationData');
expect(results5).to.have.nested.property('runs[0].results');
Comment thread
angelapwen marked this conversation as resolved.
Outdated

// Try a sixth time with an invalid large SARIF file
spy.reset();
const invalidSarifStream = fs.createWriteStream(interpretedResultsPath, { flags: 'w' });
for (let i = 0; i < 10000000; i++) {
invalidSarifStream.write(JSON.stringify({
a: '6'
}), 'utf8');
}

await expect(
interpretResultsSarif(
mockServer,
metadata,
{
resultsPath, interpretedResultsPath
},
sourceInfo as SourceInfo)
).to.be.rejectedWith('Parsing output of interpretation failed: Invalid SARIF file: expecting at least one run with result.');
Comment thread
angelapwen marked this conversation as resolved.
Outdated
Comment thread
angelapwen marked this conversation as resolved.
Outdated

// We do not attempt to re-interpret if we are reading from a SARIF file.
expect(spy).to.not.have.been.called;
});

describe('splat and slurp', () => {
Expand Down