Skip to content

Commit c36ce48

Browse files
Merge pull request #1650 from github/nora-elena/integration-tests
Add tests for variant analysis history item
2 parents 25a9ee1 + 7685d76 commit c36ce48

File tree

5 files changed

+295
-12
lines changed

5 files changed

+295
-12
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* @name Variant Analysis Integration Test 1
3+
* @kind problem
4+
* @problem.severity warning
5+
* @id javascript/variant-analysis-integration-test-1
6+
*/
7+
import javascript
8+
9+
from MemberDeclaration md
10+
where md.getName() = "dispose"
11+
select md, "Dispose method"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* @name Variant Analysis Integration Test 2
3+
* @kind problem
4+
* @problem.severity warning
5+
* @id ruby/example/empty-block
6+
*/
7+
8+
import ruby
9+
10+
from Block b
11+
where b.getNumberOfStatements() = 0
12+
select b, "This is an empty block."
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
"version": 1,
3+
"queries": [
4+
{
5+
"t": "variant-analysis",
6+
"status": "Completed",
7+
"completed": true,
8+
"variantAnalysis": {
9+
"id": 98574321397,
10+
"controllerRepoId": 128321,
11+
"query": {
12+
"name": "Variant Analysis Integration Test 1",
13+
"filePath": "PLACEHOLDER/q2.ql",
14+
"language": "javascript",
15+
"text": "/**\n * @name Variant Analysis Integration Test 1\n * @kind problem\n * @problem.severity warning\n * @id javascript/variant-analysis-integration-test-1\n */\nimport javascript\n\nfrom MemberDeclaration md\nwhere md.getName() = \"dispose\"\nselect md, \"Dispose method\"\n"
16+
},
17+
"databases": {
18+
"repositories": ["234321", "123301"]
19+
},
20+
"createdAt": "1666688269308",
21+
"updatedAt": "1666688869308",
22+
"executionStartTime": 1645644973911,
23+
"status": "succeeded",
24+
"actionsWorkflowRunId": 1889316048
25+
}
26+
},
27+
{
28+
"t": "variant-analysis",
29+
"status": "Completed",
30+
"completed": true,
31+
"variantAnalysis": {
32+
"id": 98574321397,
33+
"controllerRepoId": 128321,
34+
"query": {
35+
"name": "Variant Analysis Integration Test 2",
36+
"filePath": "PLACEHOLDER/q2.ql",
37+
"language": "ruby",
38+
"text": "/**\n * @name Variant Analysis Integration Test 2\n * @kind problem\n * @problem.severity warning\n * @id ruby/example/empty-block\n */\nimport ruby\n\nfrom Block b\nwhere b.getNumberOfStatements() = 0\nselect b, \"This is an empty block.\"\n"
39+
},
40+
"databases": {
41+
"repositories": ["92384123", "1230871"]
42+
},
43+
"createdAt": "1666689080847",
44+
"updatedAt": "1666689080847",
45+
"executionStartTime": 1645644973911,
46+
"status": "succeeded",
47+
"actionsWorkflowRunId": 1889316048
48+
}
49+
}
50+
]
51+
}

