Add some new tests and fix some comments

This commit is contained in:
Andrew Eisenberg 2022-11-23 16:16:02 -08:00
parent f79028af27
commit ad7ca9bf21
22 changed files with 339 additions and 68 deletions

View file

@ -12,7 +12,12 @@ import * as configUtils from "./config-utils";
import { Feature } from "./feature-flags";
import { Language } from "./languages";
import { getRunnerLogger, Logger } from "./logging";
import { setupTests, createFeatures } from "./testing-utils";
import { parseRepositoryNwo } from "./repository";
import {
setupTests,
createFeatures,
mockLanguagesInRepo as mockLanguagesInRepo,
} from "./testing-utils";
import * as util from "./util";
setupTests(test);
@ -1761,7 +1766,10 @@ parseInputAndConfigMacro.title = (providedTitle: string) =>
const mockLogger = {
info: (message: string) => {
console.log(message);
console.log("info:", message);
},
debug: (message: string) => {
console.log("debug:", message);
},
} as Logger;
@ -2449,3 +2457,119 @@ test("downloadPacks-with-registries fails with invalid registries block", async
);
});
});
// getLanguages
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
// eslint-disable-next-line github/array-foreach
[
{
name: "languages from input",
codeqlResolvedLanguages: ["javascript", "java", "python"],
languagesInput: "jAvAscript, \n jaVa",
languagesInRepository: ["SwiFt", "other"],
expectedLanguages: ["javascript", "java"],
expectedApiCall: false,
},
{
name: "languages from github api",
codeqlResolvedLanguages: ["javascript", "java", "python"],
languagesInput: "",
languagesInRepository: [" jAvAscript\n \t", " jaVa", "SwiFt", "other"],
expectedLanguages: ["javascript", "java"],
expectedApiCall: true,
},
{
name: "aliases from input",
codeqlResolvedLanguages: ["javascript", "csharp", "cpp", "java", "python"],
languagesInput: " typEscript\n \t, C#, c , KoTlin",
languagesInRepository: ["SwiFt", "other"],
expectedLanguages: ["javascript", "csharp", "cpp", "java"],
expectedApiCall: false,
},
{
name: "duplicate languages from input",
codeqlResolvedLanguages: ["javascript", "java", "python"],
languagesInput: "jAvAscript, \n jaVa, kotlin, typescript",
languagesInRepository: ["SwiFt", "other"],
expectedLanguages: ["javascript", "java"],
expectedApiCall: false,
},
{
name: "aliases from github api",
codeqlResolvedLanguages: ["javascript", "csharp", "cpp", "java", "python"],
languagesInput: "",
languagesInRepository: [" typEscript\n \t", " C#", "c", "other"],
expectedLanguages: ["javascript", "csharp", "cpp"],
expectedApiCall: true,
},
].forEach((args) => {
test(`getLanguages: ${args.name}`, async (t) => {
const mockRequest = mockLanguagesInRepo(args.languagesInRepository);
const languages = args.codeqlResolvedLanguages.reduce(
(acc, lang) => ({
...acc,
[lang]: true,
}),
{}
);
const codeQL = setCodeQL({
resolveLanguages: () => Promise.resolve(languages),
});
const actualLanguages = await configUtils.getLanguages(
codeQL,
args.languagesInput,
mockRepositoryNwo,
mockLogger
);
t.deepEqual(actualLanguages.sort(), args.expectedLanguages.sort());
t.deepEqual(mockRequest.called, args.expectedApiCall);
});
});
// eslint-disable-next-line github/array-foreach
[
{
name: "no languages",
codeqlResolvedLanguages: ["javascript", "java", "python"],
languagesInput: "",
languagesInRepository: [],
expectedApiCall: true,
expectedError: configUtils.getNoLanguagesError(),
},
{
name: "unrecognized languages from input",
codeqlResolvedLanguages: ["javascript", "java", "python"],
languagesInput: "a, b, c, javascript",
languagesInRepository: [],
expectedApiCall: false,
expectedError: configUtils.getUnknownLanguagesError(["a", "b"]),
},
].forEach((args) => {
test(`getLanguages (error when empty): ${args.name}`, async (t) => {
const mockRequest = mockLanguagesInRepo(args.languagesInRepository);
const languages = args.codeqlResolvedLanguages.reduce(
(acc, lang) => ({
...acc,
[lang]: true,
}),
{}
);
const codeQL = setCodeQL({
resolveLanguages: () => Promise.resolve(languages),
});
await t.throwsAsync(
async () =>
await configUtils.getLanguages(
codeQL,
args.languagesInput,
mockRepositoryNwo,
mockLogger
),
{ message: args.expectedError }
);
t.deepEqual(mockRequest.called, args.expectedApiCall);
});
});

