Skip to content

Commit 599a9ed

Browse files
When rehydrating, always trigger a monitoring command if variant analysis is not complete
1 parent caeaba2 commit 599a9ed

4 files changed

Lines changed: 206 additions & 9 deletions

File tree

extensions/ql-vscode/src/query-history.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,7 @@ export class QueryHistoryManager extends DisposableObject {
694694
await this.remoteQueriesManager.rehydrateRemoteQuery(item.queryId, item.remoteQuery, item.status);
695695
}
696696
if (item.t === 'variant-analysis') {
697-
await this.variantAnalysisManager.rehydrateVariantAnalysis(item.variantAnalysis, item.status);
697+
await this.variantAnalysisManager.rehydrateVariantAnalysis(item.variantAnalysis);
698698
}
699699
}));
700700
}

extensions/ql-vscode/src/remote-queries/shared/variant-analysis.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,14 @@ export function hasRepoScanCompleted(repo: VariantAnalysisScannedRepository): bo
151151
return isCompletedAnalysisRepoStatus(repo.analysisStatus);
152152
}
153153

154+
/**
155+
* @param repo
156+
* @returns whether the repo scan is complete and has results that can be downloaded
157+
*/
158+
export function repoScanHasResults(repo: VariantAnalysisScannedRepository): boolean {
159+
return repo.analysisStatus === VariantAnalysisRepoStatus.Succeeded && (repo.resultCount || 0) > 0;
160+
}
161+
154162
/**
155163
* @param repos
156164
* @returns the total number of results. Will be `undefined` when there are no repos with results.

extensions/ql-vscode/src/remote-queries/variant-analysis-manager.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@ import {
1111
VariantAnalysisScannedRepository as ApiVariantAnalysisScannedRepository
1212
} from './gh-api/variant-analysis';
1313
import {
14+
hasRepoScanCompleted,
15+
repoScanHasResults,
1416
VariantAnalysis, VariantAnalysisQueryLanguage,
17+
VariantAnalysisScannedRepository,
1518
VariantAnalysisScannedRepositoryDownloadStatus,
1619
VariantAnalysisScannedRepositoryResult,
17-
VariantAnalysisScannedRepositoryState
20+
VariantAnalysisScannedRepositoryState,
21+
VariantAnalysisStatus
1822
} from './shared/variant-analysis';
1923
import { getErrorMessage } from '../pure/helpers-pure';
2024
import { VariantAnalysisView } from './variant-analysis-view';
@@ -24,7 +28,6 @@ import { getControllerRepo } from './run-remote-query';
2428
import { processUpdatedVariantAnalysis } from './variant-analysis-processor';
2529
import PQueue from 'p-queue';
2630
import { createTimestampFile, showAndLogErrorMessage } from '../helpers';
27-
import { QueryStatus } from '../query-status';
2831
import * as fs from 'fs-extra';
2932

3033

@@ -56,22 +59,42 @@ export class VariantAnalysisManager extends DisposableObject implements VariantA
5659
this.variantAnalysisResultsManager.onResultLoaded(this.onRepoResultLoaded.bind(this));
5760
}
5861

59-
public async rehydrateVariantAnalysis(variantAnalysis: VariantAnalysis, status: QueryStatus) {
62+
public async rehydrateVariantAnalysis(variantAnalysis: VariantAnalysis) {
6063
if (!(await this.variantAnalysisRecordExists(variantAnalysis.id))) {
6164
// In this case, the variant analysis was deleted from disk, most likely because
6265
// it was purged by another workspace.
6366
this._onVariantAnalysisRemoved.fire(variantAnalysis);
6467
} else {
6568
this.variantAnalyses.set(variantAnalysis.id, variantAnalysis);
6669
await this.getView(variantAnalysis.id)?.updateView(variantAnalysis);
67-
if (status === QueryStatus.InProgress) {
68-
// In this case, last time we checked, the query was still in progress.
69-
// We need to setup the monitor to check for completion.
70+
71+
if (!await this.isVariantAnalysisComplete(variantAnalysis)) {
7072
await commands.executeCommand('codeQL.monitorVariantAnalysis', variantAnalysis);
7173
}
7274
}
7375
}
7476

77+
public async isVariantAnalysisComplete(variantAnalysis: VariantAnalysis): Promise<boolean> {
78+
// It's only acceptable to have no scanned repos if the variant analysis is not in a final state.
79+
// Otherwise it means the analysis hit some kind of internal error or there were no repos to scan.
80+
if (variantAnalysis.scannedRepos === undefined || variantAnalysis.scannedRepos.length === 0) {
81+
return variantAnalysis.status !== VariantAnalysisStatus.InProgress;
82+
}
83+
84+
return (await Promise.all(variantAnalysis.scannedRepos.map(repo => this.isVariantAnalysisRepoComplete(variantAnalysis.id, repo)))).every(x => x);
85+
}
86+
87+
private async isVariantAnalysisRepoComplete(variantAnalysisId: number, repo: VariantAnalysisScannedRepository): Promise<boolean> {
88+
if (!hasRepoScanCompleted(repo)) {
89+
return false;
90+
} else if (!repoScanHasResults(repo)) {
91+
return true;
92+
} else {
93+
const storageLocation = this.getVariantAnalysisStorageLocation(variantAnalysisId);
94+
return await this.variantAnalysisResultsManager.isVariantAnalysisRepoDownloaded(storageLocation, repo.repository.fullName);
95+
}
96+
}
97+
7598
public async removeVariantAnalysis(variantAnalysis: VariantAnalysis) {
7699
this.variantAnalysisResultsManager.removeAnalysisResults(variantAnalysis);
77100
await this.removeStorageDirectory(variantAnalysis.id);
@@ -234,7 +257,7 @@ export class VariantAnalysisManager extends DisposableObject implements VariantA
234257
* used by the query history manager to determine when the directory
235258
* should be deleted.
236259
*/
237-
private async prepareStorageDirectory(variantAnalysisId: number): Promise<void> {
260+
public async prepareStorageDirectory(variantAnalysisId: number): Promise<void> {
238261
await createTimestampFile(this.getVariantAnalysisStorageLocation(variantAnalysisId));
239262
}
240263

extensions/ql-vscode/src/vscode-tests/cli-integration/remote-queries/variant-analysis-manager.test.ts

Lines changed: 167 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as sinon from 'sinon';
22
import { expect } from 'chai';
3-
import { CancellationTokenSource, extensions } from 'vscode';
3+
import { CancellationTokenSource, commands, Disposable, extensions } from 'vscode';
44
import { CodeQLExtensionInterface } from '../../../extension';
55
import { logger } from '../../../logging';
66
import * as config from '../../../config';
@@ -21,6 +21,8 @@ import { createMockVariantAnalysisRepoTask } from '../../factories/remote-querie
2121
import { CodeQLCliServer } from '../../../cli';
2222
import { storagePath } from '../global.helper';
2323
import { VariantAnalysisResultsManager } from '../../../remote-queries/variant-analysis-results-manager';
24+
import { createMockVariantAnalysis } from '../../factories/remote-queries/shared/variant-analysis';
25+
import { VariantAnalysis, VariantAnalysisStatus } from '../../../remote-queries/shared/variant-analysis';
2426

2527
describe('Variant Analysis Manager', async function() {
2628
let sandbox: sinon.SinonSandbox;
@@ -165,4 +167,168 @@ describe('Variant Analysis Manager', async function() {
165167
});
166168
});
167169
});
170+
171+
describe('when rehydrating a query', async () => {
172+
let variantAnalysis: VariantAnalysis;
173+
let variantAnalysisRemovedFired = false;
174+
let onVariantAnalysisRemovedListener: Disposable;
175+
let monitorVariantAnalysisCommandCalled = false;
176+
177+
beforeEach(() => {
178+
variantAnalysis = createMockVariantAnalysis();
179+
180+
variantAnalysisRemovedFired = false;
181+
onVariantAnalysisRemovedListener = variantAnalysisManager.onVariantAnalysisRemoved(() => {
182+
variantAnalysisRemovedFired = true;
183+
});
184+
185+
monitorVariantAnalysisCommandCalled = false;
186+
sandbox.stub(commands, 'executeCommand').callsFake((command: string) => {
187+
if (command === 'codeQL.monitorVariantAnalysis') {
188+
monitorVariantAnalysisCommandCalled = true;
189+
}
190+
return Promise.resolve();
191+
});
192+
});
193+
194+
afterEach(() => {
195+
onVariantAnalysisRemovedListener.dispose();
196+
});
197+
198+
describe('when variant analysis record doesn\'t exist', async () => {
199+
it('should remove the variant analysis', async () => {
200+
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
201+
expect(variantAnalysisRemovedFired).to.equal(true);
202+
});
203+
204+
it('should not trigger a monitoring command', async () => {
205+
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
206+
expect(monitorVariantAnalysisCommandCalled).to.equal(false);
207+
});
208+
});
209+
210+
describe('when variant analysis record does exist', async () => {
211+
let variantAnalysisStorageLocation: string;
212+
213+
beforeEach(async () => {
214+
variantAnalysisStorageLocation = variantAnalysisManager.getVariantAnalysisStorageLocation(variantAnalysis.id);
215+
await variantAnalysisManager.prepareStorageDirectory(variantAnalysis.id);
216+
});
217+
218+
afterEach(() => {
219+
fs.rmSync(variantAnalysisStorageLocation, { recursive: true });
220+
});
221+
222+
describe('when the variant analysis is not complete', async () => {
223+
beforeEach(() => {
224+
sinon.stub(variantAnalysisManager, 'isVariantAnalysisComplete').resolves(false);
225+
});
226+
227+
it('should not remove the variant analysis', async () => {
228+
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
229+
expect(variantAnalysisRemovedFired).to.equal(false);
230+
});
231+
232+
it('should trigger a monitoring command', async () => {
233+
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
234+
expect(monitorVariantAnalysisCommandCalled).to.equal(true);
235+
});
236+
});
237+
238+
describe('when the variant analysis is complete', async () => {
239+
beforeEach(() => {
240+
sinon.stub(variantAnalysisManager, 'isVariantAnalysisComplete').resolves(true);
241+
});
242+
243+
it('should not remove the variant analysis', async () => {
244+
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
245+
expect(variantAnalysisRemovedFired).to.equal(false);
246+
});
247+
248+
it('should not trigger a monitoring command', async () => {
249+
await variantAnalysisManager.rehydrateVariantAnalysis(variantAnalysis);
250+
expect(monitorVariantAnalysisCommandCalled).to.equal(false);
251+
});
252+
});
253+
});
254+
});
255+
256+
describe('isVariantAnalysisComplete', async () => {
257+
let variantAnalysis: VariantAnalysis;
258+
259+
beforeEach(() => {
260+
variantAnalysis = createMockVariantAnalysis();
261+
});
262+
263+
describe('when variant analysis status is InProgress', async () => {
264+
beforeEach(() => {
265+
variantAnalysis.status = VariantAnalysisStatus.InProgress;
266+
});
267+
268+
describe('when scanned repos is undefined', async () => {
269+
it('should say the variant analysis is not complete', async () => {
270+
variantAnalysis.scannedRepos = undefined;
271+
expect(variantAnalysisManager.isVariantAnalysisComplete(variantAnalysis)).to.equal(false);
272+
});
273+
});
274+
275+
describe('when scanned repos is non-empty', async () => {
276+
describe('when not all results are downloaded', async () => {
277+
it('should say the variant analysis is not complete', async () => {
278+
sinon.stub(variantAnalysisResultsManager, 'isVariantAnalysisRepoDownloaded').resolves(false);
279+
expect(variantAnalysisManager.isVariantAnalysisComplete(variantAnalysis)).to.equal(false);
280+
});
281+
});
282+
283+
describe('when all results are downloaded', async () => {
284+
it('should say the variant analysis is complete', async () => {
285+
sinon.stub(variantAnalysisResultsManager, 'isVariantAnalysisRepoDownloaded').resolves(true);
286+
expect(variantAnalysisManager.isVariantAnalysisComplete(variantAnalysis)).to.equal(true);
287+
});
288+
});
289+
});
290+
});
291+
292+
for (const variantAnalysisStatus of [
293+
VariantAnalysisStatus.Succeeded,
294+
VariantAnalysisStatus.Failed,
295+
VariantAnalysisStatus.Canceled
296+
]) {
297+
describe(`when variant analysis status is ${variantAnalysisStatus}`, async () => {
298+
beforeEach(() => {
299+
variantAnalysis.status = variantAnalysisStatus;
300+
});
301+
302+
describe('when scanned repos is undefined', async () => {
303+
it('should say the variant analysis is complete', async () => {
304+
variantAnalysis.scannedRepos = undefined;
305+
expect(variantAnalysisManager.isVariantAnalysisComplete(variantAnalysis)).to.equal(true);
306+
});
307+
});
308+
309+
describe('when scanned repos is empty', async () => {
310+
it('should say the variant analysis is complete', async () => {
311+
variantAnalysis.scannedRepos = [];
312+
expect(variantAnalysisManager.isVariantAnalysisComplete(variantAnalysis)).to.equal(true);
313+
});
314+
});
315+
316+
describe('when scanned repos is non-empty', async () => {
317+
describe('when not all results are downloaded', async () => {
318+
it('should say the variant analysis is not complete', async () => {
319+
sinon.stub(variantAnalysisResultsManager, 'isVariantAnalysisRepoDownloaded').resolves(false);
320+
expect(variantAnalysisManager.isVariantAnalysisComplete(variantAnalysis)).to.equal(false);
321+
});
322+
});
323+
324+
describe('when all results are downloaded', async () => {
325+
it('should say the variant analysis is complete', async () => {
326+
sinon.stub(variantAnalysisResultsManager, 'isVariantAnalysisRepoDownloaded').resolves(true);
327+
expect(variantAnalysisManager.isVariantAnalysisComplete(variantAnalysis)).to.equal(true);
328+
});
329+
});
330+
});
331+
});
332+
}
333+
});
168334
});

0 commit comments

Comments
 (0)