Skip to content

Commit 68c6f0c

Browse files
authored
Script runner perf optimization (#473)
* Fix buidler runScript() '--inspect' flag so it can properly work with the debugger. * Script runner omit re-compiling with ts-node, unless requesting a TS script. * This way the run task will run much faster, the same goes for tests that otherwise can even timeout. * Fix script runner failing test, stop skipping on Windows, remove unneeded timeout. * Parallelize runScript() calls in scripts-runner tests for a faster execution time. * Add JSDocs explaining why enforce '--inspect' VS '--inspect-brk=' in runScript().
1 parent 7cebfb1 commit 68c6f0c

File tree

2 files changed

+143
-47
lines changed

2 files changed

+143
-47
lines changed

Diff for: packages/buidler-core/src/internal/util/scripts-runner.ts

+63-8
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ export async function runScript(
1616
const { fork } = await import("child_process");
1717

1818
return new Promise((resolve, reject) => {
19+
const processExecArgv = withFixedInspectArg(process.execArgv);
20+
1921
const nodeArgs = [
20-
...process.execArgv,
21-
...getTsNodeArgsIfNeeded(),
22+
...processExecArgv,
23+
...getTsNodeArgsIfNeeded(scriptPath),
2224
...extraNodeArgs
2325
];
2426

@@ -46,26 +48,79 @@ export async function runScriptWithBuidler(
4648
): Promise<number> {
4749
log(`Creating Buidler subprocess to run ${scriptPath}`);
4850

51+
const buidlerRegisterPath = resolveBuidlerRegisterPath();
52+
4953
return runScript(
5054
scriptPath,
5155
scriptArgs,
52-
[
53-
...extraNodeArgs,
54-
"--require",
55-
path.join(__dirname, "..", "..", "register")
56-
],
56+
[...extraNodeArgs, "--require", buidlerRegisterPath],
5757
{
5858
...getEnvVariablesMap(buidlerArguments),
5959
...extraEnvVars
6060
}
6161
);
6262
}
6363

64-
function getTsNodeArgsIfNeeded() {
64+
/**
65+
* Fix debugger "inspect" arg from process.argv, if present.
66+
*
67+
* When running this process with a debugger, a debugger port
68+
* is specified via the "--inspect-brk=" arg param in some IDEs/setups.
69+
*
70+
* This normally works, but if we do a fork afterwards, we'll get an error stating
71+
* that the port is already in use (since the fork would also use the same args,
72+
* therefore the same port number). To prevent this issue, we could replace the port number with
73+
* a different free one, or simply use the port-agnostic --inspect" flag, and leave the debugger
74+
* port selection to the Node process itself, which will pick an empty AND valid one.
75+
*
76+
* This way, we can properly use the debugger for this process AND for the executed
77+
* script itself - even if it's compiled using ts-node.
78+
*/
79+
function withFixedInspectArg(argv: string[]) {
80+
const fixIfInspectArg = (arg: string) => {
81+
if (arg.toLowerCase().includes("--inspect-brk=")) {
82+
return "--inspect";
83+
}
84+
return arg;
85+
};
86+
return argv.map(fixIfInspectArg);
87+
}
88+
89+
/**
90+
* Ensure buidler/register source file path is resolved to compiled JS file
91+
* instead of TS source file, so we don't need to run ts-node unnecessarily.
92+
*/
93+
export function resolveBuidlerRegisterPath() {
94+
const executionMode = getExecutionMode();
95+
const isCompiledInstallation = [
96+
ExecutionMode.EXECUTION_MODE_LOCAL_INSTALLATION,
97+
ExecutionMode.EXECUTION_MODE_GLOBAL_INSTALLATION,
98+
ExecutionMode.EXECUTION_MODE_LINKED
99+
].includes(executionMode);
100+
101+
const buidlerCoreBaseDir = path.join(__dirname, "..", "..");
102+
103+
const buidlerCoreCompiledDir = isCompiledInstallation
104+
? buidlerCoreBaseDir
105+
: path.join(buidlerCoreBaseDir, "..");
106+
107+
const buidlerCoreRegisterCompiledPath = path.join(
108+
buidlerCoreCompiledDir,
109+
"register"
110+
);
111+
112+
return buidlerCoreRegisterCompiledPath;
113+
}
114+
115+
function getTsNodeArgsIfNeeded(scriptPath: string) {
65116
if (getExecutionMode() !== ExecutionMode.EXECUTION_MODE_TS_NODE_TESTS) {
66117
return [];
67118
}
68119

120+
if (!/\.tsx?$/i.test(scriptPath)) {
121+
return [];
122+
}
123+
69124
const extraNodeArgs = [];
70125

71126
if (!process.execArgv.includes("ts-node/register")) {

Diff for: packages/buidler-core/test/internal/util/scripts-runner.ts

+80-39
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { assert } from "chai";
2-
import os from "os";
3-
import path from "path";
42

53
import {
4+
resolveBuidlerRegisterPath,
65
runScript,
76
runScriptWithBuidler
87
} from "../../../src/internal/util/scripts-runner";
@@ -13,12 +12,18 @@ describe("Scripts runner", function() {
1312
useFixtureProject("project-with-scripts");
1413

1514
it("Should pass params to the script", async function() {
16-
const statusCode = await runScript("./params-script.js", ["a", "b", "c"]);
17-
assert.equal(statusCode, 0);
15+
const [
16+
statusCodeWithScriptParams,
17+
statusCodeWithNoParams
18+
] = await Promise.all([
19+
runScript("./params-script.js", ["a", "b", "c"]),
20+
runScript("./params-script.js")
21+
]);
22+
23+
assert.equal(statusCodeWithScriptParams, 0);
1824

1925
// We check here that the script is correctly testing this:
20-
const statusCode2 = await runScript("./params-script.js");
21-
assert.notEqual(statusCode2, 0);
26+
assert.notEqual(statusCodeWithNoParams, 0);
2227
});
2328

2429
it("Should run the script to completion", async function() {
@@ -30,54 +35,90 @@ describe("Scripts runner", function() {
3035
});
3136

3237
it("Should resolve to the status code of the script run", async function() {
33-
this.timeout(35000);
34-
35-
if (os.type() === "Windows_NT") {
36-
this.skip();
37-
}
38-
39-
const statusCode1 = await runScript(
40-
"./async-script.js",
41-
[],
42-
["--require", path.join(__dirname, "..", "..", "..", "src", "register")]
38+
const buidlerRegisterPath = resolveBuidlerRegisterPath();
39+
40+
const extraNodeArgs = ["--require", buidlerRegisterPath];
41+
const scriptArgs: string[] = [];
42+
43+
const runScriptCases = [
44+
{
45+
scriptPath: "./async-script.js",
46+
extraNodeArgs,
47+
expectedStatusCode: 0
48+
},
49+
{
50+
scriptPath: "./failing-script.js",
51+
expectedStatusCode: 123
52+
},
53+
{
54+
scriptPath: "./successful-script.js",
55+
extraNodeArgs,
56+
expectedStatusCode: 0
57+
}
58+
];
59+
60+
const runScriptTestResults = await Promise.all(
61+
runScriptCases.map(
62+
async ({ scriptPath, extraNodeArgs: _extraNodeArgs }) => {
63+
const statusCode =
64+
_extraNodeArgs === undefined
65+
? await runScript(scriptPath)
66+
: await runScript(scriptPath, scriptArgs, _extraNodeArgs);
67+
return { scriptPath, statusCode };
68+
}
69+
)
4370
);
44-
assert.equal(statusCode1, 0);
45-
46-
const statusCode2 = await runScript("./failing-script.js");
47-
assert.equal(statusCode2, 123);
4871

49-
const statusCode3 = await runScript(
50-
"./successful-script.js",
51-
[],
52-
["--require", path.join(__dirname, "..", "..", "..", "src", "register")]
72+
const expectedResults = runScriptCases.map(
73+
({ expectedStatusCode, scriptPath }) => ({
74+
scriptPath,
75+
statusCode: expectedStatusCode
76+
})
5377
);
54-
assert.equal(statusCode3, 0);
78+
79+
assert.deepEqual(runScriptTestResults, expectedResults);
5580
});
5681

5782
it("Should pass env variables to the script", async function() {
58-
const statusCode = await runScript("./env-var-script.js", [], [], {
59-
TEST_ENV_VAR: "test"
60-
});
61-
assert.equal(statusCode, 0);
83+
const [statusCodeWithEnvVars, statusCodeWithNoEnvArgs] = await Promise.all([
84+
runScript("./env-var-script.js", [], [], {
85+
TEST_ENV_VAR: "test"
86+
}),
87+
runScript("./env-var-script.js")
88+
]);
89+
90+
assert.equal(
91+
statusCodeWithEnvVars,
92+
0,
93+
"Status code with env vars should be 0"
94+
);
6295

63-
// We check here that the script is correctly testing this:
64-
const statusCode2 = await runScript("./env-var-script.js");
65-
assert.notEqual(statusCode2, 0);
96+
assert.notEqual(
97+
statusCodeWithNoEnvArgs,
98+
0,
99+
"Status code with no env vars should not be 0"
100+
);
66101
});
67102

68103
describe("runWithBuidler", function() {
69104
useEnvironment();
70105

71106
it("Should load buidler/register successfully", async function() {
72-
const statusCode = await runScriptWithBuidler(
73-
this.env.buidlerArguments,
74-
"./successful-script.js"
75-
);
76-
assert.equal(statusCode, 0);
107+
const [
108+
statusCodeWithBuidler,
109+
statusCodeWithoutBuidler
110+
] = await Promise.all([
111+
runScriptWithBuidler(
112+
this.env.buidlerArguments,
113+
"./successful-script.js"
114+
),
115+
runScript("./successful-script.js")
116+
]);
117+
118+
assert.equal(statusCodeWithBuidler, 0);
77119

78120
// We check here that the script is correctly testing this:
79-
const statusCode2 = await runScript("./successful-script.js");
80-
assert.notEqual(statusCode2, 0);
121+
assert.notEqual(statusCodeWithoutBuidler, 0);
81122
});
82123

83124
it("Should forward all the buidler arguments", async function() {

0 commit comments

Comments
 (0)