Fix bug with reimporting test cases

When re-importing a test database, if the source archive for that
database is not in the workspace, then old source code will be seen when
inspected.

To reproduce:

1. Run a ql test file with a failure (I'm using a javascript DB).
2. Right click and import on the db that sticks around.
3. Change the JS source file for the test.
4. Re-run and still have a failure.
5. Re-import the database
6. Run the query under test
7. Click on a result item
8. **BUG:** The source code is old

The problem is that the source archive cache is not being flushed in
this case. I added a case to flush the source archive when the archive
was imported into the workspace as a folder, but not when the archive is
external.

The fix is to listen for database remove events in the archive
filesystem provider and flush the associated database source
archive.

There is a complication:

The database remove event didn't emit the removed database. This is
because the only place that consumed the event didn't need it.

To get around this, I changed the structure of the events. I added a
new `fullRefresh` boolean. If true, then the original database change
handler will ensure the entire tree is refreshed. If false, only the
selected database.
This commit is contained in:
Andrew Eisenberg
2024-05-17 23:10:56 +00:00
parent 233cb1cd41
commit 376d6b75d6
5 changed files with 48 additions and 26 deletions

View File

@@ -26,6 +26,8 @@ import {
// All path operations in this file must be on paths *within* the zip
// archive.
import { posix } from "path";
import { DatabaseEventKind } from "../../databases/local-databases/database-events";
import type { DatabaseManager } from "../../databases/local-databases/database-manager";
const path = posix;
@@ -242,15 +244,10 @@ export class ArchiveFileSystemProvider implements FileSystemProvider {
root = new Directory("");
constructor() {
// When a file system archive is removed from the workspace, we should
// also remove it from our cache.
workspace.onDidChangeWorkspaceFolders((event) => {
for (const removed of event.removed) {
const zipPath = removed.uri.fsPath;
this.archives.delete(zipPath);
}
});
constructor() {}
flushCache(zipPath: string) {
this.archives.delete(zipPath);
}
// metadata
@@ -366,15 +363,33 @@ export class ArchiveFileSystemProvider implements FileSystemProvider {
*/
export const zipArchiveScheme = "codeql-zip-archive";
export function activate(ctx: ExtensionContext) {
export function activate(ctx: ExtensionContext, dbm: DatabaseManager) {
const afsp = new ArchiveFileSystemProvider();
ctx.subscriptions.push(
workspace.registerFileSystemProvider(
zipArchiveScheme,
new ArchiveFileSystemProvider(),
{
isCaseSensitive: true,
isReadonly: true,
},
),
dbm.onDidChangeDatabaseItem(async ({ kind, item: db }) => {
if (kind === DatabaseEventKind.Remove) {
if (db?.sourceArchive) {
afsp.flushCache(db.sourceArchive.fsPath);
}
}
}),
);
ctx.subscriptions.push(
// When a file system archive is removed from the workspace, we should
// also remove it from our cache.
workspace.onDidChangeWorkspaceFolders((event) => {
for (const removed of event.removed) {
const zipPath = removed.uri.fsPath;
afsp.flushCache(zipPath);
}
}),
);
ctx.subscriptions.push(
workspace.registerFileSystemProvider(zipArchiveScheme, afsp, {
isCaseSensitive: true,
isReadonly: true,
}),
);
}

View File

@@ -109,9 +109,8 @@ class DatabaseTreeDataProvider
// Note that events from the database manager are instances of DatabaseChangedEvent
// and events fired by the UI are instances of DatabaseItem
// When event.item is undefined, then the entire tree is refreshed.
// When event.item is a db item, then only that item is refreshed.
this._onDidChangeTreeData.fire(event.item);
// When a full refresh has occurred, then all items are refreshed by passing undefined.
this._onDidChangeTreeData.fire(event.fullRefresh ? undefined : event.item);
}
private handleDidChangeCurrentDatabaseItem(

View File

@@ -16,4 +16,8 @@ export enum DatabaseEventKind {
export interface DatabaseChangedEvent {
kind: DatabaseEventKind;
item: DatabaseItem | undefined;
// If true, event handlers should consider the database manager
// to have been fully refreshed. Any state managed by the
/// event handler shouuld be fully refreshed as well.
fullRefresh: boolean;
}

View File

@@ -613,6 +613,7 @@ export class DatabaseManager extends DisposableObject {
this._onDidChangeCurrentDatabaseItem.fire({
item,
kind: DatabaseEventKind.Change,
fullRefresh: false,
});
}
}
@@ -662,8 +663,9 @@ export class DatabaseManager extends DisposableObject {
}
// note that we use undefined as the item in order to reset the entire tree
this._onDidChangeDatabaseItem.fire({
item: undefined,
item,
kind: DatabaseEventKind.Add,
fullRefresh: true,
});
}
@@ -671,9 +673,9 @@ export class DatabaseManager extends DisposableObject {
item.name = newName;
await this.updatePersistedDatabaseList();
this._onDidChangeDatabaseItem.fire({
// pass undefined so that the entire tree is rebuilt in order to re-sort
item: undefined,
item,
kind: DatabaseEventKind.Rename,
fullRefresh: true,
});
}
@@ -722,8 +724,9 @@ export class DatabaseManager extends DisposableObject {
// note that we use undefined as the item in order to reset the entire tree
this._onDidChangeDatabaseItem.fire({
item: undefined,
item,
kind: DatabaseEventKind.Remove,
fullRefresh: true,
});
}
@@ -776,6 +779,7 @@ export class DatabaseManager extends DisposableObject {
this._onDidChangeDatabaseItem.fire({
kind: DatabaseEventKind.Refresh,
item: databaseItem,
fullRefresh: false,
});
}
}

View File

@@ -947,7 +947,7 @@ async function activateWithInstalledDistribution(
ctx.subscriptions.push(compareView);
void extLogger.log("Initializing source archive filesystem provider.");
archiveFilesystemProvider_activate(ctx);
archiveFilesystemProvider_activate(ctx, dbm);
const qhelpTmpDir = dirSync({
prefix: "qhelp_",