|
1 | 1 | import * as child_process from "child_process"; |
2 | 2 | import * as path from "path"; |
| 3 | +import * as os from "os"; |
| 4 | + |
| 5 | + |
| 6 | + |
| 7 | +function invoke(invocation: string[], options: {cwd?: string, log_prefix?: string} = {}) : number { |
| 8 | + const log_prefix = options.log_prefix && options.log_prefix !== "" ? `${options.log_prefix} ` : ""; |
| 9 | + console.log(`${process.env["CMD_BEGIN"] || ""}${log_prefix}${invocation.join(" ")}${process.env["CMD_END"] || ""}`); |
| 10 | + try { |
| 11 | + child_process.execFileSync(invocation[0], invocation.slice(1), { stdio: "inherit", cwd: options.cwd }); |
| 12 | + } catch (error) { |
| 13 | + return 1; |
| 14 | + } |
| 15 | + return 0; |
| 16 | +} |
| 17 | + |
| 18 | +type Args = { |
| 19 | + tests: string[]; |
| 20 | + flags: string[]; |
| 21 | + env: string[]; |
| 22 | + build: boolean; |
| 23 | + testing_level: number; |
| 24 | +}; |
| 25 | + |
| 26 | +function parseArgs(args: Args, argv: string) { |
| 27 | + argv |
| 28 | + .split(/(?<!\\) /) |
| 29 | + .forEach((arg) => { |
| 30 | + if (arg === "--no-build") { |
| 31 | + args.build = false; |
| 32 | + } else if (arg.startsWith("-")) { |
| 33 | + args.flags.push(arg); |
| 34 | + } else if (/^[A-Z_][A-Z_0-9]*=.*$/.test(arg)) { |
| 35 | + args.env.push(arg); |
| 36 | + } else if (/^\++$/.test(arg)) { |
| 37 | + args.testing_level = Math.max(args.testing_level, arg.length); |
| 38 | + } else if (arg !== "") { |
| 39 | + args.tests.push(arg); |
| 40 | + } |
| 41 | + }); |
| 42 | +} |
| 43 | + |
3 | 44 |
|
4 | 45 | function codeqlTestRun(argv: string[]): number { |
5 | | - const [language, args, ...plus] = argv; |
| 46 | + const [language, extra_args, ...plus] = argv; |
6 | 47 | let codeql = |
7 | 48 | process.env["SEMMLE_CODE"] ? |
8 | 49 | path.join(process.env["SEMMLE_CODE"], "target", "intree", `codeql-${language}`, "codeql") |
9 | 50 | : |
10 | 51 | "codeql" |
11 | 52 | ; |
12 | | - process.env["CODEQL_CONFIG_FILE"] ||= "." // disable the default implicit config file, but keep an explicit one |
13 | | - let plus_options = plus.map(option => option.trim().split("\n").filter(option => option !== "")); |
14 | | - let testing_level = 0; |
15 | | - let parsed_args = args.split(" ").filter(arg => { |
16 | | - if (arg === "") return false; |
17 | | - if (/^\++$/.test(arg)) { |
18 | | - testing_level = Math.max(testing_level, arg.length); |
19 | | - return false; |
20 | | - } |
21 | | - return true; |
22 | | - }); |
23 | | - if (parsed_args.every(arg => arg.startsWith("-"))) { |
24 | | - parsed_args.push("."); |
| 53 | + const ram_per_thread = process.platform === "linux" ? 3000 : 2048; |
| 54 | + const cpus = os.cpus().length; |
| 55 | + let args: Args = { |
| 56 | + tests: [], |
| 57 | + flags: [ |
| 58 | + `--ram=${ram_per_thread}`, |
| 59 | + `-j${cpus}`, |
| 60 | + ], |
| 61 | + env: [], |
| 62 | + build: true, |
| 63 | + testing_level: 0 |
| 64 | + }; |
| 65 | + parseArgs(args, extra_args); |
| 66 | + for (let i = 0; i < Math.min(plus.length, args.testing_level); i++) { |
| 67 | + parseArgs(args, plus[i]); |
25 | 68 | } |
26 | | - let invocation = [codeql, "test", "run", "-j0", ...parsed_args]; |
27 | | - for (let i = 0; i < Math.min(plus_options.length, testing_level); i++) { |
28 | | - invocation.push(...plus_options[i]); |
| 69 | + if (args.tests.length === 0) { |
| 70 | + args.tests.push("."); |
29 | 71 | } |
30 | | - console.log(`${process.env["CMD_BEGIN"] || ""}${invocation.join(" ")}${process.env["CMD_END"] || ""}`); |
31 | | - try { |
32 | | - child_process.execFileSync(invocation[0], invocation.slice(1), { stdio: "inherit" }); |
33 | | - } catch (error) { |
34 | | - return 1; |
| 72 | + if (args.build && process.env["SEMMLE_CODE"]) { |
| 73 | + // If SEMMLE_CODE is set, we are in the semmle-code repo, so we build the codeql binary. |
| 74 | + // Otherwise, we use codeql from PATH. |
| 75 | + if (invoke(["python3", "build", `target/intree/codeql-${language}`], {cwd: process.env["SEMMLE_CODE"]}) !== 0) { |
| 76 | + return 1; |
| 77 | + } |
35 | 78 | } |
36 | | - return 0; |
| 79 | + process.env["CODEQL_CONFIG_FILE"] ||= "." // disable the default implicit config file, but keep an explicit one |
| 80 | + // Set and unset environment variables |
| 81 | + args.env.forEach((envVar) => { |
| 82 | + const [key, value] = envVar.split("=", 2); |
| 83 | + if (key) { |
| 84 | + if (value === undefined) { |
| 85 | + delete process.env[key]; |
| 86 | + } else { |
| 87 | + process.env[key] = value; |
| 88 | + } |
| 89 | + } else { |
| 90 | + console.error(`Invalid environment variable assignment: ${envVar}`); |
| 91 | + process.exit(1); |
| 92 | + } |
| 93 | + }); |
| 94 | + return invoke([codeql, "test", "run", ...args.flags, "--", ...args.tests], {log_prefix: args.env.join(" ")}); |
37 | 95 | } |
38 | 96 |
|
39 | 97 | process.exit(codeqlTestRun(process.argv.slice(2))); |
0 commit comments