Use filtering queries to do batched AI quering.
This commit is contained in:
@@ -17,6 +17,10 @@ import { redactableError } from "../common/errors";
|
|||||||
import { interpretResultsSarif } from "../query-results";
|
import { interpretResultsSarif } from "../query-results";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { assertNever } from "../common/helpers-pure";
|
import { assertNever } from "../common/helpers-pure";
|
||||||
|
import { dir } from "tmp-promise";
|
||||||
|
import { writeFile, outputFile } from "fs-extra";
|
||||||
|
import { dump as dumpYaml } from "js-yaml";
|
||||||
|
import { MethodSignature } from "./external-api-usage";
|
||||||
|
|
||||||
type AutoModelQueryOptions = {
|
type AutoModelQueryOptions = {
|
||||||
queryTag: string;
|
queryTag: string;
|
||||||
@@ -26,6 +30,7 @@ type AutoModelQueryOptions = {
|
|||||||
databaseItem: DatabaseItem;
|
databaseItem: DatabaseItem;
|
||||||
qlpack: QlPacksForLanguage;
|
qlpack: QlPacksForLanguage;
|
||||||
sourceInfo: SourceInfo | undefined;
|
sourceInfo: SourceInfo | undefined;
|
||||||
|
additionalPacks: string[];
|
||||||
extensionPacks: string[];
|
extensionPacks: string[];
|
||||||
queryStorageDir: string;
|
queryStorageDir: string;
|
||||||
|
|
||||||
@@ -52,6 +57,7 @@ async function runAutoModelQuery({
|
|||||||
databaseItem,
|
databaseItem,
|
||||||
qlpack,
|
qlpack,
|
||||||
sourceInfo,
|
sourceInfo,
|
||||||
|
additionalPacks,
|
||||||
extensionPacks,
|
extensionPacks,
|
||||||
queryStorageDir,
|
queryStorageDir,
|
||||||
progress,
|
progress,
|
||||||
@@ -99,7 +105,7 @@ async function runAutoModelQuery({
|
|||||||
quickEvalCountOnly: false,
|
quickEvalCountOnly: false,
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
getOnDiskWorkspaceFolders(),
|
additionalPacks,
|
||||||
extensionPacks,
|
extensionPacks,
|
||||||
queryStorageDir,
|
queryStorageDir,
|
||||||
undefined,
|
undefined,
|
||||||
@@ -147,6 +153,7 @@ async function runAutoModelQuery({
|
|||||||
|
|
||||||
type AutoModelQueriesOptions = {
|
type AutoModelQueriesOptions = {
|
||||||
mode: Mode;
|
mode: Mode;
|
||||||
|
candidateMethods: MethodSignature[];
|
||||||
cliServer: CodeQLCliServer;
|
cliServer: CodeQLCliServer;
|
||||||
queryRunner: QueryRunner;
|
queryRunner: QueryRunner;
|
||||||
databaseItem: DatabaseItem;
|
databaseItem: DatabaseItem;
|
||||||
@@ -161,6 +168,7 @@ export type AutoModelQueriesResult = {
|
|||||||
|
|
||||||
export async function runAutoModelQueries({
|
export async function runAutoModelQueries({
|
||||||
mode,
|
mode,
|
||||||
|
candidateMethods,
|
||||||
cliServer,
|
cliServer,
|
||||||
queryRunner,
|
queryRunner,
|
||||||
databaseItem,
|
databaseItem,
|
||||||
@@ -189,7 +197,13 @@ export async function runAutoModelQueries({
|
|||||||
sourceLocationPrefix,
|
sourceLocationPrefix,
|
||||||
};
|
};
|
||||||
|
|
||||||
const additionalPacks = getOnDiskWorkspaceFolders();
|
// Generate a pack containing the candidate filters
|
||||||
|
const filterPackDir = await generateCandidateFilterPack(
|
||||||
|
databaseItem.language,
|
||||||
|
candidateMethods,
|
||||||
|
);
|
||||||
|
|
||||||
|
const additionalPacks = [...getOnDiskWorkspaceFolders(), filterPackDir];
|
||||||
const extensionPacks = Object.keys(
|
const extensionPacks = Object.keys(
|
||||||
await cliServer.resolveQlpacks(additionalPacks, true),
|
await cliServer.resolveQlpacks(additionalPacks, true),
|
||||||
);
|
);
|
||||||
@@ -208,6 +222,7 @@ export async function runAutoModelQueries({
|
|||||||
databaseItem,
|
databaseItem,
|
||||||
qlpack,
|
qlpack,
|
||||||
sourceInfo,
|
sourceInfo,
|
||||||
|
additionalPacks,
|
||||||
extensionPacks,
|
extensionPacks,
|
||||||
queryStorageDir,
|
queryStorageDir,
|
||||||
progress: (update) => {
|
progress: (update) => {
|
||||||
@@ -228,3 +243,59 @@ export async function runAutoModelQueries({
|
|||||||
candidates,
|
candidates,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* generateCandidateFilterPack will create a temporary extension pack.
|
||||||
|
* This pack will contain a filter that will restrict the automodel queries
|
||||||
|
* to the specified candidate methods only.
|
||||||
|
* This is done using the `extensible` predicate "automodelCandidateFilter".
|
||||||
|
* @param language
|
||||||
|
* @param candidateMethods
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export async function generateCandidateFilterPack(
|
||||||
|
language: string,
|
||||||
|
candidateMethods: MethodSignature[],
|
||||||
|
): Promise<string> {
|
||||||
|
// Pack resides in a temporary directory, to not pollute the workspace.
|
||||||
|
const packDir = (await dir({ unsafeCleanup: true })).path;
|
||||||
|
|
||||||
|
const syntheticConfigPack = {
|
||||||
|
name: "codeql/automodel-filter",
|
||||||
|
version: "0.0.0",
|
||||||
|
library: true,
|
||||||
|
extensionTargets: {
|
||||||
|
[`codeql/${language}-all`]: "*",
|
||||||
|
},
|
||||||
|
dataExtensions: ["filter.yml"],
|
||||||
|
};
|
||||||
|
|
||||||
|
const qlpackFile = join(packDir, "codeql-pack.yml");
|
||||||
|
await outputFile(qlpackFile, dumpYaml(syntheticConfigPack), "utf8");
|
||||||
|
|
||||||
|
// The predicate has the following defintion:
|
||||||
|
// extensible predicate automodelCandidateFilter(string package, string type, string name, string signature)
|
||||||
|
const dataRows = candidateMethods.map((method) => [
|
||||||
|
method.packageName,
|
||||||
|
method.typeName,
|
||||||
|
method.methodName,
|
||||||
|
method.methodParameters,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const filter = {
|
||||||
|
extensions: [
|
||||||
|
{
|
||||||
|
addsTo: {
|
||||||
|
pack: `codeql/${language}-queries`,
|
||||||
|
extensible: "automodelCandidateFilter",
|
||||||
|
},
|
||||||
|
data: dataRows,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterFile = join(packDir, "filter.yml");
|
||||||
|
await writeFile(filterFile, dumpYaml(filter), "utf8");
|
||||||
|
|
||||||
|
return packDir;
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,63 @@ import { AutoModelQueriesResult } from "./auto-model-codeml-queries";
|
|||||||
import { assertNever } from "../common/helpers-pure";
|
import { assertNever } from "../common/helpers-pure";
|
||||||
import * as Sarif from "sarif";
|
import * as Sarif from "sarif";
|
||||||
import { gzipEncode } from "../common/zlib";
|
import { gzipEncode } from "../common/zlib";
|
||||||
|
import { ExternalApiUsage, MethodSignature } from "./external-api-usage";
|
||||||
|
import { ModeledMethod } from "./modeled-method";
|
||||||
|
import { groupMethods, sortGroupNames, sortMethods } from "./shared/sorting";
|
||||||
|
|
||||||
|
// Soft limit on the number of candidates to send to the model.
|
||||||
|
// Note that the model may return fewer than this number of candidates.
|
||||||
|
const candidateLimit = 20;
|
||||||
|
/**
|
||||||
|
* Return the candidates that the model should be run on. This includes limiting the number of
|
||||||
|
* candidates to the candidate limit and filtering out anything that is already modeled and respecting
|
||||||
|
* the order in the UI.
|
||||||
|
* @param mode Whether it is application or framework mode.
|
||||||
|
* @param externalApiUsages all external API usages.
|
||||||
|
* @param modeledMethods the currently modeled methods.
|
||||||
|
* @returns list of modeled methods that are candidates for modeling.
|
||||||
|
*/
|
||||||
|
export function getCandidates(
|
||||||
|
mode: Mode,
|
||||||
|
externalApiUsages: ExternalApiUsage[],
|
||||||
|
modeledMethods: Record<string, ModeledMethod>,
|
||||||
|
): MethodSignature[] {
|
||||||
|
// Sort the same way as the UI so we send the first ones listed in the UI first
|
||||||
|
const grouped = groupMethods(externalApiUsages, mode);
|
||||||
|
const sortedGroupNames = sortGroupNames(grouped);
|
||||||
|
const sortedExternalApiUsages = sortedGroupNames.flatMap((name) =>
|
||||||
|
sortMethods(grouped[name]),
|
||||||
|
);
|
||||||
|
|
||||||
|
const candidates: MethodSignature[] = [];
|
||||||
|
|
||||||
|
for (const externalApiUsage of sortedExternalApiUsages) {
|
||||||
|
const modeledMethod: ModeledMethod = modeledMethods[
|
||||||
|
externalApiUsage.signature
|
||||||
|
] ?? {
|
||||||
|
type: "none",
|
||||||
|
};
|
||||||
|
|
||||||
|
// If we have reached the max number of candidates then stop
|
||||||
|
if (candidates.length >= candidateLimit) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anything that is modeled is not a candidate
|
||||||
|
if (modeledMethod.type !== "none") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A method that is supported is modeled outside of the model file, so it is not a candidate.
|
||||||
|
if (externalApiUsage.supported) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The rest are candidates
|
||||||
|
candidates.push(externalApiUsage);
|
||||||
|
}
|
||||||
|
return candidates;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode a SARIF log to the format expected by the server: JSON, GZIP-compressed, base64-encoded
|
* Encode a SARIF log to the format expected by the server: JSON, GZIP-compressed, base64-encoded
|
||||||
|
|||||||
@@ -56,9 +56,10 @@ import { join } from "path";
|
|||||||
import { pickExtensionPack } from "./extension-pack-picker";
|
import { pickExtensionPack } from "./extension-pack-picker";
|
||||||
import { getLanguageDisplayName } from "../common/query-language";
|
import { getLanguageDisplayName } from "../common/query-language";
|
||||||
import { runAutoModelQueries } from "./auto-model-codeml-queries";
|
import { runAutoModelQueries } from "./auto-model-codeml-queries";
|
||||||
import { createAutoModelV2Request } from "./auto-model-v2";
|
import { createAutoModelV2Request, getCandidates } from "./auto-model-v2";
|
||||||
import { load as loadYaml } from "js-yaml";
|
import { load as loadYaml } from "js-yaml";
|
||||||
import { loadDataExtensionYaml } from "./yaml";
|
import { loadDataExtensionYaml } from "./yaml";
|
||||||
|
import { extLogger } from "../common/logging/vscode";
|
||||||
|
|
||||||
export class DataExtensionsEditorView extends AbstractWebview<
|
export class DataExtensionsEditorView extends AbstractWebview<
|
||||||
ToDataExtensionsEditorMessage,
|
ToDataExtensionsEditorMessage,
|
||||||
@@ -380,8 +381,22 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
|||||||
let predictedModeledMethods: Record<string, ModeledMethod>;
|
let predictedModeledMethods: Record<string, ModeledMethod>;
|
||||||
|
|
||||||
if (useLlmGenerationV2()) {
|
if (useLlmGenerationV2()) {
|
||||||
|
// Fetch the candidates to send to the model
|
||||||
|
const candidateMethods = getCandidates(
|
||||||
|
this.mode,
|
||||||
|
externalApiUsages,
|
||||||
|
modeledMethods,
|
||||||
|
);
|
||||||
|
|
||||||
|
// If there are no candidates, there is nothing to model and we just return
|
||||||
|
if (candidateMethods.length === 0) {
|
||||||
|
void extLogger.log("No candidates to model. Stopping.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const usages = await runAutoModelQueries({
|
const usages = await runAutoModelQueries({
|
||||||
mode: this.mode,
|
mode: this.mode,
|
||||||
|
candidateMethods,
|
||||||
cliServer: this.cliServer,
|
cliServer: this.cliServer,
|
||||||
queryRunner: this.queryRunner,
|
queryRunner: this.queryRunner,
|
||||||
queryStorageDir: this.queryStorageDir,
|
queryStorageDir: this.queryStorageDir,
|
||||||
@@ -421,12 +436,33 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
|||||||
filename: "auto-model.yml",
|
filename: "auto-model.yml",
|
||||||
});
|
});
|
||||||
|
|
||||||
const modeledMethods = loadDataExtensionYaml(models);
|
const loadedMethods = loadDataExtensionYaml(models);
|
||||||
if (!modeledMethods) {
|
if (!loadedMethods) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
predictedModeledMethods = modeledMethods;
|
// Any candidate that was part of the response is a negative result
|
||||||
|
// meaning that the canidate is not a sink for the kinds that the LLM is checking for.
|
||||||
|
// For now we model this as a sink neutral method, however this is subject
|
||||||
|
// to discussion.
|
||||||
|
for (const candidate of candidateMethods) {
|
||||||
|
if (!(candidate.signature in loadedMethods)) {
|
||||||
|
loadedMethods[candidate.signature] = {
|
||||||
|
type: "neutral",
|
||||||
|
kind: "sink",
|
||||||
|
input: "",
|
||||||
|
output: "",
|
||||||
|
provenance: "ai-generated",
|
||||||
|
signature: candidate.signature,
|
||||||
|
packageName: candidate.packageName,
|
||||||
|
typeName: candidate.typeName,
|
||||||
|
methodName: candidate.methodName,
|
||||||
|
methodParameters: candidate.methodParameters,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
predictedModeledMethods = loadedMethods;
|
||||||
} else {
|
} else {
|
||||||
const usages = await getAutoModelUsages({
|
const usages = await getAutoModelUsages({
|
||||||
cliServer: this.cliServer,
|
cliServer: this.cliServer,
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import {
|
import {
|
||||||
createAutoModelV2Request,
|
createAutoModelV2Request,
|
||||||
encodeSarif,
|
encodeSarif,
|
||||||
|
getCandidates,
|
||||||
} from "../../../src/data-extensions-editor/auto-model-v2";
|
} from "../../../src/data-extensions-editor/auto-model-v2";
|
||||||
import { Mode } from "../../../src/data-extensions-editor/shared/mode";
|
import { Mode } from "../../../src/data-extensions-editor/shared/mode";
|
||||||
import { AutomodelMode } from "../../../src/data-extensions-editor/auto-model-api-v2";
|
import { AutomodelMode } from "../../../src/data-extensions-editor/auto-model-api-v2";
|
||||||
import { AutoModelQueriesResult } from "../../../src/data-extensions-editor/auto-model-codeml-queries";
|
import { AutoModelQueriesResult } from "../../../src/data-extensions-editor/auto-model-codeml-queries";
|
||||||
import * as sarif from "sarif";
|
import * as sarif from "sarif";
|
||||||
import { gzipDecode } from "../../../src/common/zlib";
|
import { gzipDecode } from "../../../src/common/zlib";
|
||||||
|
import { ExternalApiUsage } from "../../../src/data-extensions-editor/external-api-usage";
|
||||||
|
import { ModeledMethod } from "../../../src/data-extensions-editor/modeled-method";
|
||||||
|
|
||||||
describe("createAutoModelV2Request", () => {
|
describe("createAutoModelV2Request", () => {
|
||||||
const createSarifLog = (queryId: string): sarif.Log => {
|
const createSarifLog = (queryId: string): sarif.Log => {
|
||||||
@@ -80,3 +83,106 @@ describe("createAutoModelV2Request", () => {
|
|||||||
expect(parsed).toEqual(result.candidates);
|
expect(parsed).toEqual(result.candidates);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("getCandidates", () => {
|
||||||
|
it("doesnt return methods that are already modelled", () => {
|
||||||
|
const externalApiUsages: ExternalApiUsage[] = [];
|
||||||
|
externalApiUsages.push({
|
||||||
|
library: "my.jar",
|
||||||
|
signature: "org.my.A#x()",
|
||||||
|
packageName: "org.my",
|
||||||
|
typeName: "A",
|
||||||
|
methodName: "x",
|
||||||
|
methodParameters: "()",
|
||||||
|
supported: false,
|
||||||
|
supportedType: "none",
|
||||||
|
usages: [],
|
||||||
|
});
|
||||||
|
const modeledMethods: Record<string, ModeledMethod> = {
|
||||||
|
"org.my.A#x()": {
|
||||||
|
type: "neutral",
|
||||||
|
kind: "",
|
||||||
|
input: "",
|
||||||
|
output: "",
|
||||||
|
provenance: "manual",
|
||||||
|
signature: "org.my.A#x()",
|
||||||
|
packageName: "org.my",
|
||||||
|
typeName: "A",
|
||||||
|
methodName: "x",
|
||||||
|
methodParameters: "()",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const candidates = getCandidates(
|
||||||
|
Mode.Application,
|
||||||
|
externalApiUsages,
|
||||||
|
modeledMethods,
|
||||||
|
);
|
||||||
|
expect(candidates.length).toEqual(0);
|
||||||
|
});
|
||||||
|
it("doesnt return methods that are supported from other sources", () => {
|
||||||
|
const externalApiUsages: ExternalApiUsage[] = [];
|
||||||
|
externalApiUsages.push({
|
||||||
|
library: "my.jar",
|
||||||
|
signature: "org.my.A#x()",
|
||||||
|
packageName: "org.my",
|
||||||
|
typeName: "A",
|
||||||
|
methodName: "x",
|
||||||
|
methodParameters: "()",
|
||||||
|
supported: true,
|
||||||
|
supportedType: "none",
|
||||||
|
usages: [],
|
||||||
|
});
|
||||||
|
const modeledMethods = {};
|
||||||
|
const candidates = getCandidates(
|
||||||
|
Mode.Application,
|
||||||
|
externalApiUsages,
|
||||||
|
modeledMethods,
|
||||||
|
);
|
||||||
|
expect(candidates.length).toEqual(0);
|
||||||
|
});
|
||||||
|
it("return methods that neither modeled nor supported from other sources", () => {
|
||||||
|
const externalApiUsages: ExternalApiUsage[] = [];
|
||||||
|
externalApiUsages.push({
|
||||||
|
library: "my.jar",
|
||||||
|
signature: "org.my.A#x()",
|
||||||
|
packageName: "org.my",
|
||||||
|
typeName: "A",
|
||||||
|
methodName: "x",
|
||||||
|
methodParameters: "()",
|
||||||
|
supported: false,
|
||||||
|
supportedType: "none",
|
||||||
|
usages: [],
|
||||||
|
});
|
||||||
|
const modeledMethods = {};
|
||||||
|
const candidates = getCandidates(
|
||||||
|
Mode.Application,
|
||||||
|
externalApiUsages,
|
||||||
|
modeledMethods,
|
||||||
|
);
|
||||||
|
expect(candidates.length).toEqual(1);
|
||||||
|
});
|
||||||
|
it("respects the limit", () => {
|
||||||
|
const externalApiUsages: ExternalApiUsage[] = [];
|
||||||
|
for (let i = 0; i < 30; i++) {
|
||||||
|
externalApiUsages.push({
|
||||||
|
library: "my.jar",
|
||||||
|
signature: `org.my.A#x${i}()`,
|
||||||
|
|
||||||
|
packageName: "org.my",
|
||||||
|
typeName: "A",
|
||||||
|
methodName: `x${i}`,
|
||||||
|
methodParameters: "()",
|
||||||
|
supported: false,
|
||||||
|
supportedType: "none",
|
||||||
|
usages: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const modeledMethods = {};
|
||||||
|
const candidates = getCandidates(
|
||||||
|
Mode.Application,
|
||||||
|
externalApiUsages,
|
||||||
|
modeledMethods,
|
||||||
|
);
|
||||||
|
expect(candidates.length).toEqual(20);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -5,13 +5,20 @@ import {
|
|||||||
} from "../../../../src/databases/local-databases";
|
} from "../../../../src/databases/local-databases";
|
||||||
import { file } from "tmp-promise";
|
import { file } from "tmp-promise";
|
||||||
import { QueryResultType } from "../../../../src/query-server/new-messages";
|
import { QueryResultType } from "../../../../src/query-server/new-messages";
|
||||||
import { runAutoModelQueries } from "../../../../src/data-extensions-editor/auto-model-codeml-queries";
|
import {
|
||||||
|
generateCandidateFilterPack,
|
||||||
|
runAutoModelQueries,
|
||||||
|
} from "../../../../src/data-extensions-editor/auto-model-codeml-queries";
|
||||||
import { Mode } from "../../../../src/data-extensions-editor/shared/mode";
|
import { Mode } from "../../../../src/data-extensions-editor/shared/mode";
|
||||||
import { mockedObject, mockedUri } from "../../utils/mocking.helpers";
|
import { mockedObject, mockedUri } from "../../utils/mocking.helpers";
|
||||||
import { CodeQLCliServer } from "../../../../src/codeql-cli/cli";
|
import { CodeQLCliServer } from "../../../../src/codeql-cli/cli";
|
||||||
import { QueryRunner } from "../../../../src/query-server";
|
import { QueryRunner } from "../../../../src/query-server";
|
||||||
import * as queryResolver from "../../../../src/local-queries/query-resolver";
|
import * as queryResolver from "../../../../src/local-queries/query-resolver";
|
||||||
import * as standardQueries from "../../../../src/local-queries/standard-queries";
|
import * as standardQueries from "../../../../src/local-queries/standard-queries";
|
||||||
|
import { MethodSignature } from "../../../../src/data-extensions-editor/external-api-usage";
|
||||||
|
import { join } from "path";
|
||||||
|
import { exists, readFile } from "fs-extra";
|
||||||
|
import { load as loadYaml } from "js-yaml";
|
||||||
|
|
||||||
describe("runAutoModelQueries", () => {
|
describe("runAutoModelQueries", () => {
|
||||||
const qlpack = {
|
const qlpack = {
|
||||||
@@ -60,6 +67,7 @@ describe("runAutoModelQueries", () => {
|
|||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
mode: Mode.Application,
|
mode: Mode.Application,
|
||||||
|
candidateMethods: [],
|
||||||
cliServer: mockedObject<CodeQLCliServer>({
|
cliServer: mockedObject<CodeQLCliServer>({
|
||||||
resolveQlpacks: jest.fn().mockResolvedValue({
|
resolveQlpacks: jest.fn().mockResolvedValue({
|
||||||
"/a/b/c/my-extension-pack": {},
|
"/a/b/c/my-extension-pack": {},
|
||||||
@@ -140,7 +148,10 @@ describe("runAutoModelQueries", () => {
|
|||||||
expect(result).not.toBeUndefined();
|
expect(result).not.toBeUndefined();
|
||||||
|
|
||||||
expect(options.cliServer.resolveQlpacks).toHaveBeenCalledTimes(1);
|
expect(options.cliServer.resolveQlpacks).toHaveBeenCalledTimes(1);
|
||||||
expect(options.cliServer.resolveQlpacks).toHaveBeenCalledWith([], true);
|
expect(options.cliServer.resolveQlpacks).toHaveBeenCalledWith(
|
||||||
|
expect.arrayContaining([expect.stringContaining("tmp")]),
|
||||||
|
true,
|
||||||
|
);
|
||||||
expect(resolveQueriesSpy).toHaveBeenCalledTimes(1);
|
expect(resolveQueriesSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(resolveQueriesSpy).toHaveBeenCalledWith(
|
expect(resolveQueriesSpy).toHaveBeenCalledWith(
|
||||||
options.cliServer,
|
options.cliServer,
|
||||||
@@ -165,7 +176,7 @@ describe("runAutoModelQueries", () => {
|
|||||||
quickEvalCountOnly: false,
|
quickEvalCountOnly: false,
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
[],
|
expect.arrayContaining([expect.stringContaining("tmp")]),
|
||||||
["/a/b/c/my-extension-pack"],
|
["/a/b/c/my-extension-pack"],
|
||||||
"/tmp/queries",
|
"/tmp/queries",
|
||||||
undefined,
|
undefined,
|
||||||
@@ -173,3 +184,34 @@ describe("runAutoModelQueries", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("generateCandidateFilterPack", () => {
|
||||||
|
it("should create a temp pack containing the candidate filters", async () => {
|
||||||
|
const candidateMethods: MethodSignature[] = [
|
||||||
|
{
|
||||||
|
signature: "org.my.A#x()",
|
||||||
|
packageName: "org.my",
|
||||||
|
typeName: "A",
|
||||||
|
methodName: "x",
|
||||||
|
methodParameters: "()",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const packDir = await generateCandidateFilterPack("java", candidateMethods);
|
||||||
|
expect(packDir).not.toBeUndefined();
|
||||||
|
const qlpackFile = join(packDir, "codeql-pack.yml");
|
||||||
|
expect(await exists(qlpackFile)).toBe(true);
|
||||||
|
const filterFile = join(packDir, "filter.yml");
|
||||||
|
expect(await exists(filterFile)).toBe(true);
|
||||||
|
// Read the contents of filterFile and parse as yaml
|
||||||
|
const yaml = await loadYaml(await readFile(filterFile, "utf8"));
|
||||||
|
const extensions = yaml.extensions;
|
||||||
|
expect(extensions).toBeInstanceOf(Array);
|
||||||
|
expect(extensions).toHaveLength(1);
|
||||||
|
const extension = extensions[0];
|
||||||
|
expect(extension.addsTo.pack).toEqual("codeql/java-queries");
|
||||||
|
expect(extension.addsTo.extensible).toEqual("automodelCandidateFilter");
|
||||||
|
expect(extension.data).toBeInstanceOf(Array);
|
||||||
|
expect(extension.data).toHaveLength(1);
|
||||||
|
expect(extension.data[0]).toEqual(["org.my", "A", "x", "()"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user