View file

@ -18,8 +18,8 @@ import { Feature, FeatureEnablement } from "./feature-flags";
import {
Language,
LanguageOrAlias,
LANGUAGE_ALIASES,
parseLanguage,
resolveAlias,
} from "./languages";
import { Logger } from "./logging";
import { RepositoryNwo } from "./repository";
@ -857,7 +857,8 @@ export function getUnknownLanguagesError(languages: string[]): string {
}
/**
* Gets the set of languages in the current repository
* Gets the set of languages in the current repository that are
* scannable by CodeQL.
*/
async function getLanguagesInRepo(
repository: RepositoryNwo,
@ -895,7 +896,7 @@ async function getLanguagesInRepo(
* If no languages could be detected from either the workflow or the repository
* then throw an error.
*/
async function getLanguages(
export async function getLanguages(
codeQL: CodeQL,
languagesInput: string | undefined,
repository: RepositoryNwo,
@ -908,13 +909,13 @@ async function getLanguages(
logger
);
let languages: string[];
let languages = rawLanguages.map(resolveAlias);
if (autodetected) {
const availableLanguages = await codeQL.resolveLanguages();
languages = rawLanguages.filter((value) => value in availableLanguages);
languages = languages.filter((value) => value in availableLanguages);
logger.info(`Automatically detected languages: ${languages.join(", ")}`);
} else {
languages = rawLanguages;
logger.info(`Languages from configuration: ${languages.join(", ")}`);
}
@ -928,15 +929,12 @@ async function getLanguages(
const parsedLanguages: Language[] = [];
const unknownLanguages: string[] = [];
for (const language of languages) {
const parsedLanguage = parseLanguage(language);
const dealiasedLanguage =
parsedLanguage && parsedLanguage in LANGUAGE_ALIASES
? LANGUAGE_ALIASES[parsedLanguage]
: (parsedLanguage as Language);
// We know this is not an alias since we resolved it above.
const parsedLanguage = parseLanguage(language) as Language;
if (parsedLanguage === undefined) {
unknownLanguages.push(language);
} else if (!parsedLanguages.includes(dealiasedLanguage)) {
parsedLanguages.push(dealiasedLanguage);
} else if (!parsedLanguages.includes(parsedLanguage)) {
parsedLanguages.push(parsedLanguage);
}
}
if (unknownLanguages.length > 0) {

View file

@ -13,12 +13,12 @@ export interface FeatureEnablement {
export enum Feature {
BypassToolcacheEnabled = "bypass_toolcache_enabled",
BypassToolcacheKotlinSwiftEnabled = "bypass_toolcache_kotlin_swift_enabled",
CliConfigFileEnabled = "cli_config_file_enabled",
DisableKotlinAnalysisEnabled = "disable_kotlin_analysis_enabled",
FileBaselineInformationEnabled = "file_baseline_information_enabled",
MlPoweredQueriesEnabled = "ml_powered_queries_enabled",
TrapCachingEnabled = "trap_caching_enabled",
BypassToolcacheKotlinSwiftEnabled = "bypass_toolcache_kotlin_switft_enabled",
}
export const featureConfig: Record<
@ -27,6 +27,14 @@ export const featureConfig: Record<
> = {
[Feature.BypassToolcacheEnabled]: {
envVar: "CODEQL_BYPASS_TOOLCACHE",
// Cannot specify a minimum version because this flag is checked before we have
// access to the CodeQL instance.
minimumVersion: undefined,
},
[Feature.BypassToolcacheKotlinSwiftEnabled]: {
envVar: "CODEQL_BYPASS_TOOLCACHE_KOTLIN_SWIFT",
// Cannot specify a minimum version because this flag is checked before we have
// access to the CodeQL instance.
minimumVersion: undefined,
},
[Feature.DisableKotlinAnalysisEnabled]: {
@ -49,10 +57,6 @@ export const featureConfig: Record<
envVar: "CODEQL_TRAP_CACHING",
minimumVersion: undefined,
},
[Feature.BypassToolcacheKotlinSwiftEnabled]: {
envVar: "CODEQL_BYPASS_TOOLCACHE_KOTLIN_SWIFT",
minimumVersion: undefined,
},
};
/**

View file

@ -20,11 +20,15 @@ test("parseLanguage", async (t) => {
t.deepEqual(parseLanguage("python"), Language.python);
// Aliases
t.deepEqual(parseLanguage("c"), Language.cpp);
t.deepEqual(parseLanguage("c++"), Language.cpp);
t.deepEqual(parseLanguage("c#"), Language.csharp);
t.deepEqual(parseLanguage("kotlin"), Language.java);
t.deepEqual(parseLanguage("typescript"), Language.javascript);
t.deepEqual(parseLanguage("c"), "c");
t.deepEqual(parseLanguage("c++"), "c++");
t.deepEqual(parseLanguage("c#"), "c#");
t.deepEqual(parseLanguage("kotlin"), "kotlin");
t.deepEqual(parseLanguage("typescript"), "typescript");
// spaces and case-insensitivity
t.deepEqual(parseLanguage(" \t\nCsHaRp\t\t"), Language.csharp);
t.deepEqual(parseLanguage(" \t\nkOtLin\t\t"), "kotlin");
// Not matches
t.deepEqual(parseLanguage("foo"), undefined);

View file

@ -23,17 +23,31 @@ export type LanguageOrAlias = Language | keyof typeof LANGUAGE_ALIASES;
export const KOTLIN_SWIFT_BYPASS = ["kotlin", "swift"];
// Translate from user input or GitHub's API names for languages to CodeQL's names for languages
export function resolveAlias(lang: LanguageOrAlias): Language {
return LANGUAGE_ALIASES[lang] || lang;
}
/**
* Translate from user input or GitHub's API names for languages to CodeQL's
* names for languages. This does not translate a language alias to the actual
* language used by CodeQL.
*
* @param language The language to translate.
* @returns A language supported by CodeQL, an alias for a language, or
* `undefined` if the input language cannot be parsed into a langauge supported
* by CodeQL.
*/
export function parseLanguage(language: string): LanguageOrAlias | undefined {
// Normalise to lower case
language = language.toLowerCase();
language = language.trim().toLowerCase();
// See if it's an exact match
if (language in Language) {
return language as Language;
}
// Check language aliases
// Check language aliases, but return the original language name,
// the alias will be resolved later.
if (language in LANGUAGE_ALIASES) {
return language;
}

View file

@ -162,7 +162,7 @@ export function mockFeatureFlagApiEndpoint(
sinon.stub(apiClient, "getApiClient").value(() => client);
}
export function mockLangaugesInRepo(languages: string[]) {
export function mockLanguagesInRepo(languages: string[]) {
const mockClient = sinon.stub(apiClient, "getApiClient");
const listLanguages = sinon.stub().resolves({
status: 200,

View file

@ -15,7 +15,7 @@ import { parseRepositoryNwo } from "./repository";
import {
createFeatures,
getRecordingLogger,
mockLangaugesInRepo,
mockLanguagesInRepo,
setupTests,
} from "./testing-utils";
import * as util from "./util";
@ -543,7 +543,7 @@ const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
},
].forEach((args) => {
test(`shouldBypassToolcache: ${args.name}`, async (t) => {
const mockRequest = mockLangaugesInRepo(args.languagesInRepository);
const mockRequest = mockLanguagesInRepo(args.languagesInRepository);
const mockLogger = getRecordingLogger([]);
const featureEnablement = createFeatures(args.features);
const codeqlUrl = args.hasCustomCodeQL ? "custom-codeql-url" : undefined;

View file

@ -870,7 +870,15 @@ export async function shouldBypassToolcache(
repository,
logger
);
return rawLanguages.some((lang) => KOTLIN_SWIFT_BYPASS.includes(lang));
const bypass = rawLanguages.some((lang) =>
KOTLIN_SWIFT_BYPASS.includes(lang)
);
if (bypass) {
logger.info(
`Bypassing toolcache for kotlin or swift. Languages: ${rawLanguages}`
);
}
return bypass;
}
return false;
}