Extract query history scrubber tests
This commit is contained in:
@@ -1,9 +1,7 @@
|
|||||||
import { readdirSync, mkdirSync, writeFileSync } from "fs-extra";
|
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import * as vscode from "vscode";
|
import * as vscode from "vscode";
|
||||||
|
|
||||||
import { extLogger } from "../../../../src/common";
|
import { extLogger } from "../../../../src/common";
|
||||||
import { registerQueryHistoryScrubber } from "../../../../src/query-history/query-history-scrubber";
|
|
||||||
import { QueryHistoryManager } from "../../../../src/query-history/query-history-manager";
|
import { QueryHistoryManager } from "../../../../src/query-history/query-history-manager";
|
||||||
import {
|
import {
|
||||||
QueryHistoryConfig,
|
QueryHistoryConfig,
|
||||||
@@ -11,13 +9,6 @@ import {
|
|||||||
} from "../../../../src/config";
|
} from "../../../../src/config";
|
||||||
import { LocalQueryInfo } from "../../../../src/query-results";
|
import { LocalQueryInfo } from "../../../../src/query-results";
|
||||||
import { DatabaseManager } from "../../../../src/databases";
|
import { DatabaseManager } from "../../../../src/databases";
|
||||||
import { dirSync } from "tmp-promise";
|
|
||||||
import {
|
|
||||||
ONE_DAY_IN_MS,
|
|
||||||
ONE_HOUR_IN_MS,
|
|
||||||
THREE_HOURS_IN_MS,
|
|
||||||
TWO_HOURS_IN_MS,
|
|
||||||
} from "../../../../src/pure/time";
|
|
||||||
import { tmpDir } from "../../../../src/helpers";
|
import { tmpDir } from "../../../../src/helpers";
|
||||||
import { HistoryItemLabelProvider } from "../../../../src/query-history/history-item-label-provider";
|
import { HistoryItemLabelProvider } from "../../../../src/query-history/history-item-label-provider";
|
||||||
import { RemoteQueriesManager } from "../../../../src/remote-queries/remote-queries-manager";
|
import { RemoteQueriesManager } from "../../../../src/remote-queries/remote-queries-manager";
|
||||||
@@ -1468,189 +1459,6 @@ describe("QueryHistoryManager", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("query history scrubber", () => {
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
let deregister: vscode.Disposable | undefined;
|
|
||||||
let mockCtx: vscode.ExtensionContext;
|
|
||||||
let runCount = 0;
|
|
||||||
|
|
||||||
// We don't want our times to align exactly with the hour,
|
|
||||||
// so we can better mimic real life
|
|
||||||
const LESS_THAN_ONE_DAY = ONE_DAY_IN_MS - 1000;
|
|
||||||
const tmpDir = dirSync({
|
|
||||||
unsafeCleanup: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.useFakeTimers({
|
|
||||||
doNotFake: ["setTimeout"],
|
|
||||||
now,
|
|
||||||
});
|
|
||||||
|
|
||||||
mockCtx = {
|
|
||||||
globalState: {
|
|
||||||
lastScrubTime: now,
|
|
||||||
get(key: string) {
|
|
||||||
if (key !== "lastScrubTime") {
|
|
||||||
throw new Error(`Unexpected key: ${key}`);
|
|
||||||
}
|
|
||||||
return this.lastScrubTime;
|
|
||||||
},
|
|
||||||
async update(key: string, value: any) {
|
|
||||||
if (key !== "lastScrubTime") {
|
|
||||||
throw new Error(`Unexpected key: ${key}`);
|
|
||||||
}
|
|
||||||
this.lastScrubTime = value;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as any as vscode.ExtensionContext;
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
if (deregister) {
|
|
||||||
deregister.dispose();
|
|
||||||
deregister = undefined;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not throw an error when the query directory does not exist", async () => {
|
|
||||||
registerScrubber("idontexist");
|
|
||||||
|
|
||||||
jest.advanceTimersByTime(ONE_HOUR_IN_MS);
|
|
||||||
await wait();
|
|
||||||
// "Should not have called the scrubber"
|
|
||||||
expect(runCount).toBe(0);
|
|
||||||
|
|
||||||
jest.advanceTimersByTime(ONE_HOUR_IN_MS - 1);
|
|
||||||
await wait();
|
|
||||||
// "Should not have called the scrubber"
|
|
||||||
expect(runCount).toBe(0);
|
|
||||||
|
|
||||||
jest.advanceTimersByTime(1);
|
|
||||||
await wait();
|
|
||||||
// "Should have called the scrubber once"
|
|
||||||
expect(runCount).toBe(1);
|
|
||||||
|
|
||||||
jest.advanceTimersByTime(TWO_HOURS_IN_MS);
|
|
||||||
await wait();
|
|
||||||
// "Should have called the scrubber a second time"
|
|
||||||
expect(runCount).toBe(2);
|
|
||||||
|
|
||||||
expect((mockCtx.globalState as any).lastScrubTime).toBe(
|
|
||||||
now + TWO_HOURS_IN_MS * 2,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should scrub directories", async () => {
|
|
||||||
// create two query directories that are right around the cut off time
|
|
||||||
const queryDir = createMockQueryDir(
|
|
||||||
ONE_HOUR_IN_MS,
|
|
||||||
TWO_HOURS_IN_MS,
|
|
||||||
THREE_HOURS_IN_MS,
|
|
||||||
);
|
|
||||||
registerScrubber(queryDir);
|
|
||||||
|
|
||||||
jest.advanceTimersByTime(TWO_HOURS_IN_MS);
|
|
||||||
await wait();
|
|
||||||
|
|
||||||
// should have deleted only the invalid locations
|
|
||||||
expectDirectories(
|
|
||||||
queryDir,
|
|
||||||
toQueryDirName(ONE_HOUR_IN_MS),
|
|
||||||
toQueryDirName(TWO_HOURS_IN_MS),
|
|
||||||
toQueryDirName(THREE_HOURS_IN_MS),
|
|
||||||
);
|
|
||||||
|
|
||||||
jest.advanceTimersByTime(LESS_THAN_ONE_DAY);
|
|
||||||
await wait();
|
|
||||||
|
|
||||||
// nothing should have happened...yet
|
|
||||||
expectDirectories(
|
|
||||||
queryDir,
|
|
||||||
toQueryDirName(ONE_HOUR_IN_MS),
|
|
||||||
toQueryDirName(TWO_HOURS_IN_MS),
|
|
||||||
toQueryDirName(THREE_HOURS_IN_MS),
|
|
||||||
);
|
|
||||||
|
|
||||||
jest.advanceTimersByTime(1000);
|
|
||||||
await wait();
|
|
||||||
|
|
||||||
// should have deleted the two older directories
|
|
||||||
// even though they have different time stamps,
|
|
||||||
// they both expire during the same scrubbing period
|
|
||||||
expectDirectories(queryDir, toQueryDirName(THREE_HOURS_IN_MS));
|
|
||||||
|
|
||||||
// Wait until the next scrub time and the final directory is deleted
|
|
||||||
jest.advanceTimersByTime(TWO_HOURS_IN_MS);
|
|
||||||
await wait();
|
|
||||||
|
|
||||||
// should have deleted everything
|
|
||||||
expectDirectories(queryDir);
|
|
||||||
});
|
|
||||||
|
|
||||||
function expectDirectories(queryDir: string, ...dirNames: string[]) {
|
|
||||||
const files = readdirSync(queryDir);
|
|
||||||
expect(files.sort()).toEqual(dirNames.sort());
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMockQueryDir(...timestamps: number[]) {
|
|
||||||
const dir = tmpDir.name;
|
|
||||||
const queryDir = join(dir, "query");
|
|
||||||
// create qyuery directory and fill it with some query directories
|
|
||||||
mkdirSync(queryDir);
|
|
||||||
|
|
||||||
// create an invalid file
|
|
||||||
const invalidFile = join(queryDir, "invalid.txt");
|
|
||||||
writeFileSync(invalidFile, "invalid");
|
|
||||||
|
|
||||||
// create a directory without a timestamp file
|
|
||||||
const noTimestampDir = join(queryDir, "noTimestampDir");
|
|
||||||
mkdirSync(noTimestampDir);
|
|
||||||
writeFileSync(join(noTimestampDir, "invalid.txt"), "invalid");
|
|
||||||
|
|
||||||
// create a directory with a timestamp file, but is invalid
|
|
||||||
const invalidTimestampDir = join(queryDir, "invalidTimestampDir");
|
|
||||||
mkdirSync(invalidTimestampDir);
|
|
||||||
writeFileSync(join(invalidTimestampDir, "timestamp"), "invalid");
|
|
||||||
|
|
||||||
// create a directories with a valid timestamp files from the args
|
|
||||||
timestamps.forEach((timestamp) => {
|
|
||||||
const dir = join(queryDir, toQueryDirName(timestamp));
|
|
||||||
mkdirSync(dir);
|
|
||||||
writeFileSync(join(dir, "timestamp"), `${now + timestamp}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
return queryDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toQueryDirName(timestamp: number) {
|
|
||||||
return `query-${timestamp}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerScrubber(dir: string) {
|
|
||||||
deregister = registerQueryHistoryScrubber(
|
|
||||||
ONE_HOUR_IN_MS,
|
|
||||||
TWO_HOURS_IN_MS,
|
|
||||||
LESS_THAN_ONE_DAY,
|
|
||||||
dir,
|
|
||||||
{
|
|
||||||
removeDeletedQueries: () => {
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
} as QueryHistoryManager,
|
|
||||||
mockCtx,
|
|
||||||
{
|
|
||||||
increment: () => runCount++,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function wait(ms = 500) {
|
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function createMockQueryHistory(
|
async function createMockQueryHistory(
|
||||||
allHistory: QueryHistoryInfo[],
|
allHistory: QueryHistoryInfo[],
|
||||||
credentials?: Credentials,
|
credentials?: Credentials,
|
||||||
|
|||||||
@@ -0,0 +1,205 @@
|
|||||||
|
import { readdirSync, mkdirSync, writeFileSync } from "fs-extra";
|
||||||
|
import { join } from "path";
|
||||||
|
import * as vscode from "vscode";
|
||||||
|
|
||||||
|
import { extLogger } from "../../../../src/common";
|
||||||
|
import { registerQueryHistoryScrubber } from "../../../../src/query-history/query-history-scrubber";
|
||||||
|
import { QueryHistoryManager } from "../../../../src/query-history/query-history-manager";
|
||||||
|
import { dirSync } from "tmp-promise";
|
||||||
|
import {
|
||||||
|
ONE_DAY_IN_MS,
|
||||||
|
ONE_HOUR_IN_MS,
|
||||||
|
THREE_HOURS_IN_MS,
|
||||||
|
TWO_HOURS_IN_MS,
|
||||||
|
} from "../../../../src/pure/time";
|
||||||
|
|
||||||
|
describe("query history scrubber", () => {
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
let deregister: vscode.Disposable | undefined;
|
||||||
|
let mockCtx: vscode.ExtensionContext;
|
||||||
|
let runCount = 0;
|
||||||
|
|
||||||
|
// We don't want our times to align exactly with the hour,
|
||||||
|
// so we can better mimic real life
|
||||||
|
const LESS_THAN_ONE_DAY = ONE_DAY_IN_MS - 1000;
|
||||||
|
const tmpDir = dirSync({
|
||||||
|
unsafeCleanup: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
let queryHistoryManager: QueryHistoryManager;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.spyOn(extLogger, "log").mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
jest.useFakeTimers({
|
||||||
|
doNotFake: ["setTimeout"],
|
||||||
|
now,
|
||||||
|
});
|
||||||
|
|
||||||
|
mockCtx = {
|
||||||
|
globalState: {
|
||||||
|
lastScrubTime: now,
|
||||||
|
get(key: string) {
|
||||||
|
if (key !== "lastScrubTime") {
|
||||||
|
throw new Error(`Unexpected key: ${key}`);
|
||||||
|
}
|
||||||
|
return this.lastScrubTime;
|
||||||
|
},
|
||||||
|
async update(key: string, value: any) {
|
||||||
|
if (key !== "lastScrubTime") {
|
||||||
|
throw new Error(`Unexpected key: ${key}`);
|
||||||
|
}
|
||||||
|
this.lastScrubTime = value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as any as vscode.ExtensionContext;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
if (queryHistoryManager) {
|
||||||
|
queryHistoryManager.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deregister) {
|
||||||
|
deregister.dispose();
|
||||||
|
deregister = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not throw an error when the query directory does not exist", async () => {
|
||||||
|
registerScrubber("idontexist");
|
||||||
|
|
||||||
|
jest.advanceTimersByTime(ONE_HOUR_IN_MS);
|
||||||
|
await wait();
|
||||||
|
// "Should not have called the scrubber"
|
||||||
|
expect(runCount).toBe(0);
|
||||||
|
|
||||||
|
jest.advanceTimersByTime(ONE_HOUR_IN_MS - 1);
|
||||||
|
await wait();
|
||||||
|
// "Should not have called the scrubber"
|
||||||
|
expect(runCount).toBe(0);
|
||||||
|
|
||||||
|
jest.advanceTimersByTime(1);
|
||||||
|
await wait();
|
||||||
|
// "Should have called the scrubber once"
|
||||||
|
expect(runCount).toBe(1);
|
||||||
|
|
||||||
|
jest.advanceTimersByTime(TWO_HOURS_IN_MS);
|
||||||
|
await wait();
|
||||||
|
// "Should have called the scrubber a second time"
|
||||||
|
expect(runCount).toBe(2);
|
||||||
|
|
||||||
|
expect((mockCtx.globalState as any).lastScrubTime).toBe(
|
||||||
|
now + TWO_HOURS_IN_MS * 2,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should scrub directories", async () => {
|
||||||
|
// create two query directories that are right around the cut off time
|
||||||
|
const queryDir = createMockQueryDir(
|
||||||
|
ONE_HOUR_IN_MS,
|
||||||
|
TWO_HOURS_IN_MS,
|
||||||
|
THREE_HOURS_IN_MS,
|
||||||
|
);
|
||||||
|
registerScrubber(queryDir);
|
||||||
|
|
||||||
|
jest.advanceTimersByTime(TWO_HOURS_IN_MS);
|
||||||
|
await wait();
|
||||||
|
|
||||||
|
// should have deleted only the invalid locations
|
||||||
|
expectDirectories(
|
||||||
|
queryDir,
|
||||||
|
toQueryDirName(ONE_HOUR_IN_MS),
|
||||||
|
toQueryDirName(TWO_HOURS_IN_MS),
|
||||||
|
toQueryDirName(THREE_HOURS_IN_MS),
|
||||||
|
);
|
||||||
|
|
||||||
|
jest.advanceTimersByTime(LESS_THAN_ONE_DAY);
|
||||||
|
await wait();
|
||||||
|
|
||||||
|
// nothing should have happened...yet
|
||||||
|
expectDirectories(
|
||||||
|
queryDir,
|
||||||
|
toQueryDirName(ONE_HOUR_IN_MS),
|
||||||
|
toQueryDirName(TWO_HOURS_IN_MS),
|
||||||
|
toQueryDirName(THREE_HOURS_IN_MS),
|
||||||
|
);
|
||||||
|
|
||||||
|
jest.advanceTimersByTime(1000);
|
||||||
|
await wait();
|
||||||
|
|
||||||
|
// should have deleted the two older directories
|
||||||
|
// even though they have different time stamps,
|
||||||
|
// they both expire during the same scrubbing period
|
||||||
|
expectDirectories(queryDir, toQueryDirName(THREE_HOURS_IN_MS));
|
||||||
|
|
||||||
|
// Wait until the next scrub time and the final directory is deleted
|
||||||
|
jest.advanceTimersByTime(TWO_HOURS_IN_MS);
|
||||||
|
await wait();
|
||||||
|
|
||||||
|
// should have deleted everything
|
||||||
|
expectDirectories(queryDir);
|
||||||
|
});
|
||||||
|
|
||||||
|
function expectDirectories(queryDir: string, ...dirNames: string[]) {
|
||||||
|
const files = readdirSync(queryDir);
|
||||||
|
expect(files.sort()).toEqual(dirNames.sort());
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMockQueryDir(...timestamps: number[]) {
|
||||||
|
const dir = tmpDir.name;
|
||||||
|
const queryDir = join(dir, "query");
|
||||||
|
// create qyuery directory and fill it with some query directories
|
||||||
|
mkdirSync(queryDir);
|
||||||
|
|
||||||
|
// create an invalid file
|
||||||
|
const invalidFile = join(queryDir, "invalid.txt");
|
||||||
|
writeFileSync(invalidFile, "invalid");
|
||||||
|
|
||||||
|
// create a directory without a timestamp file
|
||||||
|
const noTimestampDir = join(queryDir, "noTimestampDir");
|
||||||
|
mkdirSync(noTimestampDir);
|
||||||
|
writeFileSync(join(noTimestampDir, "invalid.txt"), "invalid");
|
||||||
|
|
||||||
|
// create a directory with a timestamp file, but is invalid
|
||||||
|
const invalidTimestampDir = join(queryDir, "invalidTimestampDir");
|
||||||
|
mkdirSync(invalidTimestampDir);
|
||||||
|
writeFileSync(join(invalidTimestampDir, "timestamp"), "invalid");
|
||||||
|
|
||||||
|
// create a directories with a valid timestamp files from the args
|
||||||
|
timestamps.forEach((timestamp) => {
|
||||||
|
const dir = join(queryDir, toQueryDirName(timestamp));
|
||||||
|
mkdirSync(dir);
|
||||||
|
writeFileSync(join(dir, "timestamp"), `${now + timestamp}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
return queryDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toQueryDirName(timestamp: number) {
|
||||||
|
return `query-${timestamp}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerScrubber(dir: string) {
|
||||||
|
deregister = registerQueryHistoryScrubber(
|
||||||
|
ONE_HOUR_IN_MS,
|
||||||
|
TWO_HOURS_IN_MS,
|
||||||
|
LESS_THAN_ONE_DAY,
|
||||||
|
dir,
|
||||||
|
{
|
||||||
|
removeDeletedQueries: () => {
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
} as QueryHistoryManager,
|
||||||
|
mockCtx,
|
||||||
|
{
|
||||||
|
increment: () => runCount++,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function wait(ms = 500) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user