extensions/ql-vscode/src/vscode-tests/no-workspace/remote-queries/remote-query-history.test.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ describe('Remote queries and query history manager', function() {
5858
await copyHistoryState();
5959

6060
sandbox = sinon.createSandbox();
61+
disposables = new DisposableBucket();
6162

6263
localQueriesResultsViewStub = {
6364
showResults: sandbox.stub()
@@ -81,17 +82,6 @@ describe('Remote queries and query history manager', function() {
8182
onVariantAnalysisStatusUpdated: sandbox.stub(),
8283
onVariantAnalysisRemoved: sandbox.stub()
8384
} as any as VariantAnalysisManager;
84-
});
85-
86-
afterEach(function() {
87-
// set a higher timeout since recursive delete below may take a while, expecially on Windows.
88-
this.timeout(120000);
89-
deleteHistoryState();
90-
});
91-
92-
beforeEach(() => {
93-
sandbox = sinon.createSandbox();
94-
disposables = new DisposableBucket();
9585

9686
rawQueryHistory = fs.readJSONSync(path.join(STORAGE_DIR, 'workspace-query-history.json')).queries;
9787
remoteQueryResult0 = fs.readJSONSync(path.join(STORAGE_DIR, 'queries', rawQueryHistory[0].queryId, 'query-result.json'));
@@ -121,7 +111,10 @@ describe('Remote queries and query history manager', function() {
121111
openTextDocumentSpy = sandbox.spy(workspace, 'openTextDocument');
122112
});
123113

124-
afterEach(() => {
114+
afterEach(function() {
115+
// set a higher timeout since recursive delete below may take a while, expecially on Windows.
116+
this.timeout(120000);
117+
deleteHistoryState();
125118
disposables.dispose(testDisposeHandler);
126119
sandbox.restore();
127120
});
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
import * as fs from 'fs-extra';
2+
import * as path from 'path';
3+
import * as sinon from 'sinon';
4+
import { expect } from 'chai';
5+
6+
import {
7+
ExtensionContext,
8+
Uri,
9+
window,
10+
workspace,
11+
} from 'vscode';
12+
import { QueryHistoryConfig } from '../../../config';
13+
import { DatabaseManager } from '../../../databases';
14+
import { tmpDir } from '../../../helpers';
15+
import { QueryHistoryManager } from '../../../query-history';
16+
import { DisposableBucket } from '../../disposable-bucket';
17+
import { testDisposeHandler } from '../../test-dispose-handler';
18+
import { walkDirectory } from '../../../helpers';
19+
import { HistoryItemLabelProvider } from '../../../history-item-label-provider';
20+
import { RemoteQueriesManager } from '../../../remote-queries/remote-queries-manager';
21+
import { ResultsView } from '../../../interface';
22+
import { EvalLogViewer } from '../../../eval-log-viewer';
23+
import { QueryRunner } from '../../../queryRunner';
24+
import { VariantAnalysisManager } from '../../../remote-queries/variant-analysis-manager';
25+
26+
/**
27+
* Tests for variant analyses and how they interact with the query history manager.
28+
*/
29+
30+
describe('Variant Analyses and QueryHistoryManager', function() {
31+
const EXTENSION_PATH = path.join(__dirname, '../../../../');
32+
const STORAGE_DIR = Uri.file(path.join(tmpDir.name, 'variant-analysis')).fsPath;
33+
const asyncNoop = async () => {
34+
/** noop */
35+
};
36+
37+
let sandbox: sinon.SinonSandbox;
38+
let qhm: QueryHistoryManager;
39+
let localQueriesResultsViewStub: ResultsView;
40+
let remoteQueriesManagerStub: RemoteQueriesManager;
41+
let variantAnalysisManagerStub: VariantAnalysisManager;
42+
let rawQueryHistory: any;
43+
let disposables: DisposableBucket;
44+
let showTextDocumentSpy: sinon.SinonSpy;
45+
let openTextDocumentSpy: sinon.SinonSpy;
46+
47+
let rehydrateVariantAnalysisStub: sinon.SinonStub;
48+
let removeVariantAnalysisStub: sinon.SinonStub;
49+
let showViewStub: sinon.SinonStub;
50+
51+
beforeEach(async function() {
52+
// set a higher timeout since recursive delete below may take a while, expecially on Windows.
53+
this.timeout(120000);
54+
55+
// Since these tests change the state of the query history manager, we need to copy the original
56+
// to a temporary folder where we can manipulate it for tests
57+
await copyHistoryState();
58+
59+
sandbox = sinon.createSandbox();
60+
disposables = new DisposableBucket();
61+
62+
localQueriesResultsViewStub = {
63+
showResults: sandbox.stub()
64+
} as any as ResultsView;
65+
66+
rehydrateVariantAnalysisStub = sandbox.stub();
67+
removeVariantAnalysisStub = sandbox.stub();
68+
showViewStub = sandbox.stub();
69+
70+
remoteQueriesManagerStub = {
71+
onRemoteQueryAdded: sandbox.stub(),
72+
onRemoteQueryRemoved: sandbox.stub(),
73+
onRemoteQueryStatusUpdate: sandbox.stub(),
74+
rehydrateRemoteQuery: sandbox.stub(),
75+
openRemoteQueryResults: sandbox.stub()
76+
} as any as RemoteQueriesManager;
77+
78+
variantAnalysisManagerStub = {
79+
onVariantAnalysisAdded: sandbox.stub(),
80+
onVariantAnalysisRemoved: sandbox.stub(),
81+
removeVariantAnalysis: removeVariantAnalysisStub,
82+
rehydrateVariantAnalysis: rehydrateVariantAnalysisStub,
83+
onVariantAnalysisStatusUpdated: sandbox.stub(),
84+
showView: showViewStub
85+
} as any as VariantAnalysisManager;
86+
87+
rawQueryHistory = fs.readJSONSync(path.join(STORAGE_DIR, 'workspace-query-history.json')).queries;
88+
89+
qhm = new QueryHistoryManager(
90+
{} as QueryRunner,
91+
{} as DatabaseManager,
92+
localQueriesResultsViewStub,
93+
remoteQueriesManagerStub,
94+
variantAnalysisManagerStub,
95+
{} as EvalLogViewer,
96+
STORAGE_DIR,
97+
{
98+
globalStorageUri: Uri.file(STORAGE_DIR),
99+
extensionPath: EXTENSION_PATH
100+
} as ExtensionContext,
101+
{
102+
onDidChangeConfiguration: () => new DisposableBucket(),
103+
} as unknown as QueryHistoryConfig,
104+
new HistoryItemLabelProvider({} as QueryHistoryConfig),
105+
asyncNoop
106+
);
107+
disposables.push(qhm);
108+
109+
showTextDocumentSpy = sandbox.spy(window, 'showTextDocument');
110+
openTextDocumentSpy = sandbox.spy(workspace, 'openTextDocument');
111+
});
112+
113+
afterEach(function() {
114+
deleteHistoryState();
115+
disposables.dispose(testDisposeHandler);
116+
sandbox.restore();
117+
});
118+
119+
it('should read query history that has variant analysis history items', async () => {
120+
await qhm.readQueryHistory();
121+
122+
expect(rehydrateVariantAnalysisStub).to.have.callCount(2);
123+
expect(rehydrateVariantAnalysisStub.getCall(0).args[0]).to.deep.eq(rawQueryHistory[0].variantAnalysis);
124+
expect(rehydrateVariantAnalysisStub.getCall(0).args[1]).to.deep.eq(rawQueryHistory[0].status);
125+
expect(rehydrateVariantAnalysisStub.getCall(1).args[0]).to.deep.eq(rawQueryHistory[1].variantAnalysis);
126+
expect(rehydrateVariantAnalysisStub.getCall(1).args[1]).to.deep.eq(rawQueryHistory[1].status);
127+
128+
expect(qhm.treeDataProvider.allHistory[0]).to.deep.eq(rawQueryHistory[0]);
129+
expect(qhm.treeDataProvider.allHistory[1]).to.deep.eq(rawQueryHistory[1]);
130+
expect(qhm.treeDataProvider.allHistory.length).to.eq(2);
131+
});
132+
133+
it('should remove the variant analysis history item', async () => {
134+
await qhm.readQueryHistory();
135+
136+
// Remove the first variant analysis
137+
await qhm.handleRemoveHistoryItem(qhm.treeDataProvider.allHistory[0]);
138+
139+
// Add it back to the history
140+
qhm.addQuery(rawQueryHistory[0]);
141+
expect(removeVariantAnalysisStub).to.have.callCount(1);
142+
expect(rehydrateVariantAnalysisStub).to.have.callCount(2);
143+
expect(qhm.treeDataProvider.allHistory).to.deep.eq([rawQueryHistory[1], rawQueryHistory[0]]);
144+
});
145+
146+
it('should remove two queries from history', async () => {
147+
await qhm.readQueryHistory();
148+
149+
// Remove both queries
150+
// Just for fun, let's do it in reverse order
151+
await qhm.handleRemoveHistoryItem(undefined!, [qhm.treeDataProvider.allHistory[1], qhm.treeDataProvider.allHistory[0]]);
152+
153+
expect(removeVariantAnalysisStub.callCount).to.eq(2);
154+
expect(removeVariantAnalysisStub.getCall(0).args[0]).to.deep.eq(rawQueryHistory[1].variantAnalysis);
155+
expect(removeVariantAnalysisStub.getCall(1).args[0]).to.deep.eq(rawQueryHistory[0].variantAnalysis);
156+
expect(qhm.treeDataProvider.allHistory).to.deep.eq([]);
157+
158+
// also, both queries should be removed from disk storage
159+
expect(fs.readJSONSync(path.join(STORAGE_DIR, 'workspace-query-history.json'))).to.deep.eq({
160+
version: 2,
161+
queries: []
162+
});
163+
});
164+
165+
it('should handle a click', async () => {
166+
await qhm.readQueryHistory();
167+
168+
await qhm.handleItemClicked(qhm.treeDataProvider.allHistory[0], []);
169+
expect(showViewStub).calledOnceWithExactly(rawQueryHistory[0].variantAnalysis.id);
170+
});
171+
172+
it('should get the query text', async () => {
173+
await qhm.readQueryHistory();
174+
await qhm.handleShowQueryText(qhm.treeDataProvider.allHistory[0], []);
175+
176+
expect(showTextDocumentSpy).to.have.been.calledOnce;
177+
expect(openTextDocumentSpy).to.have.been.calledOnce;
178+
179+
const uri: Uri = openTextDocumentSpy.getCall(0).args[0];
180+
expect(uri.scheme).to.eq('codeql');
181+
const params = new URLSearchParams(uri.query);
182+
expect(params.get('isQuickEval')).to.eq('false');
183+
expect(params.get('queryText')).to.eq(rawQueryHistory[0].variantAnalysis.query.text);
184+
});
185+
186+
async function copyHistoryState() {
187+
fs.ensureDirSync(STORAGE_DIR);
188+
fs.copySync(
189+
path.join(__dirname, '../data/variant-analysis/'),
190+
path.join(tmpDir.name, 'variant-analysis')
191+
);
192+
193+
// also, replace the files with 'PLACEHOLDER' so that they have the correct directory
194+
for await (const p of walkDirectory(STORAGE_DIR)) {
195+
replacePlaceholder(path.join(p));
196+
}
197+
}
198+
199+
function replacePlaceholder(filePath: string) {
200+
if (filePath.endsWith('.json')) {
201+
const newContents = fs
202+
.readFileSync(filePath, 'utf8')
203+
.replaceAll('PLACEHOLDER', STORAGE_DIR.replaceAll('\\', '/'));
204+
fs.writeFileSync(filePath, newContents, 'utf8');
205+
}
206+
}
207+
208+
function deleteHistoryState() {
209+
fs.rmSync(STORAGE_DIR, {
210+
recursive: true,
211+
force: true,
212+
maxRetries: 10,
213+
retryDelay: 100
214+
});
215+
}
216+
});

0 commit comments

Comments
 (0)