gdharness 1.3.10 → 1.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cli.js +661 -294
- package/build/godot/tests/listed.gd +120 -2
- package/build/keeper.js +21 -4
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -10094,7 +10094,15 @@ var init_import_freshness = __esm(() => {
|
|
|
10094
10094
|
});
|
|
10095
10095
|
|
|
10096
10096
|
// src/library-copies.ts
|
|
10097
|
-
import {
|
|
10097
|
+
import {
|
|
10098
|
+
existsSync as existsSync9,
|
|
10099
|
+
mkdirSync as mkdirSync3,
|
|
10100
|
+
readdirSync as readdirSync7,
|
|
10101
|
+
readFileSync as readFileSync7,
|
|
10102
|
+
renameSync,
|
|
10103
|
+
unlinkSync,
|
|
10104
|
+
writeFileSync as writeFileSync3
|
|
10105
|
+
} from "node:fs";
|
|
10098
10106
|
import { basename as basename6, dirname as dirname6, isAbsolute as isAbsolute5, join as join10 } from "node:path";
|
|
10099
10107
|
function freeLibraryCopies(projectPath, platform = process.platform) {
|
|
10100
10108
|
const setAside = [];
|
|
@@ -10129,6 +10137,44 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
|
|
|
10129
10137
|
}
|
|
10130
10138
|
return { setAside, swept };
|
|
10131
10139
|
}
|
|
10140
|
+
function seedExtensionList(projectPath) {
|
|
10141
|
+
const list = join10(projectPath, ".godot", "extension_list.cfg");
|
|
10142
|
+
if (existsSync9(list)) {
|
|
10143
|
+
return null;
|
|
10144
|
+
}
|
|
10145
|
+
const found = [];
|
|
10146
|
+
const visit = (directory, resDirectory) => {
|
|
10147
|
+
if (existsSync9(join10(directory, ".gdignore"))) {
|
|
10148
|
+
return;
|
|
10149
|
+
}
|
|
10150
|
+
let entries;
|
|
10151
|
+
try {
|
|
10152
|
+
entries = readdirSync7(directory, { withFileTypes: true });
|
|
10153
|
+
} catch {
|
|
10154
|
+
return;
|
|
10155
|
+
}
|
|
10156
|
+
for (const entry of entries) {
|
|
10157
|
+
if (entry.name.startsWith(".")) {
|
|
10158
|
+
continue;
|
|
10159
|
+
}
|
|
10160
|
+
if (entry.isDirectory()) {
|
|
10161
|
+
visit(join10(directory, entry.name), `${resDirectory}${entry.name}/`);
|
|
10162
|
+
} else if (entry.name.endsWith(".gdextension")) {
|
|
10163
|
+
found.push(`${resDirectory}${entry.name}`);
|
|
10164
|
+
}
|
|
10165
|
+
}
|
|
10166
|
+
};
|
|
10167
|
+
visit(projectPath, "res://");
|
|
10168
|
+
if (found.length === 0) {
|
|
10169
|
+
return [];
|
|
10170
|
+
}
|
|
10171
|
+
found.sort();
|
|
10172
|
+
mkdirSync3(dirname6(list), { recursive: true });
|
|
10173
|
+
writeFileSync3(list, `${found.join(`
|
|
10174
|
+
`)}
|
|
10175
|
+
`);
|
|
10176
|
+
return found;
|
|
10177
|
+
}
|
|
10132
10178
|
function windowsLibraries(projectPath) {
|
|
10133
10179
|
const listed = readOrNull(join10(projectPath, ".godot", "extension_list.cfg"));
|
|
10134
10180
|
if (listed === null) {
|
|
@@ -10177,7 +10223,7 @@ var init_library_copies = () => {};
|
|
|
10177
10223
|
|
|
10178
10224
|
// src/reimport.ts
|
|
10179
10225
|
import { createHash as createHash2 } from "node:crypto";
|
|
10180
|
-
import { existsSync as existsSync10, readFileSync as readFileSync8, rmSync as rmSync3, statSync as statSync8, utimesSync, writeFileSync as
|
|
10226
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8, rmSync as rmSync3, statSync as statSync8, utimesSync, writeFileSync as writeFileSync4 } from "node:fs";
|
|
10181
10227
|
import { basename as basename7, join as join11 } from "node:path";
|
|
10182
10228
|
function forceNextImport(projectPath, resourcePath) {
|
|
10183
10229
|
const relative = resourcePath.replace(/^res:\/\//, "");
|
|
@@ -10186,7 +10232,7 @@ function forceNextImport(projectPath, resourcePath) {
|
|
|
10186
10232
|
return;
|
|
10187
10233
|
}
|
|
10188
10234
|
const { atime, mtimeMs } = statSync8(sidecar);
|
|
10189
|
-
|
|
10235
|
+
writeFileSync4(sidecar, readFileSync8(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
|
|
10190
10236
|
utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
|
|
10191
10237
|
rmSync3(importRecord(projectPath, resourcePath), { force: true });
|
|
10192
10238
|
}
|
|
@@ -10961,7 +11007,7 @@ var init_script_origins = __esm(() => {
|
|
|
10961
11007
|
});
|
|
10962
11008
|
|
|
10963
11009
|
// src/headless.ts
|
|
10964
|
-
import { existsSync as existsSync12, readFileSync as readFileSync9, writeFileSync as
|
|
11010
|
+
import { existsSync as existsSync12, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "node:fs";
|
|
10965
11011
|
import { dirname as dirname7, join as join14 } from "node:path";
|
|
10966
11012
|
function snakeCased(params) {
|
|
10967
11013
|
const result = emptyRecord();
|
|
@@ -11156,7 +11202,7 @@ async function operationOutcome(engine, operation, params, projectPath) {
|
|
|
11156
11202
|
const paramsDir = scratchDirectory("params");
|
|
11157
11203
|
const paramsFile = join14(paramsDir, `${operation}.json`);
|
|
11158
11204
|
const answerFile = join14(paramsDir, "answer.json");
|
|
11159
|
-
|
|
11205
|
+
writeFileSync5(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
|
|
11160
11206
|
const args = [
|
|
11161
11207
|
"--headless",
|
|
11162
11208
|
...engine.editor === true ? ["--editor"] : [],
|
|
@@ -11277,7 +11323,7 @@ var init_headless = __esm(() => {
|
|
|
11277
11323
|
});
|
|
11278
11324
|
|
|
11279
11325
|
// src/update-check.ts
|
|
11280
|
-
import { existsSync as existsSync13, mkdirSync as
|
|
11326
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "node:fs";
|
|
11281
11327
|
import { homedir as homedir4, tmpdir as tmpdir3 } from "node:os";
|
|
11282
11328
|
import { join as join15 } from "node:path";
|
|
11283
11329
|
function packageUrl(root) {
|
|
@@ -11311,7 +11357,7 @@ function cacheDirectory(environment) {
|
|
|
11311
11357
|
function cacheFile(environment = process.env) {
|
|
11312
11358
|
try {
|
|
11313
11359
|
const directory = cacheDirectory(environment);
|
|
11314
|
-
|
|
11360
|
+
mkdirSync4(directory, { recursive: true, mode: 448 });
|
|
11315
11361
|
return join15(directory, "update-check.json");
|
|
11316
11362
|
} catch {
|
|
11317
11363
|
return join15(tmpdir3(), "gdharness-update-check.json");
|
|
@@ -11339,7 +11385,7 @@ function readCache(path) {
|
|
|
11339
11385
|
}
|
|
11340
11386
|
function writeCache(path, entry) {
|
|
11341
11387
|
try {
|
|
11342
|
-
|
|
11388
|
+
writeFileSync6(path, JSON.stringify(entry), { encoding: "utf8", mode: 384 });
|
|
11343
11389
|
} catch {}
|
|
11344
11390
|
}
|
|
11345
11391
|
function parts(version) {
|
|
@@ -12023,13 +12069,13 @@ import { createHash as createHash4 } from "node:crypto";
|
|
|
12023
12069
|
import {
|
|
12024
12070
|
cpSync,
|
|
12025
12071
|
existsSync as existsSync14,
|
|
12026
|
-
mkdirSync as
|
|
12072
|
+
mkdirSync as mkdirSync5,
|
|
12027
12073
|
readdirSync as readdirSync9,
|
|
12028
12074
|
readFileSync as readFileSync13,
|
|
12029
12075
|
rmdirSync,
|
|
12030
12076
|
rmSync as rmSync5,
|
|
12031
12077
|
statSync as statSync9,
|
|
12032
|
-
writeFileSync as
|
|
12078
|
+
writeFileSync as writeFileSync7
|
|
12033
12079
|
} from "node:fs";
|
|
12034
12080
|
import { dirname as dirname8, join as join16 } from "node:path";
|
|
12035
12081
|
import { fileURLToPath } from "node:url";
|
|
@@ -12118,15 +12164,15 @@ function installAddons(projectPath, from = shippedAddonsDirectory()) {
|
|
|
12118
12164
|
const target = join16(projectPath, "addons", name);
|
|
12119
12165
|
const replaced = existsSync14(target);
|
|
12120
12166
|
rmSync5(target, { recursive: true, force: true });
|
|
12121
|
-
|
|
12167
|
+
mkdirSync5(dirname8(target), { recursive: true });
|
|
12122
12168
|
cpSync(source, target, { recursive: true });
|
|
12123
|
-
|
|
12169
|
+
writeFileSync7(join16(target, VERSION_MARKER), `${SERVER_VERSION}
|
|
12124
12170
|
`);
|
|
12125
12171
|
installed.push({ name, path: target, replaced });
|
|
12126
12172
|
}
|
|
12127
|
-
|
|
12173
|
+
writeFileSync7(join16(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
|
|
12128
12174
|
`);
|
|
12129
|
-
|
|
12175
|
+
writeFileSync7(join16(projectPath, "addons", RUNTIME_ADDON, DIGEST_MARKER), `${runtimeAddonDigest(from)}
|
|
12130
12176
|
`);
|
|
12131
12177
|
return installed;
|
|
12132
12178
|
}
|
|
@@ -14767,7 +14813,7 @@ import {
|
|
|
14767
14813
|
readFileSync as readFileSync14,
|
|
14768
14814
|
rmSync as rmSync7,
|
|
14769
14815
|
statSync as statSync10,
|
|
14770
|
-
writeFileSync as
|
|
14816
|
+
writeFileSync as writeFileSync9,
|
|
14771
14817
|
writeSync
|
|
14772
14818
|
} from "node:fs";
|
|
14773
14819
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
@@ -14816,7 +14862,7 @@ async function ensureHelperCompiled(directory = tmpdir4(), budgetMs = HELPER_COM
|
|
|
14816
14862
|
async function compileHelper(directory, assembly, budgetMs) {
|
|
14817
14863
|
const scratch = mkdtempSync2(join18(directory, "gdharness-desktop-source-"));
|
|
14818
14864
|
const source = join18(scratch, "helper.cs");
|
|
14819
|
-
|
|
14865
|
+
writeFileSync9(source, SOURCE, "utf8");
|
|
14820
14866
|
const literal = (path) => `'${path.replaceAll("'", "''")}'`;
|
|
14821
14867
|
const script = [
|
|
14822
14868
|
"$ErrorActionPreference = 'Stop'",
|
|
@@ -14902,9 +14948,10 @@ function throughHelper(how, command, args, files, assembly = helperAssembly()) {
|
|
|
14902
14948
|
how.noActivate === true ? "1" : "0",
|
|
14903
14949
|
files.stop,
|
|
14904
14950
|
how.directory,
|
|
14905
|
-
files.step
|
|
14951
|
+
files.step,
|
|
14952
|
+
how.affinity === undefined ? "" : how.affinity.toString()
|
|
14906
14953
|
];
|
|
14907
|
-
|
|
14954
|
+
writeFileSync9(files.spec, fields.map((field) => Buffer.from(field, "utf8").toString("base64")).join(`
|
|
14908
14955
|
`), "utf8");
|
|
14909
14956
|
const literal = (path) => `'${path.replaceAll("'", "''")}'`;
|
|
14910
14957
|
const script = [
|
|
@@ -14976,6 +15023,9 @@ public static class GdharnessDesktop {
|
|
|
14976
15023
|
[DllImport("kernel32.dll")] static extern bool CloseHandle(IntPtr handle);
|
|
14977
15024
|
[DllImport("kernel32.dll")] static extern bool TerminateProcess(IntPtr handle, uint code);
|
|
14978
15025
|
[DllImport("kernel32.dll")] static extern uint SetErrorMode(uint mode);
|
|
15026
|
+
[DllImport("kernel32.dll", SetLastError = true)] static extern bool SetProcessAffinityMask(IntPtr process, UIntPtr mask);
|
|
15027
|
+
[DllImport("kernel32.dll")] static extern uint ResumeThread(IntPtr thread);
|
|
15028
|
+
const uint CreateSuspended = 0x4;
|
|
14979
15029
|
const uint FailCriticalErrors = 0x1;
|
|
14980
15030
|
const uint NoOpenFileErrorBox = 0x8000;
|
|
14981
15031
|
|
|
@@ -15037,7 +15087,8 @@ public static class GdharnessDesktop {
|
|
|
15037
15087
|
public static int RunSpec(string specFile) {
|
|
15038
15088
|
string[] lines = File.ReadAllLines(specFile);
|
|
15039
15089
|
Func<int, string> field = delegate(int index) { return Encoding.UTF8.GetString(Convert.FromBase64String(lines[index])); };
|
|
15040
|
-
|
|
15090
|
+
string affinity = lines.Length > 7 ? field(7) : "";
|
|
15091
|
+
return Run(field(0), field(1), field(2), field(3) == "1", field(4), field(5), field(6), affinity.Length == 0 ? 0UL : ulong.Parse(affinity));
|
|
15041
15092
|
}
|
|
15042
15093
|
|
|
15043
15094
|
static void Step(string stepFile, string step) {
|
|
@@ -15046,7 +15097,9 @@ public static class GdharnessDesktop {
|
|
|
15046
15097
|
}
|
|
15047
15098
|
}
|
|
15048
15099
|
|
|
15049
|
-
|
|
15100
|
+
// With an affinity, the process starts suspended and is held to those processors before its
|
|
15101
|
+
// first thread runs, so every thread it starts afterwards is held to them as well.
|
|
15102
|
+
public static int Run(string desktop, string commandLine, string pidFile, bool noActivate, string stopFile, string directory, string stepFile, ulong affinity) {
|
|
15050
15103
|
StartupInfo startup = new StartupInfo();
|
|
15051
15104
|
startup.cb = Marshal.SizeOf(typeof(StartupInfo));
|
|
15052
15105
|
if (desktop.Length > 0) {
|
|
@@ -15076,9 +15129,19 @@ public static class GdharnessDesktop {
|
|
|
15076
15129
|
}
|
|
15077
15130
|
try {
|
|
15078
15131
|
Step(stepFile, "starting the process");
|
|
15079
|
-
|
|
15132
|
+
uint creation = DetachedProcess | (affinity == 0 ? 0u : CreateSuspended);
|
|
15133
|
+
if (!CreateProcessW(null, new StringBuilder(commandLine), IntPtr.Zero, IntPtr.Zero, true, creation, IntPtr.Zero, directory, ref startup, out started)) {
|
|
15080
15134
|
throw new Win32Exception(Marshal.GetLastWin32Error(), "CreateProcessW " + commandLine);
|
|
15081
15135
|
}
|
|
15136
|
+
if (affinity != 0) {
|
|
15137
|
+
Step(stepFile, "holding the process to its processors");
|
|
15138
|
+
if (!SetProcessAffinityMask(started.process, new UIntPtr(affinity))) {
|
|
15139
|
+
int error = Marshal.GetLastWin32Error();
|
|
15140
|
+
TerminateProcess(started.process, 1);
|
|
15141
|
+
throw new Win32Exception(error, "SetProcessAffinityMask " + affinity);
|
|
15142
|
+
}
|
|
15143
|
+
ResumeThread(started.thread);
|
|
15144
|
+
}
|
|
15082
15145
|
File.WriteAllText(pidFile, started.processId.ToString());
|
|
15083
15146
|
if (noActivate) {
|
|
15084
15147
|
HoldTheForeground(started);
|
|
@@ -15127,6 +15190,13 @@ function funcOf(name) {
|
|
|
15127
15190
|
const colon = name.indexOf(":");
|
|
15128
15191
|
return colon < 0 ? name : name.slice(0, colon);
|
|
15129
15192
|
}
|
|
15193
|
+
function rowOf(name) {
|
|
15194
|
+
const row = /^[^:]+:(\d+)(?: |$)/.exec(name)?.[1];
|
|
15195
|
+
return row === undefined ? null : Number(row);
|
|
15196
|
+
}
|
|
15197
|
+
function rowRuns(share, row) {
|
|
15198
|
+
return "only" in share ? row !== null && share.only.includes(row) : row === null || !share.besides.includes(row);
|
|
15199
|
+
}
|
|
15130
15200
|
function readRun(printed) {
|
|
15131
15201
|
const lines = stripVTControlCharacters(printed).replaceAll(`\r
|
|
15132
15202
|
`, `
|
|
@@ -15146,7 +15216,21 @@ function readRun(printed) {
|
|
|
15146
15216
|
}
|
|
15147
15217
|
afterCase = [];
|
|
15148
15218
|
};
|
|
15219
|
+
const leftOut = new Map;
|
|
15220
|
+
let engineVersion = null;
|
|
15149
15221
|
for (const [at, line] of lines.entries()) {
|
|
15222
|
+
const banner = engineVersion === null ? BANNER.exec(line) : null;
|
|
15223
|
+
if (banner !== null) {
|
|
15224
|
+
engineVersion = banner[1] ?? null;
|
|
15225
|
+
continue;
|
|
15226
|
+
}
|
|
15227
|
+
const left = LEFT_OUT.exec(line);
|
|
15228
|
+
if (left !== null) {
|
|
15229
|
+
const key = `${left[1] ?? ""} > ${left[2] ?? ""}`;
|
|
15230
|
+
const row = Number(left[3]);
|
|
15231
|
+
leftOut.set(key, [...leftOut.get(key) ?? [], row < 0 ? null : row]);
|
|
15232
|
+
continue;
|
|
15233
|
+
}
|
|
15150
15234
|
const opened = SUITE_OPENED.exec(line);
|
|
15151
15235
|
if (opened?.[1] !== undefined) {
|
|
15152
15236
|
settleAfterCase("after");
|
|
@@ -15166,6 +15250,7 @@ function readRun(printed) {
|
|
|
15166
15250
|
open.cases.push({
|
|
15167
15251
|
name,
|
|
15168
15252
|
func: funcOf(name),
|
|
15253
|
+
row: rowOf(name),
|
|
15169
15254
|
verdict: caseLine[3] ?? "",
|
|
15170
15255
|
seconds: secondsOf(caseLine[4] ?? ""),
|
|
15171
15256
|
at
|
|
@@ -15225,7 +15310,7 @@ function readRun(printed) {
|
|
|
15225
15310
|
`);
|
|
15226
15311
|
const starved = STARVED.flatMap((pattern) => [...joined.matchAll(pattern)].map((found) => found[0]));
|
|
15227
15312
|
const unloadable = scriptErrorsPrinted(lines).map((error) => `${error.path}${error.line === null ? "" : `:${String(error.line)}`}: ${error.message}`);
|
|
15228
|
-
return { lines, suites, errors, leaks, starved, executed, unloadable, stoppedIn };
|
|
15313
|
+
return { lines, suites, errors, leaks, starved, executed, unloadable, stoppedIn, leftOut, engineVersion };
|
|
15229
15314
|
}
|
|
15230
15315
|
function withNestedErrors(printed, piped) {
|
|
15231
15316
|
const nested = piped.flatMap((entry, at) => {
|
|
@@ -15234,6 +15319,12 @@ function withNestedErrors(printed, piped) {
|
|
|
15234
15319
|
});
|
|
15235
15320
|
return nested.length === 0 ? printed : { ...printed, errors: [...printed.errors, ...nested] };
|
|
15236
15321
|
}
|
|
15322
|
+
function versionFault(said, wanted) {
|
|
15323
|
+
if (said === null) {
|
|
15324
|
+
return `the engine printed no version as it started, so it cannot be told to be Godot ${wanted}`;
|
|
15325
|
+
}
|
|
15326
|
+
return said === wanted || said.startsWith(`${wanted}.`) ? null : `the engine that ran is Godot ${said}, not the ${wanted} --godot-version asks for`;
|
|
15327
|
+
}
|
|
15237
15328
|
function readExpected(file) {
|
|
15238
15329
|
const declared = (value) => typeof value === "object" && value !== null ? value : {};
|
|
15239
15330
|
try {
|
|
@@ -15253,6 +15344,56 @@ function exitNamed(code) {
|
|
|
15253
15344
|
const gdunit = GDUNIT_EXITS.get(code);
|
|
15254
15345
|
return gdunit === undefined ? String(code) : `${String(code)} (${gdunit})`;
|
|
15255
15346
|
}
|
|
15347
|
+
function rowsAcross(engines) {
|
|
15348
|
+
const dealtOut = new Map;
|
|
15349
|
+
for (const { handed, printed } of engines) {
|
|
15350
|
+
for (const suite of handed) {
|
|
15351
|
+
for (const func of Object.keys(suite.rows ?? {})) {
|
|
15352
|
+
const key = `${suite.path} > ${func}`;
|
|
15353
|
+
const entry = dealtOut.get(key) ?? { path: suite.path, func, holders: [] };
|
|
15354
|
+
entry.holders.push(printed);
|
|
15355
|
+
dealtOut.set(key, entry);
|
|
15356
|
+
}
|
|
15357
|
+
}
|
|
15358
|
+
}
|
|
15359
|
+
const faults = [];
|
|
15360
|
+
for (const [key, { path, func, holders }] of dealtOut) {
|
|
15361
|
+
const suites = holders.map((printed) => printed.suites.get(path));
|
|
15362
|
+
if (suites.some((suite) => suite?.closed === null || suite?.closed === undefined)) {
|
|
15363
|
+
continue;
|
|
15364
|
+
}
|
|
15365
|
+
const counts = new Map;
|
|
15366
|
+
for (const ran of suites.flatMap((suite) => suite?.cases ?? []).filter((ran) => ran.func === func)) {
|
|
15367
|
+
counts.set(ran.row, (counts.get(ran.row) ?? 0) + 1);
|
|
15368
|
+
}
|
|
15369
|
+
const said = (row) => row === null ? "the case whole" : `row ${String(row)}`;
|
|
15370
|
+
if (counts.size === 0) {
|
|
15371
|
+
faults.push({
|
|
15372
|
+
suite: path,
|
|
15373
|
+
fault: `${key} ran in none of the ${String(holders.length)} engines its rows were dealt to`
|
|
15374
|
+
});
|
|
15375
|
+
continue;
|
|
15376
|
+
}
|
|
15377
|
+
const twice = [...counts].filter(([, times]) => times > 1).map(([row]) => said(row));
|
|
15378
|
+
if (twice.length > 0) {
|
|
15379
|
+
faults.push({ suite: path, fault: `${key} ran ${named(twice)} in more than one engine` });
|
|
15380
|
+
}
|
|
15381
|
+
const rows = [...counts.keys()].filter((row) => row !== null);
|
|
15382
|
+
if (rows.length > 0 && counts.has(null)) {
|
|
15383
|
+
faults.push({ suite: path, fault: `${key} ran both whole and by its rows` });
|
|
15384
|
+
}
|
|
15385
|
+
const left = holders.flatMap((printed) => printed.leftOut.get(key) ?? []).filter((row) => row !== null);
|
|
15386
|
+
const top = Math.max(-1, ...rows, ...left);
|
|
15387
|
+
const gaps = Array.from({ length: top + 1 }, (_, row) => row).filter((row) => !counts.has(row));
|
|
15388
|
+
if (gaps.length > 0) {
|
|
15389
|
+
faults.push({
|
|
15390
|
+
suite: path,
|
|
15391
|
+
fault: `${key} did not run ${named(gaps.map(said))} in any of the ${String(holders.length)} engines its rows were dealt to`
|
|
15392
|
+
});
|
|
15393
|
+
}
|
|
15394
|
+
}
|
|
15395
|
+
return faults;
|
|
15396
|
+
}
|
|
15256
15397
|
function named(items) {
|
|
15257
15398
|
const shown = items.slice(0, MOST_NAMED).join(", ");
|
|
15258
15399
|
return items.length > MOST_NAMED ? `${shown} and ${String(items.length - MOST_NAMED)} more` : shown;
|
|
@@ -15265,15 +15406,23 @@ function judgeEngine(printed, handed, end) {
|
|
|
15265
15406
|
if (end.ended !== null) {
|
|
15266
15407
|
faults.push(end.ended);
|
|
15267
15408
|
} else if (end.code !== 0 && !explained) {
|
|
15268
|
-
|
|
15409
|
+
const free = end.freeBytes === undefined ? "" : `, with ${(end.freeBytes / 1024 ** 3).toFixed(1)} GB of memory free as it ended`;
|
|
15410
|
+
faults.push(end.code === null ? `the engine was ended by ${end.signal ?? "a signal"}${free}` : `the engine exited ${exitNamed(end.code)}${free}`);
|
|
15411
|
+
}
|
|
15412
|
+
if (printed.lines.every((line) => line.trim() === "")) {
|
|
15413
|
+
faults.push("the engine printed nothing at all");
|
|
15269
15414
|
}
|
|
15270
15415
|
if (printed.unloadable.length > 0) {
|
|
15271
15416
|
faults.push(`gdUnit4 could not load ${named(printed.unloadable)}, so it ran nothing`);
|
|
15272
15417
|
}
|
|
15418
|
+
const wrongEngine = handed.godotVersion === undefined || handed.godotVersion === null ? null : versionFault(printed.engineVersion, handed.godotVersion);
|
|
15419
|
+
if (wrongEngine !== null) {
|
|
15420
|
+
faults.push(wrongEngine);
|
|
15421
|
+
}
|
|
15273
15422
|
if (printed.executed === null || printed.executed === 0) {
|
|
15274
15423
|
faults.push(printed.executed === null ? "the run never printed its summary" : "the run executed no test case at all, whatever it exited with");
|
|
15275
15424
|
}
|
|
15276
|
-
for (const { path, cases } of handed.suites) {
|
|
15425
|
+
for (const { path, cases, rows = {} } of handed.suites) {
|
|
15277
15426
|
const suite = printed.suites.get(path);
|
|
15278
15427
|
if (suite === undefined) {
|
|
15279
15428
|
faults.push(`${path} never started`);
|
|
@@ -15290,11 +15439,18 @@ function judgeEngine(printed, handed, end) {
|
|
|
15290
15439
|
failing.add(path);
|
|
15291
15440
|
}
|
|
15292
15441
|
const finished = new Set(suite.cases.map((ran) => ran.func));
|
|
15293
|
-
const missing = cases.filter((func) => !finished.has(func));
|
|
15442
|
+
const missing = cases.filter((func) => !finished.has(func) && rows[func] === undefined);
|
|
15294
15443
|
if (missing.length > 0 && suite.closed !== null) {
|
|
15295
15444
|
faults.push(`${path} closed without running ${named(missing)}`);
|
|
15296
15445
|
failing.add(path);
|
|
15297
15446
|
}
|
|
15447
|
+
for (const [func, share] of Object.entries(rows)) {
|
|
15448
|
+
const strays = suite.cases.filter((ran) => ran.func === func && !rowRuns(share, ran.row)).map((ran) => ran.row === null ? "the case whole" : `row ${String(ran.row)}`);
|
|
15449
|
+
if (strays.length > 0) {
|
|
15450
|
+
faults.push(`${path} > ${func} ran ${named(strays)}, which ${strays.length === 1 ? "was" : "were"} dealt to another engine`);
|
|
15451
|
+
failing.add(path);
|
|
15452
|
+
}
|
|
15453
|
+
}
|
|
15298
15454
|
const wanted = new Set(cases);
|
|
15299
15455
|
const unasked = [...finished].filter((func) => !wanted.has(func));
|
|
15300
15456
|
if (unasked.length > 0) {
|
|
@@ -15416,7 +15572,7 @@ ${String(error.warning)}`);
|
|
|
15416
15572
|
const neverPrinted = [...owed.values()].flatMap((debt) => debt.texts.map((text) => ({ suite: debt.suite, during: debt.during, text, warning: debt.warning })));
|
|
15417
15573
|
return { printed: unexpected, neverPrinted };
|
|
15418
15574
|
}
|
|
15419
|
-
var SUITE_OPENED, TIME, CASE_LINE, STATISTICS, ERROR_LINE, WARNING_LINE, EXECUTED, DRIVER_FALLBACK, ORPHANS_REPORTED, STARVED, NESTED_ERROR = "While attempting to print an error, another error was printed:", WINDOWS_EXITS, MEMORY_EXITS, GDUNIT_FAILED = 100, GDUNIT_EXITS, TAIL = 60, MOST_NAMED = 8;
|
|
15575
|
+
var SUITE_OPENED, TIME, CASE_LINE, STATISTICS, ERROR_LINE, WARNING_LINE, EXECUTED, BANNER, LEFT_OUT, DRIVER_FALLBACK, ORPHANS_REPORTED, STARVED, NESTED_ERROR = "While attempting to print an error, another error was printed:", WINDOWS_EXITS, MEMORY_EXITS, GDUNIT_FAILED = 100, GDUNIT_EXITS, TAIL = 60, MOST_NAMED = 8;
|
|
15420
15576
|
var init_suite_judge = __esm(() => {
|
|
15421
15577
|
init_engine_leaks();
|
|
15422
15578
|
init_junit();
|
|
@@ -15427,6 +15583,8 @@ var init_suite_judge = __esm(() => {
|
|
|
15427
15583
|
ERROR_LINE = /^\s*(?:SCRIPT |USER )?ERROR: (.*?)\s*$/;
|
|
15428
15584
|
WARNING_LINE = /^\s*(?:SCRIPT |USER )?WARNING: (.*?)\s*$/;
|
|
15429
15585
|
EXECUTED = /^Executed test cases\s*:\s*\((\d+)\//;
|
|
15586
|
+
BANNER = /^\s*Godot Engine v(\S+?)(?:\s+-\s+\S+)?\s*$/;
|
|
15587
|
+
LEFT_OUT = /^gdharness left out (res:\/\/.+?\.gd) > ([^:\s]+):(-?\d+)\s*$/;
|
|
15430
15588
|
DRIVER_FALLBACK = /^\s*WARNING: Your video card drivers .*, switching to /;
|
|
15431
15589
|
ORPHANS_REPORTED = /^\s*WARNING: Detected \d+ (?:possible )?orphan nodes\.\s*$/;
|
|
15432
15590
|
STARVED = [
|
|
@@ -15453,11 +15611,11 @@ var init_suite_judge = __esm(() => {
|
|
|
15453
15611
|
});
|
|
15454
15612
|
|
|
15455
15613
|
// src/whole-file.ts
|
|
15456
|
-
import { renameSync as renameSync2, rmSync as rmSync9, writeFileSync as
|
|
15614
|
+
import { renameSync as renameSync2, rmSync as rmSync9, writeFileSync as writeFileSync10 } from "node:fs";
|
|
15457
15615
|
function writeWhole(path, text) {
|
|
15458
15616
|
const partial = `${path}.${process.pid}.partial`;
|
|
15459
15617
|
try {
|
|
15460
|
-
|
|
15618
|
+
writeFileSync10(partial, text, "utf8");
|
|
15461
15619
|
} catch {
|
|
15462
15620
|
return false;
|
|
15463
15621
|
}
|
|
@@ -15484,16 +15642,29 @@ var init_whole_file = __esm(() => {
|
|
|
15484
15642
|
});
|
|
15485
15643
|
|
|
15486
15644
|
// src/suite-plan.ts
|
|
15487
|
-
import { existsSync as existsSync18, mkdirSync as
|
|
15488
|
-
import { dirname as dirname10, posix as posix3 } from "node:path";
|
|
15645
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync8, readFileSync as readFileSync18 } from "node:fs";
|
|
15646
|
+
import { dirname as dirname10, join as join21, posix as posix3 } from "node:path";
|
|
15647
|
+
function within(path, folder) {
|
|
15648
|
+
return path === folder || path.startsWith(`${folder}/`);
|
|
15649
|
+
}
|
|
15489
15650
|
|
|
15490
15651
|
class ProjectScripts {
|
|
15491
15652
|
sources = new Map;
|
|
15492
15653
|
classes = new Map;
|
|
15493
15654
|
read = new Map;
|
|
15494
15655
|
projectPath;
|
|
15656
|
+
notSearched;
|
|
15657
|
+
guessWindow;
|
|
15495
15658
|
constructor(projectPath) {
|
|
15496
15659
|
this.projectPath = projectPath;
|
|
15660
|
+
let settings = {};
|
|
15661
|
+
try {
|
|
15662
|
+
settings = parseProjectGodot(readFileSync18(join21(projectPath, "project.godot"), "utf8"))["gdharness"] ?? {};
|
|
15663
|
+
} catch {}
|
|
15664
|
+
const [section, key] = [NOT_SEARCHED_SETTING, GUESS_WINDOW_SETTING].map((setting) => setting.slice("gdharness/".length));
|
|
15665
|
+
const listed = settings[section ?? ""];
|
|
15666
|
+
this.notSearched = typeof listed === "string" ? [...listed.matchAll(/"((?:[^"\\]|\\.)*)"/g)].map((found) => (found[1] ?? "").replace(/\/+$/, "")) : [];
|
|
15667
|
+
this.guessWindow = settings[key ?? ""] !== false;
|
|
15497
15668
|
eachScript(projectPath, (script, source) => {
|
|
15498
15669
|
this.sources.set(script, source);
|
|
15499
15670
|
const declared = declaredClassOf(source);
|
|
@@ -15554,7 +15725,7 @@ class ProjectScripts {
|
|
|
15554
15725
|
}
|
|
15555
15726
|
const declared = NEEDS_WINDOW.exec(read.code)?.[1];
|
|
15556
15727
|
said ??= declared === undefined ? undefined : declared === "true";
|
|
15557
|
-
guessed ||= SCENE_RUNNER.test(read.code);
|
|
15728
|
+
guessed ||= this.guessWindow && SCENE_RUNNER.test(read.code);
|
|
15558
15729
|
const base = this.base(at);
|
|
15559
15730
|
if (base === null) {
|
|
15560
15731
|
return null;
|
|
@@ -15570,8 +15741,16 @@ class ProjectScripts {
|
|
|
15570
15741
|
return null;
|
|
15571
15742
|
}
|
|
15572
15743
|
suitesUnder(roots) {
|
|
15573
|
-
const
|
|
15574
|
-
return [...this.sources.keys()].filter((path) =>
|
|
15744
|
+
const searched = roots.map((root) => root.replace(/\/+$/, ""));
|
|
15745
|
+
return [...this.sources.keys()].filter((path) => searched.some((root) => path.startsWith(`${root}/`) && !this.notSearched.some((folder) => within(path, folder) && !within(root, folder)))).sort().flatMap((path) => this.suite(path) ?? []);
|
|
15746
|
+
}
|
|
15747
|
+
searchedAsTold(paths) {
|
|
15748
|
+
return [
|
|
15749
|
+
...new Set(paths.flatMap((path) => {
|
|
15750
|
+
const root = path.replace(/\/+$/, "");
|
|
15751
|
+
return path.endsWith(".gd") || !this.notSearched.some((folder) => folder.startsWith(`${root}/`) && !within(root, folder)) ? [path] : this.suitesUnder([root]).map((suite) => suite.path);
|
|
15752
|
+
}))
|
|
15753
|
+
];
|
|
15575
15754
|
}
|
|
15576
15755
|
}
|
|
15577
15756
|
function readTimes(file) {
|
|
@@ -15583,11 +15762,11 @@ function readTimes(file) {
|
|
|
15583
15762
|
}
|
|
15584
15763
|
}
|
|
15585
15764
|
function writeJson(file, value) {
|
|
15586
|
-
|
|
15765
|
+
mkdirSync8(dirname10(file), { recursive: true });
|
|
15587
15766
|
writeWhole(file, `${JSON.stringify(value, null, 1)}
|
|
15588
15767
|
`);
|
|
15589
15768
|
}
|
|
15590
|
-
function timesAfter(times, suites, runs, now) {
|
|
15769
|
+
function timesAfter(times, suites, runs, now, cutShort = new Set) {
|
|
15591
15770
|
const next = {};
|
|
15592
15771
|
for (const [path, time] of Object.entries(times)) {
|
|
15593
15772
|
const suite = now(path);
|
|
@@ -15596,7 +15775,12 @@ function timesAfter(times, suites, runs, now) {
|
|
|
15596
15775
|
}
|
|
15597
15776
|
const cases = Object.fromEntries(Object.entries(time.cases).filter(([func]) => suite.cases.includes(func)));
|
|
15598
15777
|
const dropped = Object.entries(time.cases).filter(([func]) => !(func in cases)).reduce((sum, [, seconds]) => sum + seconds, 0);
|
|
15599
|
-
|
|
15778
|
+
const rows = Object.fromEntries(Object.entries(time.rows ?? {}).filter(([func]) => suite.cases.includes(func)));
|
|
15779
|
+
next[path] = {
|
|
15780
|
+
seconds: round(Math.max(0, time.seconds - dropped)),
|
|
15781
|
+
cases,
|
|
15782
|
+
...Object.keys(rows).length > 0 ? { rows } : {}
|
|
15783
|
+
};
|
|
15600
15784
|
}
|
|
15601
15785
|
const declared = new Map(suites.map((suite) => [suite.path, suite]));
|
|
15602
15786
|
const measured = new Map;
|
|
@@ -15605,15 +15789,24 @@ function timesAfter(times, suites, runs, now) {
|
|
|
15605
15789
|
if (printed.closed === null || !declared.has(printed.path)) {
|
|
15606
15790
|
continue;
|
|
15607
15791
|
}
|
|
15608
|
-
const entry = measured.get(printed.path) ?? {
|
|
15792
|
+
const entry = measured.get(printed.path) ?? {
|
|
15793
|
+
cases: new Map,
|
|
15794
|
+
rows: new Map,
|
|
15795
|
+
around: 0
|
|
15796
|
+
};
|
|
15609
15797
|
const own = new Map;
|
|
15610
15798
|
for (const ran of printed.cases) {
|
|
15611
15799
|
own.set(ran.func, (own.get(ran.func) ?? 0) + ran.seconds);
|
|
15800
|
+
if (ran.row !== null) {
|
|
15801
|
+
const rows = entry.rows.get(ran.func) ?? new Map;
|
|
15802
|
+
rows.set(ran.row, ran.seconds);
|
|
15803
|
+
entry.rows.set(ran.func, rows);
|
|
15804
|
+
}
|
|
15612
15805
|
}
|
|
15613
15806
|
const inCases = [...own.values()].reduce((sum, seconds) => sum + seconds, 0);
|
|
15614
15807
|
entry.around = Math.max(entry.around, printed.closed.seconds - inCases);
|
|
15615
15808
|
for (const [func, seconds] of own) {
|
|
15616
|
-
entry.cases.set(func, seconds);
|
|
15809
|
+
entry.cases.set(func, (entry.cases.get(func) ?? 0) + seconds);
|
|
15617
15810
|
}
|
|
15618
15811
|
measured.set(printed.path, entry);
|
|
15619
15812
|
}
|
|
@@ -15621,17 +15814,33 @@ function timesAfter(times, suites, runs, now) {
|
|
|
15621
15814
|
for (const [path, entry] of measured) {
|
|
15622
15815
|
const suite = declared.get(path);
|
|
15623
15816
|
const cases = {};
|
|
15817
|
+
const rows = {};
|
|
15624
15818
|
for (const func of suite?.cases ?? []) {
|
|
15625
|
-
const
|
|
15819
|
+
const measuredRows = entry.rows.get(func);
|
|
15820
|
+
const keptRows = measuredRows === undefined ? times[path]?.rows?.[func] : rowsAfter(times[path]?.rows?.[func], measuredRows, cutShort.has(`${path}:${func}`));
|
|
15821
|
+
if (keptRows !== undefined) {
|
|
15822
|
+
rows[func] = keptRows.map(round);
|
|
15823
|
+
}
|
|
15824
|
+
const seconds = measuredRows === undefined || keptRows === undefined ? entry.cases.get(func) ?? times[path]?.cases[func] : keptRows.reduce((sum, row) => sum + row, 0);
|
|
15626
15825
|
if (seconds !== undefined) {
|
|
15627
15826
|
cases[func] = round(seconds);
|
|
15628
15827
|
}
|
|
15629
15828
|
}
|
|
15630
15829
|
const inCases = Object.values(cases).reduce((sum, seconds) => sum + seconds, 0);
|
|
15631
|
-
next[path] = {
|
|
15830
|
+
next[path] = {
|
|
15831
|
+
seconds: round(entry.around + inCases),
|
|
15832
|
+
cases,
|
|
15833
|
+
...Object.keys(rows).length > 0 ? { rows } : {}
|
|
15834
|
+
};
|
|
15632
15835
|
}
|
|
15633
15836
|
return next;
|
|
15634
15837
|
}
|
|
15838
|
+
function rowsAfter(before, measured, cutShort) {
|
|
15839
|
+
const count = Math.max(...measured.keys()) + 1;
|
|
15840
|
+
const whole = !cutShort && measured.size === count;
|
|
15841
|
+
const length = whole ? count : Math.max(count, before?.length ?? 0);
|
|
15842
|
+
return Array.from({ length }, (_, row) => measured.get(row) ?? before?.[row] ?? 0);
|
|
15843
|
+
}
|
|
15635
15844
|
function round(seconds) {
|
|
15636
15845
|
return Math.round(seconds * 1000) / 1000;
|
|
15637
15846
|
}
|
|
@@ -15652,24 +15861,39 @@ function unitsOf(suites, times, splitPast = SPLIT_PAST_SECONDS) {
|
|
|
15652
15861
|
const costOf = (func) => time?.cases[func] ?? caseCost;
|
|
15653
15862
|
const around = time === undefined ? aroundCost : Math.max(0, time.seconds - Object.values(time.cases).reduce((sum, s) => sum + s, 0));
|
|
15654
15863
|
const whole = around + suite.cases.reduce((sum, func) => sum + costOf(func), 0);
|
|
15655
|
-
|
|
15656
|
-
return [
|
|
15657
|
-
{
|
|
15658
|
-
suite: suite.path,
|
|
15659
|
-
cases: suite.cases,
|
|
15660
|
-
cost: whole,
|
|
15661
|
-
windowed: suite.windowed,
|
|
15662
|
-
headless: suite.headless
|
|
15663
|
-
}
|
|
15664
|
-
];
|
|
15665
|
-
}
|
|
15666
|
-
return suite.cases.map((func) => ({
|
|
15864
|
+
const wholly = {
|
|
15667
15865
|
suite: suite.path,
|
|
15668
|
-
cases:
|
|
15669
|
-
cost:
|
|
15670
|
-
windowed:
|
|
15866
|
+
cases: suite.cases,
|
|
15867
|
+
cost: whole,
|
|
15868
|
+
windowed: suite.windowed,
|
|
15671
15869
|
headless: suite.headless
|
|
15672
|
-
}
|
|
15870
|
+
};
|
|
15871
|
+
if (suite.windowed || whole <= splitPast) {
|
|
15872
|
+
return [wholly];
|
|
15873
|
+
}
|
|
15874
|
+
const units = suite.cases.flatMap((func) => {
|
|
15875
|
+
const rows = time?.rows?.[func] ?? [];
|
|
15876
|
+
if (rows.length < 2 || around + costOf(func) <= splitPast) {
|
|
15877
|
+
return [
|
|
15878
|
+
{
|
|
15879
|
+
suite: suite.path,
|
|
15880
|
+
cases: [func],
|
|
15881
|
+
cost: around + costOf(func),
|
|
15882
|
+
windowed: false,
|
|
15883
|
+
headless: suite.headless
|
|
15884
|
+
}
|
|
15885
|
+
];
|
|
15886
|
+
}
|
|
15887
|
+
return rows.map((seconds, index) => ({
|
|
15888
|
+
suite: suite.path,
|
|
15889
|
+
cases: [func],
|
|
15890
|
+
row: { index, rest: index === rows.length - 1 },
|
|
15891
|
+
cost: around + seconds,
|
|
15892
|
+
windowed: false,
|
|
15893
|
+
headless: suite.headless
|
|
15894
|
+
}));
|
|
15895
|
+
});
|
|
15896
|
+
return units.length < 2 ? [wholly] : units;
|
|
15673
15897
|
});
|
|
15674
15898
|
}
|
|
15675
15899
|
function dealt(units, declared, most) {
|
|
@@ -15677,7 +15901,7 @@ function dealt(units, declared, most) {
|
|
|
15677
15901
|
return [];
|
|
15678
15902
|
}
|
|
15679
15903
|
const windowed = units.filter((unit) => unit.windowed);
|
|
15680
|
-
const headless = units.filter((unit) => !unit.windowed).sort((a, b) => b.cost - a.cost || a.suite.localeCompare(b.suite) || (a.cases[0] ?? "").localeCompare(b.cases[0] ?? ""));
|
|
15904
|
+
const headless = units.filter((unit) => !unit.windowed).sort((a, b) => b.cost - a.cost || a.suite.localeCompare(b.suite) || (a.cases[0] ?? "").localeCompare(b.cases[0] ?? "") || (a.row?.index ?? 0) - (b.row?.index ?? 0));
|
|
15681
15905
|
const together = windowed.reduce((sum, unit) => sum + unit.cost, 0);
|
|
15682
15906
|
const restart = windowed.length > 0 ? ENGINE_START_SECONDS : 0;
|
|
15683
15907
|
const total = units.reduce((sum, unit) => sum + unit.cost, 0);
|
|
@@ -15691,8 +15915,8 @@ function dealt(units, declared, most) {
|
|
|
15691
15915
|
loads[lightest] = (loads[lightest] ?? 0) + unit.cost;
|
|
15692
15916
|
}
|
|
15693
15917
|
return [
|
|
15694
|
-
...windowed.length > 0 ? [planOf(windowed, declared, true, 0)] : [],
|
|
15695
|
-
...bins.flatMap((bin, lane) => bin.length === 0 ? [] : [planOf(bin, declared, false, lane)])
|
|
15918
|
+
...windowed.length > 0 ? [planOf(windowed, declared, true, 0, units)] : [],
|
|
15919
|
+
...bins.flatMap((bin, lane) => bin.length === 0 ? [] : [planOf(bin, declared, false, lane, units)])
|
|
15696
15920
|
];
|
|
15697
15921
|
}
|
|
15698
15922
|
function lanesOf(plans) {
|
|
@@ -15713,24 +15937,41 @@ function headlessInAWindow(plans, units) {
|
|
|
15713
15937
|
return plans.filter((plan) => plan.windowed).flatMap((plan) => plan.suites.map((suite) => suite.path)).filter((path) => said.has(path));
|
|
15714
15938
|
}
|
|
15715
15939
|
function inOneEngine(units, declared) {
|
|
15716
|
-
return planOf(units, declared, units.some((unit) => unit.windowed), 0);
|
|
15940
|
+
return planOf(units, declared, units.some((unit) => unit.windowed), 0, units);
|
|
15717
15941
|
}
|
|
15718
|
-
function planOf(bin, declared, windowed, lane) {
|
|
15942
|
+
function planOf(bin, declared, windowed, lane, all) {
|
|
15719
15943
|
const held = new Map;
|
|
15720
15944
|
for (const unit of bin) {
|
|
15721
|
-
|
|
15945
|
+
const cases = held.get(unit.suite) ?? [];
|
|
15946
|
+
held.set(unit.suite, [...cases, ...unit.cases.filter((func) => !cases.includes(func))]);
|
|
15722
15947
|
}
|
|
15723
15948
|
const skipping = [...held].flatMap(([path, cases]) => (declared.get(path) ?? []).filter((func) => !cases.includes(func)).map((func) => `${path}:${func}`));
|
|
15949
|
+
const rowsOf = (units, path, func) => units.flatMap((unit) => unit.suite === path && unit.cases[0] === func && unit.row !== undefined ? [unit.row.index] : []);
|
|
15950
|
+
const sharesOf = (path, cases) => {
|
|
15951
|
+
const shares = {};
|
|
15952
|
+
for (const func of cases) {
|
|
15953
|
+
const mine = rowsOf(bin, path, func);
|
|
15954
|
+
const dealtOut = rowsOf(all, path, func);
|
|
15955
|
+
if (mine.length === 0 || mine.length === dealtOut.length) {
|
|
15956
|
+
continue;
|
|
15957
|
+
}
|
|
15958
|
+
const rest = bin.some((unit) => unit.suite === path && unit.cases[0] === func && unit.row?.rest === true);
|
|
15959
|
+
shares[func] = rest ? { besides: dealtOut.filter((row) => !mine.includes(row)) } : { only: mine.sort((a, b) => a - b) };
|
|
15960
|
+
}
|
|
15961
|
+
return shares;
|
|
15962
|
+
};
|
|
15724
15963
|
return {
|
|
15725
|
-
suites: [...held].map(([path, cases]) =>
|
|
15964
|
+
suites: [...held].map(([path, cases]) => {
|
|
15965
|
+
const rows = sharesOf(path, cases);
|
|
15966
|
+
return { path, cases: [...cases].sort(), ...Object.keys(rows).length > 0 ? { rows } : {} };
|
|
15967
|
+
}),
|
|
15726
15968
|
skipping,
|
|
15727
15969
|
cost: bin.reduce((sum, unit) => sum + unit.cost, 0),
|
|
15728
15970
|
windowed,
|
|
15729
15971
|
lane
|
|
15730
15972
|
};
|
|
15731
15973
|
}
|
|
15732
|
-
function windowNeedsOf(
|
|
15733
|
-
const scripts = new ProjectScripts(projectPath);
|
|
15974
|
+
function windowNeedsOf(scripts, paths) {
|
|
15734
15975
|
const suites = new Map(paths.flatMap((path) => path.endsWith(".gd") ? scripts.suite(path) ?? [] : scripts.suitesUnder([path])).map((suite) => [suite.path, suite]));
|
|
15735
15976
|
const sorted = [...suites.values()].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
15736
15977
|
return {
|
|
@@ -15748,10 +15989,11 @@ function viewportOf(projectPath) {
|
|
|
15748
15989
|
const height = /^window\/size\/viewport_height\s*=\s*(\d+)/m.exec(text)?.[1];
|
|
15749
15990
|
return width === undefined || height === undefined ? null : { width: Number(width), height: Number(height) };
|
|
15750
15991
|
}
|
|
15751
|
-
var SUITE_BASE = "GdUnitTestSuite", CASE_PREFIX = "test_", NEEDS_WINDOW, TEST_DIRECTORIES, SCENE_RUNNER, UNTIMED_CASE = 0.02, SPLIT_PAST_SECONDS = 10, ENGINE_START_SECONDS = 5;
|
|
15992
|
+
var SUITE_BASE = "GdUnitTestSuite", CASE_PREFIX = "test_", NEEDS_WINDOW, TEST_DIRECTORIES, SCENE_RUNNER, NOT_SEARCHED_SETTING = "gdharness/tests/not_searched", GUESS_WINDOW_SETTING = "gdharness/tests/scene_runner_needs_window", UNTIMED_CASE = 0.02, SPLIT_PAST_SECONDS = 10, ENGINE_START_SECONDS = 5;
|
|
15752
15993
|
var init_suite_plan = __esm(() => {
|
|
15753
15994
|
init_class_cache();
|
|
15754
15995
|
init_gdscript_source();
|
|
15996
|
+
init_resources();
|
|
15755
15997
|
init_whole_file();
|
|
15756
15998
|
NEEDS_WINDOW = /^const\s+GDHARNESS_NEEDS_WINDOW\b[^=\n]*=\s*(true|false)\b/m;
|
|
15757
15999
|
TEST_DIRECTORIES = ["tests", "test"];
|
|
@@ -37360,12 +37602,12 @@ var init_stdio = __esm(() => {
|
|
|
37360
37602
|
|
|
37361
37603
|
// src/autoload-failure.ts
|
|
37362
37604
|
import { existsSync as existsSync20, readFileSync as readFileSync21 } from "node:fs";
|
|
37363
|
-
import { join as
|
|
37605
|
+
import { join as join24 } from "node:path";
|
|
37364
37606
|
function scriptsAutoloaded(projectPath) {
|
|
37365
37607
|
const scripts = new Map;
|
|
37366
37608
|
let settings;
|
|
37367
37609
|
try {
|
|
37368
|
-
settings = parseProjectGodot(readFileSync21(
|
|
37610
|
+
settings = parseProjectGodot(readFileSync21(join24(projectPath, "project.godot"), "utf8"));
|
|
37369
37611
|
} catch {
|
|
37370
37612
|
return scripts;
|
|
37371
37613
|
}
|
|
@@ -37374,7 +37616,7 @@ function scriptsAutoloaded(projectPath) {
|
|
|
37374
37616
|
scripts.set(`res://${path}`, name);
|
|
37375
37617
|
continue;
|
|
37376
37618
|
}
|
|
37377
|
-
const scene =
|
|
37619
|
+
const scene = join24(projectPath, path);
|
|
37378
37620
|
if (!existsSync20(scene)) {
|
|
37379
37621
|
continue;
|
|
37380
37622
|
}
|
|
@@ -37401,7 +37643,7 @@ var init_autoload_failure = __esm(() => {
|
|
|
37401
37643
|
import { existsSync as existsSync21, readdirSync as readdirSync13, readFileSync as readFileSync22, statSync as statSync14, unlinkSync as unlinkSync2 } from "node:fs";
|
|
37402
37644
|
import { createConnection } from "node:net";
|
|
37403
37645
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
37404
|
-
import { dirname as dirname12, join as
|
|
37646
|
+
import { dirname as dirname12, join as join25, resolve as resolve6 } from "node:path";
|
|
37405
37647
|
import { setTimeout as delay4 } from "node:timers/promises";
|
|
37406
37648
|
function runtimeDirectory(variables = process.env) {
|
|
37407
37649
|
const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
|
|
@@ -37409,19 +37651,19 @@ function runtimeDirectory(variables = process.env) {
|
|
|
37409
37651
|
return explicit;
|
|
37410
37652
|
}
|
|
37411
37653
|
const perUser = envValue("XDG_RUNTIME_DIR", variables);
|
|
37412
|
-
return
|
|
37654
|
+
return join25(perUser ?? tmpdir5(), "gdharness");
|
|
37413
37655
|
}
|
|
37414
37656
|
function runtimeDirectories(variables = process.env) {
|
|
37415
37657
|
const candidates = [runtimeDirectory(variables)];
|
|
37416
37658
|
const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
|
|
37417
37659
|
for (const base of fallbacks) {
|
|
37418
|
-
candidates.push(
|
|
37660
|
+
candidates.push(join25(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
|
|
37419
37661
|
}
|
|
37420
37662
|
return [...new Set(candidates.map((path) => resolve6(path)))];
|
|
37421
37663
|
}
|
|
37422
37664
|
function errorReportOf(pid, directories = runtimeDirectories()) {
|
|
37423
37665
|
for (const directory of directories) {
|
|
37424
|
-
const path =
|
|
37666
|
+
const path = join25(directory, `runtime-${pid}.log`);
|
|
37425
37667
|
if (existsSync21(path)) {
|
|
37426
37668
|
return path;
|
|
37427
37669
|
}
|
|
@@ -37577,14 +37819,14 @@ function announcedIn(directory) {
|
|
|
37577
37819
|
for (const entry of readdirSync13(directory)) {
|
|
37578
37820
|
const report = ERROR_REPORT_PATTERN.exec(entry);
|
|
37579
37821
|
if (report) {
|
|
37580
|
-
sweepErrorReport(
|
|
37822
|
+
sweepErrorReport(join25(directory, entry), Number.parseInt(report[1] ?? "", 10));
|
|
37581
37823
|
continue;
|
|
37582
37824
|
}
|
|
37583
37825
|
const match = ANNOUNCEMENT_PATTERN.exec(entry);
|
|
37584
37826
|
if (!match) {
|
|
37585
37827
|
continue;
|
|
37586
37828
|
}
|
|
37587
|
-
const file =
|
|
37829
|
+
const file = join25(directory, entry);
|
|
37588
37830
|
const pid = Number.parseInt(match[1] ?? "", 10);
|
|
37589
37831
|
const running = alive(pid);
|
|
37590
37832
|
entries.push({ file, pid, running });
|
|
@@ -37612,7 +37854,7 @@ function announcedIn(directory) {
|
|
|
37612
37854
|
found.push(announced);
|
|
37613
37855
|
}
|
|
37614
37856
|
const held = new Set(entries.map((one) => one.file));
|
|
37615
|
-
const here =
|
|
37857
|
+
const here = join25(directory, ".");
|
|
37616
37858
|
for (const file of verdicts.keys()) {
|
|
37617
37859
|
if (dirname12(file) === here && !held.has(file)) {
|
|
37618
37860
|
verdicts.delete(file);
|
|
@@ -37621,7 +37863,7 @@ function announcedIn(directory) {
|
|
|
37621
37863
|
return found;
|
|
37622
37864
|
}
|
|
37623
37865
|
async function announcementEnded(pid, directories = runtimeDirectories()) {
|
|
37624
|
-
const files = directories.map((directory) =>
|
|
37866
|
+
const files = directories.map((directory) => join25(directory, `runtime-${pid}.json`)).filter((file) => existsSync21(file));
|
|
37625
37867
|
const first = files[0];
|
|
37626
37868
|
if (first === undefined) {
|
|
37627
37869
|
return;
|
|
@@ -38040,10 +38282,10 @@ var init_runtime_client = __esm(() => {
|
|
|
38040
38282
|
});
|
|
38041
38283
|
|
|
38042
38284
|
// src/boot-note.ts
|
|
38043
|
-
import { mkdirSync as
|
|
38044
|
-
import { dirname as dirname13, join as
|
|
38285
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync23, writeFileSync as writeFileSync12 } from "node:fs";
|
|
38286
|
+
import { dirname as dirname13, join as join26 } from "node:path";
|
|
38045
38287
|
function bootNotePath(projectPath) {
|
|
38046
|
-
return
|
|
38288
|
+
return join26(projectPath, ".godot", "gdharness-boot.json");
|
|
38047
38289
|
}
|
|
38048
38290
|
function readScenes(projectPath) {
|
|
38049
38291
|
try {
|
|
@@ -38063,7 +38305,7 @@ function readBootNote(projectPath, scene) {
|
|
|
38063
38305
|
function writeBootNote(projectPath, scene, announcedAfterMs) {
|
|
38064
38306
|
try {
|
|
38065
38307
|
const path = bootNotePath(projectPath);
|
|
38066
|
-
|
|
38308
|
+
mkdirSync10(dirname13(path), { recursive: true });
|
|
38067
38309
|
const scenes = {
|
|
38068
38310
|
...readScenes(projectPath),
|
|
38069
38311
|
[scene]: {
|
|
@@ -38071,7 +38313,7 @@ function writeBootNote(projectPath, scene, announcedAfterMs) {
|
|
|
38071
38313
|
at: new Date().toISOString()
|
|
38072
38314
|
}
|
|
38073
38315
|
};
|
|
38074
|
-
|
|
38316
|
+
writeFileSync12(path, JSON.stringify({ scenes }, null, 2), "utf8");
|
|
38075
38317
|
} catch {}
|
|
38076
38318
|
}
|
|
38077
38319
|
function halfAsLongAgain(lastBootMs) {
|
|
@@ -38089,10 +38331,10 @@ var init_boot_note = __esm(() => {
|
|
|
38089
38331
|
});
|
|
38090
38332
|
|
|
38091
38333
|
// src/breakpoint-note.ts
|
|
38092
|
-
import { mkdirSync as
|
|
38093
|
-
import { dirname as dirname14, isAbsolute as isAbsolute7, join as
|
|
38334
|
+
import { mkdirSync as mkdirSync11, readFileSync as readFileSync24, rmSync as rmSync11, writeFileSync as writeFileSync13 } from "node:fs";
|
|
38335
|
+
import { dirname as dirname14, isAbsolute as isAbsolute7, join as join27, relative as relative4 } from "node:path";
|
|
38094
38336
|
function breakpointNotePath(projectPath) {
|
|
38095
|
-
return
|
|
38337
|
+
return join27(projectPath, ".godot", "gdharness-breakpoints.json");
|
|
38096
38338
|
}
|
|
38097
38339
|
function writeBreakpointNote(projectPath, held) {
|
|
38098
38340
|
const path = breakpointNotePath(projectPath);
|
|
@@ -38109,8 +38351,8 @@ function writeBreakpointNote(projectPath, held) {
|
|
|
38109
38351
|
lines: [...one.lines]
|
|
38110
38352
|
}))
|
|
38111
38353
|
};
|
|
38112
|
-
|
|
38113
|
-
|
|
38354
|
+
mkdirSync11(dirname14(path), { recursive: true });
|
|
38355
|
+
writeFileSync13(path, `${JSON.stringify(note, null, 2)}
|
|
38114
38356
|
`, "utf8");
|
|
38115
38357
|
} catch (error) {
|
|
38116
38358
|
console.error(`[SERVER] Could not record the breakpoints at ${path}: ${errorMessage(error)}`);
|
|
@@ -38133,7 +38375,7 @@ function readBreakpointNote(projectPath) {
|
|
|
38133
38375
|
}
|
|
38134
38376
|
const lines = Array.isArray(listed) ? listed.filter((line) => Number.isInteger(line) && line > 0) : [];
|
|
38135
38377
|
if (lines.length > 0) {
|
|
38136
|
-
held.push({ scriptPath: realAsFarAsItExists(
|
|
38378
|
+
held.push({ scriptPath: realAsFarAsItExists(join27(projectPath, script)), lines });
|
|
38137
38379
|
}
|
|
38138
38380
|
}
|
|
38139
38381
|
return held;
|
|
@@ -38151,10 +38393,10 @@ var init_breakpoint_note = __esm(() => {
|
|
|
38151
38393
|
});
|
|
38152
38394
|
|
|
38153
38395
|
// src/class-note.ts
|
|
38154
|
-
import { mkdirSync as
|
|
38155
|
-
import { dirname as dirname15, join as
|
|
38396
|
+
import { mkdirSync as mkdirSync12, readFileSync as readFileSync25 } from "node:fs";
|
|
38397
|
+
import { dirname as dirname15, join as join28 } from "node:path";
|
|
38156
38398
|
function classNotePath(projectPath) {
|
|
38157
|
-
return
|
|
38399
|
+
return join28(projectPath, ".godot", "gdharness-classes.json");
|
|
38158
38400
|
}
|
|
38159
38401
|
function readClassNote(projectPath) {
|
|
38160
38402
|
try {
|
|
@@ -38188,7 +38430,7 @@ function unfinishedScanIn(value) {
|
|
|
38188
38430
|
function writeClassNote(projectPath, note) {
|
|
38189
38431
|
const path = classNotePath(projectPath);
|
|
38190
38432
|
try {
|
|
38191
|
-
|
|
38433
|
+
mkdirSync12(dirname15(path), { recursive: true });
|
|
38192
38434
|
} catch {
|
|
38193
38435
|
return;
|
|
38194
38436
|
}
|
|
@@ -38201,9 +38443,9 @@ var init_class_note = __esm(() => {
|
|
|
38201
38443
|
|
|
38202
38444
|
// src/config-pin.ts
|
|
38203
38445
|
import { existsSync as existsSync22, readFileSync as readFileSync26 } from "node:fs";
|
|
38204
|
-
import { join as
|
|
38446
|
+
import { join as join29 } from "node:path";
|
|
38205
38447
|
function versionsNamedInConfig(projectPath) {
|
|
38206
|
-
const config =
|
|
38448
|
+
const config = join29(projectPath, ".mcp.json");
|
|
38207
38449
|
if (!existsSync22(config)) {
|
|
38208
38450
|
return [];
|
|
38209
38451
|
}
|
|
@@ -38230,16 +38472,16 @@ var init_config_pin = __esm(() => {
|
|
|
38230
38472
|
});
|
|
38231
38473
|
|
|
38232
38474
|
// src/cut-test-run.ts
|
|
38233
|
-
import { existsSync as existsSync23, mkdirSync as
|
|
38234
|
-
import { dirname as dirname16, join as
|
|
38475
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync13, readFileSync as readFileSync27, rmSync as rmSync12, writeFileSync as writeFileSync14 } from "node:fs";
|
|
38476
|
+
import { dirname as dirname16, join as join30 } from "node:path";
|
|
38235
38477
|
function keptAt(projectPath) {
|
|
38236
|
-
return
|
|
38478
|
+
return join30(projectPath, ".godot", "gdharness-reports", "cut-run.json");
|
|
38237
38479
|
}
|
|
38238
38480
|
function keepCutTestRun(projectPath, run) {
|
|
38239
38481
|
const path = keptAt(projectPath);
|
|
38240
38482
|
try {
|
|
38241
|
-
|
|
38242
|
-
|
|
38483
|
+
mkdirSync13(dirname16(path), { recursive: true });
|
|
38484
|
+
writeFileSync14(path, `${JSON.stringify(run, null, 2)}
|
|
38243
38485
|
`, "utf8");
|
|
38244
38486
|
} catch {}
|
|
38245
38487
|
}
|
|
@@ -39109,20 +39351,20 @@ var init_dap_client = __esm(() => {
|
|
|
39109
39351
|
import {
|
|
39110
39352
|
closeSync as closeSync4,
|
|
39111
39353
|
existsSync as existsSync24,
|
|
39112
|
-
mkdirSync as
|
|
39354
|
+
mkdirSync as mkdirSync14,
|
|
39113
39355
|
openSync as openSync4,
|
|
39114
39356
|
readFileSync as readFileSync28,
|
|
39115
39357
|
readSync as readSync2,
|
|
39116
39358
|
rmSync as rmSync13,
|
|
39117
39359
|
statSync as statSync15,
|
|
39118
|
-
writeFileSync as
|
|
39360
|
+
writeFileSync as writeFileSync15
|
|
39119
39361
|
} from "node:fs";
|
|
39120
|
-
import { dirname as dirname17, join as
|
|
39362
|
+
import { dirname as dirname17, join as join31 } from "node:path";
|
|
39121
39363
|
function editorLogPath(projectPath) {
|
|
39122
|
-
return
|
|
39364
|
+
return join31(projectPath, ".godot", "gdharness-editor.log");
|
|
39123
39365
|
}
|
|
39124
39366
|
function editorLogNotePath(projectPath) {
|
|
39125
|
-
return
|
|
39367
|
+
return join31(projectPath, ".godot", "gdharness-editor.json");
|
|
39126
39368
|
}
|
|
39127
39369
|
function readEditorLogNote(projectPath) {
|
|
39128
39370
|
try {
|
|
@@ -39138,7 +39380,7 @@ function readEditorLogNote(projectPath) {
|
|
|
39138
39380
|
function clearEditorLog(projectPath) {
|
|
39139
39381
|
try {
|
|
39140
39382
|
const path = editorLogPath(projectPath);
|
|
39141
|
-
|
|
39383
|
+
mkdirSync14(dirname17(path), { recursive: true });
|
|
39142
39384
|
rmSync13(path, { force: true });
|
|
39143
39385
|
rmSync13(editorLogNotePath(projectPath), { force: true });
|
|
39144
39386
|
} catch {}
|
|
@@ -39146,8 +39388,8 @@ function clearEditorLog(projectPath) {
|
|
|
39146
39388
|
function writeEditorLogNote(projectPath, pid, processStartedAt) {
|
|
39147
39389
|
try {
|
|
39148
39390
|
const path = editorLogNotePath(projectPath);
|
|
39149
|
-
|
|
39150
|
-
|
|
39391
|
+
mkdirSync14(dirname17(path), { recursive: true });
|
|
39392
|
+
writeFileSync15(path, JSON.stringify({
|
|
39151
39393
|
pid,
|
|
39152
39394
|
startedAt: new Date().toISOString(),
|
|
39153
39395
|
...processStartedAt === undefined ? {} : { processStartedAt }
|
|
@@ -39391,7 +39633,7 @@ var init_editor_log = __esm(() => {
|
|
|
39391
39633
|
});
|
|
39392
39634
|
|
|
39393
39635
|
// src/rename.ts
|
|
39394
|
-
import { existsSync as existsSync25, mkdirSync as
|
|
39636
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync15, readFileSync as readFileSync29, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync16 } from "node:fs";
|
|
39395
39637
|
import { dirname as dirname18, posix as posix4 } from "node:path";
|
|
39396
39638
|
|
|
39397
39639
|
class Script {
|
|
@@ -40107,9 +40349,9 @@ function applyRewrites(rewrites) {
|
|
|
40107
40349
|
const done = [];
|
|
40108
40350
|
try {
|
|
40109
40351
|
for (const rewrite of rewrites) {
|
|
40110
|
-
|
|
40352
|
+
mkdirSync15(dirname18(rewrite.writeTo), { recursive: true });
|
|
40111
40353
|
const staging = `${rewrite.writeTo}.gdharness-rename`;
|
|
40112
|
-
|
|
40354
|
+
writeFileSync16(staging, rewrite.after, "utf8");
|
|
40113
40355
|
renameSync3(staging, rewrite.writeTo);
|
|
40114
40356
|
let movedUid = false;
|
|
40115
40357
|
if (rewrite.writeTo !== rewrite.absolute) {
|
|
@@ -40124,7 +40366,7 @@ function applyRewrites(rewrites) {
|
|
|
40124
40366
|
} catch (error) {
|
|
40125
40367
|
for (const { rewrite, movedUid } of done.reverse()) {
|
|
40126
40368
|
try {
|
|
40127
|
-
|
|
40369
|
+
writeFileSync16(rewrite.absolute, rewrite.before, "utf8");
|
|
40128
40370
|
if (rewrite.writeTo !== rewrite.absolute) {
|
|
40129
40371
|
unlinkSync3(rewrite.writeTo);
|
|
40130
40372
|
if (movedUid) {
|
|
@@ -40163,7 +40405,7 @@ var init_rename = __esm(() => {
|
|
|
40163
40405
|
import { existsSync as existsSync26, realpathSync as realpathSync3 } from "node:fs";
|
|
40164
40406
|
import { readFile, realpath } from "node:fs/promises";
|
|
40165
40407
|
import { createConnection as createConnection3 } from "node:net";
|
|
40166
|
-
import { dirname as dirname19, join as
|
|
40408
|
+
import { dirname as dirname19, join as join32, resolve as resolve7 } from "node:path";
|
|
40167
40409
|
import { setTimeout as delay6 } from "node:timers/promises";
|
|
40168
40410
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
40169
40411
|
function initializeTimeoutMs() {
|
|
@@ -40841,7 +41083,7 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
|
|
|
40841
41083
|
} catch {
|
|
40842
41084
|
throw new ArgumentRefusal(`Project path does not exist: ${requestedProjectPath}`);
|
|
40843
41085
|
}
|
|
40844
|
-
if (!existsSync26(
|
|
41086
|
+
if (!existsSync26(join32(projectPath, "project.godot"))) {
|
|
40845
41087
|
throw new ArgumentRefusal(`Not a Godot project: ${projectPath}. Point projectPath at the directory holding project.godot.`);
|
|
40846
41088
|
}
|
|
40847
41089
|
const contained = resolveWithinProject(projectPath, scriptPathValue);
|
|
@@ -41001,8 +41243,8 @@ var init_lsp_client = __esm(() => {
|
|
|
41001
41243
|
});
|
|
41002
41244
|
|
|
41003
41245
|
// src/rename-symbol.ts
|
|
41004
|
-
import { existsSync as existsSync27, mkdirSync as
|
|
41005
|
-
import { basename as basename9, dirname as dirname20, join as
|
|
41246
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync16, readdirSync as readdirSync14, realpathSync as realpathSync4, rmSync as rmSync14, writeFileSync as writeFileSync17 } from "node:fs";
|
|
41247
|
+
import { basename as basename9, dirname as dirname20, join as join33, relative as relative5, resolve as resolve8 } from "node:path";
|
|
41006
41248
|
function resourcePathOf(projectPath, absolute) {
|
|
41007
41249
|
const inside = relative5(onDiskSpelling(projectPath), onDiskSpelling(absolute));
|
|
41008
41250
|
if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
|
|
@@ -41015,7 +41257,7 @@ function onDiskSpelling(path) {
|
|
|
41015
41257
|
let at = resolve8(path);
|
|
41016
41258
|
for (;; ) {
|
|
41017
41259
|
try {
|
|
41018
|
-
return
|
|
41260
|
+
return join33(realpathSync4.native(at), ...missing.reverse());
|
|
41019
41261
|
} catch {
|
|
41020
41262
|
const parent = dirname20(at);
|
|
41021
41263
|
if (parent === at) {
|
|
@@ -41444,15 +41686,15 @@ ${mention.kind}`;
|
|
|
41444
41686
|
};
|
|
41445
41687
|
}
|
|
41446
41688
|
function writeLeftAloneDetail(projectPath, symbol, detail) {
|
|
41447
|
-
const directory =
|
|
41448
|
-
|
|
41689
|
+
const directory = join33(projectPath, ".godot", "gdharness-renames");
|
|
41690
|
+
mkdirSync16(directory, { recursive: true });
|
|
41449
41691
|
detailWrites += 1;
|
|
41450
|
-
const path =
|
|
41451
|
-
|
|
41692
|
+
const path = join33(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
|
|
41693
|
+
writeFileSync17(path, `${JSON.stringify(detail, null, 2)}
|
|
41452
41694
|
`, "utf8");
|
|
41453
41695
|
const kept = readdirSync14(directory).filter((name) => name.endsWith(".json")).sort().reverse();
|
|
41454
41696
|
for (const old of kept.slice(10)) {
|
|
41455
|
-
rmSync14(
|
|
41697
|
+
rmSync14(join33(directory, old), { force: true });
|
|
41456
41698
|
}
|
|
41457
41699
|
return path;
|
|
41458
41700
|
}
|
|
@@ -41494,17 +41736,17 @@ var init_rename_symbol = __esm(() => {
|
|
|
41494
41736
|
// src/file-move.ts
|
|
41495
41737
|
import {
|
|
41496
41738
|
existsSync as existsSync28,
|
|
41497
|
-
mkdirSync as
|
|
41739
|
+
mkdirSync as mkdirSync17,
|
|
41498
41740
|
readdirSync as readdirSync15,
|
|
41499
41741
|
readFileSync as readFileSync30,
|
|
41500
41742
|
renameSync as renameSync4,
|
|
41501
41743
|
statSync as statSync16,
|
|
41502
|
-
writeFileSync as
|
|
41744
|
+
writeFileSync as writeFileSync18
|
|
41503
41745
|
} from "node:fs";
|
|
41504
|
-
import { basename as basename10, dirname as dirname21, extname as extname3, join as
|
|
41746
|
+
import { basename as basename10, dirname as dirname21, extname as extname3, join as join34, relative as relative6 } from "node:path";
|
|
41505
41747
|
function filesUnder(directory) {
|
|
41506
41748
|
return readdirSync15(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
41507
|
-
const path =
|
|
41749
|
+
const path = join34(directory, entry.name);
|
|
41508
41750
|
return entry.isDirectory() ? filesUnder(path) : [path];
|
|
41509
41751
|
});
|
|
41510
41752
|
}
|
|
@@ -41525,8 +41767,8 @@ function unseenByTheEngine(projectPath, path) {
|
|
|
41525
41767
|
return `${path} is under ${step}, and the engine passes over every directory whose name starts with a dot`;
|
|
41526
41768
|
}
|
|
41527
41769
|
if (at < steps.length - 1) {
|
|
41528
|
-
directory =
|
|
41529
|
-
if (existsSync28(
|
|
41770
|
+
directory = join34(directory, step);
|
|
41771
|
+
if (existsSync28(join34(directory, ".gdignore"))) {
|
|
41530
41772
|
return `${path} is under a directory holding a .gdignore, which the engine does not load`;
|
|
41531
41773
|
}
|
|
41532
41774
|
}
|
|
@@ -41538,12 +41780,12 @@ function installedAddonOf(projectPath, path) {
|
|
|
41538
41780
|
if (steps[0] !== "addons" || steps[1] === undefined) {
|
|
41539
41781
|
return null;
|
|
41540
41782
|
}
|
|
41541
|
-
return existsSync28(
|
|
41783
|
+
return existsSync28(join34(projectPath, "addons", steps[1], VERSION_MARKER)) ? `res://addons/${steps[1]}` : null;
|
|
41542
41784
|
}
|
|
41543
41785
|
function binaryResources(projectPath) {
|
|
41544
41786
|
const found = [];
|
|
41545
41787
|
const visit = (directory, prefix) => {
|
|
41546
|
-
if (existsSync28(
|
|
41788
|
+
if (existsSync28(join34(directory, ".gdignore"))) {
|
|
41547
41789
|
return;
|
|
41548
41790
|
}
|
|
41549
41791
|
for (const entry of readdirSync15(directory, { withFileTypes: true })) {
|
|
@@ -41552,7 +41794,7 @@ function binaryResources(projectPath) {
|
|
|
41552
41794
|
}
|
|
41553
41795
|
const path = `${prefix}${entry.name}`;
|
|
41554
41796
|
if (entry.isDirectory()) {
|
|
41555
|
-
visit(
|
|
41797
|
+
visit(join34(directory, entry.name), `${path}/`);
|
|
41556
41798
|
} else if (/\.(scn|res)$/i.test(entry.name)) {
|
|
41557
41799
|
found.push(path);
|
|
41558
41800
|
}
|
|
@@ -41562,7 +41804,7 @@ function binaryResources(projectPath) {
|
|
|
41562
41804
|
return found;
|
|
41563
41805
|
}
|
|
41564
41806
|
function targetPathOf(projectPath, absolute) {
|
|
41565
|
-
const inside = relative6(onDiskSpelling(projectPath),
|
|
41807
|
+
const inside = relative6(onDiskSpelling(projectPath), join34(onDiskSpelling(dirname21(absolute)), basename10(absolute)));
|
|
41566
41808
|
if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
|
|
41567
41809
|
return null;
|
|
41568
41810
|
}
|
|
@@ -41617,7 +41859,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
41617
41859
|
return { ok: false, reason: `${unseen}, so nothing there could load what was moved.` };
|
|
41618
41860
|
}
|
|
41619
41861
|
if (!isDirectory) {
|
|
41620
|
-
const owner = SIDECARS.map((sidecar) => from.endsWith(sidecar) ? from.slice(0, -sidecar.length) : null).find((path) => path !== null && existsSync28(
|
|
41862
|
+
const owner = SIDECARS.map((sidecar) => from.endsWith(sidecar) ? from.slice(0, -sidecar.length) : null).find((path) => path !== null && existsSync28(join34(projectPath, path.slice("res://".length))));
|
|
41621
41863
|
if (owner !== undefined && owner !== null) {
|
|
41622
41864
|
return { ok: false, reason: `${from} belongs to ${owner} and moves with it: move ${owner} instead.` };
|
|
41623
41865
|
}
|
|
@@ -41651,12 +41893,12 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
41651
41893
|
scripts,
|
|
41652
41894
|
moves,
|
|
41653
41895
|
named: [from],
|
|
41654
|
-
exists: (path) => existsSync28(
|
|
41896
|
+
exists: (path) => existsSync28(join34(projectPath, path.slice("res://".length)))
|
|
41655
41897
|
}, { edits: [], mentions: [] });
|
|
41656
41898
|
plan = mentionIgnored(plan, ignoredTexts(projectPath), null, [from]);
|
|
41657
41899
|
const binaries = [];
|
|
41658
41900
|
for (const path of binaryResources(projectPath)) {
|
|
41659
|
-
const bytes = readFileSync30(
|
|
41901
|
+
const bytes = readFileSync30(join34(projectPath, path.slice("res://".length)));
|
|
41660
41902
|
const names = [...moves.keys()].find((moving) => bytes.includes(moving));
|
|
41661
41903
|
if (names !== undefined) {
|
|
41662
41904
|
binaries.push({
|
|
@@ -41675,7 +41917,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
|
|
|
41675
41917
|
}));
|
|
41676
41918
|
const left = leftAloneReport(mentions);
|
|
41677
41919
|
const rewrites = rewritesOf(plan, texts, null);
|
|
41678
|
-
const absoluteOf = (path) =>
|
|
41920
|
+
const absoluteOf = (path) => join34(projectPath, path.slice("res://".length));
|
|
41679
41921
|
const rewritten = new Map(rewrites.map((rewrite) => [rewrite.path, rewrite]));
|
|
41680
41922
|
const moved = [...moves].map(([source, target]) => {
|
|
41681
41923
|
const rewrite = rewritten.get(source);
|
|
@@ -41734,7 +41976,7 @@ function applyFileMove(outcome) {
|
|
|
41734
41976
|
}
|
|
41735
41977
|
const undo = [];
|
|
41736
41978
|
const rename = (source, target) => {
|
|
41737
|
-
|
|
41979
|
+
mkdirSync17(dirname21(target), { recursive: true });
|
|
41738
41980
|
renameSync4(source, target);
|
|
41739
41981
|
undo.push(() => {
|
|
41740
41982
|
renameSync4(target, source);
|
|
@@ -41743,10 +41985,10 @@ function applyFileMove(outcome) {
|
|
|
41743
41985
|
try {
|
|
41744
41986
|
for (const rewrite of outcome.inPlace) {
|
|
41745
41987
|
const staging = `${rewrite.absolute}.gdharness-move`;
|
|
41746
|
-
|
|
41988
|
+
writeFileSync18(staging, rewrite.after, "utf8");
|
|
41747
41989
|
renameSync4(staging, rewrite.absolute);
|
|
41748
41990
|
undo.push(() => {
|
|
41749
|
-
|
|
41991
|
+
writeFileSync18(rewrite.absolute, rewrite.before, "utf8");
|
|
41750
41992
|
});
|
|
41751
41993
|
}
|
|
41752
41994
|
if (outcome.directory !== null) {
|
|
@@ -41758,9 +42000,9 @@ function applyFileMove(outcome) {
|
|
|
41758
42000
|
}
|
|
41759
42001
|
if (file.text !== null) {
|
|
41760
42002
|
const { before, after } = file.text;
|
|
41761
|
-
|
|
42003
|
+
writeFileSync18(file.absoluteTo, after, "utf8");
|
|
41762
42004
|
undo.push(() => {
|
|
41763
|
-
|
|
42005
|
+
writeFileSync18(file.absoluteTo, before, "utf8");
|
|
41764
42006
|
});
|
|
41765
42007
|
}
|
|
41766
42008
|
}
|
|
@@ -45462,16 +45704,16 @@ var DEBUG_PREFIX = "debug ";
|
|
|
45462
45704
|
var init_keeper_channel = () => {};
|
|
45463
45705
|
|
|
45464
45706
|
// src/lifecycle-log.ts
|
|
45465
|
-
import { appendFileSync, mkdirSync as
|
|
45466
|
-
import { dirname as dirname22, join as
|
|
45707
|
+
import { appendFileSync, mkdirSync as mkdirSync18, renameSync as renameSync5, statSync as statSync17 } from "node:fs";
|
|
45708
|
+
import { dirname as dirname22, join as join35 } from "node:path";
|
|
45467
45709
|
import process14 from "node:process";
|
|
45468
45710
|
function lifecycleLogPath() {
|
|
45469
|
-
return
|
|
45711
|
+
return join35(runtimeDirectory(), "servers.log");
|
|
45470
45712
|
}
|
|
45471
45713
|
function writeLifecycle(event, fields = {}) {
|
|
45472
45714
|
const path = lifecycleLogPath();
|
|
45473
45715
|
try {
|
|
45474
|
-
|
|
45716
|
+
mkdirSync18(dirname22(path), { recursive: true });
|
|
45475
45717
|
if ((statSync17(path, { throwIfNoEntry: false })?.size ?? 0) > LIFECYCLE_LOG_LIMIT) {
|
|
45476
45718
|
try {
|
|
45477
45719
|
renameSync5(path, `${path}.1`);
|
|
@@ -45830,16 +46072,16 @@ var init_prompts = __esm(() => {
|
|
|
45830
46072
|
});
|
|
45831
46073
|
|
|
45832
46074
|
// src/restart-note.ts
|
|
45833
|
-
import { mkdirSync as
|
|
45834
|
-
import { dirname as dirname23, join as
|
|
46075
|
+
import { mkdirSync as mkdirSync19, readFileSync as readFileSync31, rmSync as rmSync15, writeFileSync as writeFileSync19 } from "node:fs";
|
|
46076
|
+
import { dirname as dirname23, join as join36 } from "node:path";
|
|
45835
46077
|
function restartNotePath(projectPath) {
|
|
45836
|
-
return
|
|
46078
|
+
return join36(projectPath, ".godot", "gdharness-restart.json");
|
|
45837
46079
|
}
|
|
45838
46080
|
function noteRestartBegun(note) {
|
|
45839
46081
|
const path = restartNotePath(note.projectPath);
|
|
45840
46082
|
try {
|
|
45841
|
-
|
|
45842
|
-
|
|
46083
|
+
mkdirSync19(dirname23(path), { recursive: true });
|
|
46084
|
+
writeFileSync19(path, `${JSON.stringify(note, null, 2)}
|
|
45843
46085
|
`, "utf8");
|
|
45844
46086
|
} catch (error) {
|
|
45845
46087
|
console.error(`[SERVER] Could not record the restart at ${path}: ${errorMessage(error)}`);
|
|
@@ -45873,10 +46115,10 @@ var init_restart_note = __esm(() => {
|
|
|
45873
46115
|
|
|
45874
46116
|
// src/run-record.ts
|
|
45875
46117
|
import { createHash as createHash7 } from "node:crypto";
|
|
45876
|
-
import { mkdirSync as
|
|
45877
|
-
import { basename as basename11, join as
|
|
46118
|
+
import { mkdirSync as mkdirSync20, openSync as openSync5, readdirSync as readdirSync16, readFileSync as readFileSync32, rmSync as rmSync16, statSync as statSync18 } from "node:fs";
|
|
46119
|
+
import { basename as basename11, join as join37, resolve as resolve9 } from "node:path";
|
|
45878
46120
|
function runsDirectory() {
|
|
45879
|
-
return
|
|
46121
|
+
return join37(runtimeDirectory(), "runs");
|
|
45880
46122
|
}
|
|
45881
46123
|
function projectKey(projectPath) {
|
|
45882
46124
|
const real = realAsFarAsItExists(resolve9(projectPath)).replace(/[\\/]+$/, "");
|
|
@@ -45884,14 +46126,14 @@ function projectKey(projectPath) {
|
|
|
45884
46126
|
return createHash7("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
|
|
45885
46127
|
}
|
|
45886
46128
|
function runRecordPath(projectPath, directory = runtimeDirectory()) {
|
|
45887
|
-
return
|
|
46129
|
+
return join37(directory, "runs", `note-${projectKey(projectPath)}.json`);
|
|
45888
46130
|
}
|
|
45889
46131
|
function latestRunPath(servedBy, directory = runtimeDirectory()) {
|
|
45890
|
-
return
|
|
46132
|
+
return join37(directory, "runs", `latest-${projectKey(servedBy)}.json`);
|
|
45891
46133
|
}
|
|
45892
46134
|
function writeLatestRun(servedBy, latest) {
|
|
45893
46135
|
try {
|
|
45894
|
-
|
|
46136
|
+
mkdirSync20(runsDirectory(), { recursive: true });
|
|
45895
46137
|
writeWhole(latestRunPath(servedBy), JSON.stringify(latest, null, 2));
|
|
45896
46138
|
} catch {}
|
|
45897
46139
|
}
|
|
@@ -45911,24 +46153,24 @@ function readLatestRun(servedBy, directory = runtimeDirectory()) {
|
|
|
45911
46153
|
}
|
|
45912
46154
|
}
|
|
45913
46155
|
function openTranscript(startedAt) {
|
|
45914
|
-
|
|
45915
|
-
const path =
|
|
46156
|
+
mkdirSync20(runsDirectory(), { recursive: true });
|
|
46157
|
+
const path = join37(runsDirectory(), `run-${startedAt}.log`);
|
|
45916
46158
|
return { path, fd: openSync5(path, "a") };
|
|
45917
46159
|
}
|
|
45918
46160
|
function profilePath(startedAt) {
|
|
45919
|
-
return
|
|
46161
|
+
return join37(runsDirectory(), `run-${startedAt}.profile.json`);
|
|
45920
46162
|
}
|
|
45921
46163
|
function profileMarkPath(startedAt) {
|
|
45922
|
-
return
|
|
46164
|
+
return join37(runsDirectory(), `run-${startedAt}.profile-mark.json`);
|
|
45923
46165
|
}
|
|
45924
46166
|
function noteIsAbout(note, playing) {
|
|
45925
46167
|
return note.play !== undefined && playing !== null && note.play.play === playing.play && note.play.editorPid === playing.editorPid;
|
|
45926
46168
|
}
|
|
45927
46169
|
function editorNotePath(projectPath, directory = runtimeDirectory()) {
|
|
45928
|
-
return
|
|
46170
|
+
return join37(directory, "runs", `editor-${projectKey(projectPath)}.json`);
|
|
45929
46171
|
}
|
|
45930
46172
|
function writeEditorRunNote(note) {
|
|
45931
|
-
|
|
46173
|
+
mkdirSync20(runsDirectory(), { recursive: true });
|
|
45932
46174
|
const path = editorNotePath(note.projectPath);
|
|
45933
46175
|
if (!writeWhole(path, JSON.stringify(note, null, 2))) {
|
|
45934
46176
|
throw new Error(`the editor run note could not be written at ${path}`);
|
|
@@ -45978,13 +46220,13 @@ function everyRunRecord() {
|
|
|
45978
46220
|
for (const directory of runtimeDirectories()) {
|
|
45979
46221
|
let entries;
|
|
45980
46222
|
try {
|
|
45981
|
-
entries = readdirSync16(
|
|
46223
|
+
entries = readdirSync16(join37(directory, "runs"));
|
|
45982
46224
|
} catch {
|
|
45983
46225
|
continue;
|
|
45984
46226
|
}
|
|
45985
46227
|
for (const entry of entries) {
|
|
45986
46228
|
if (entry.startsWith("note-") && entry.endsWith(".json")) {
|
|
45987
|
-
const record = recordAt(
|
|
46229
|
+
const record = recordAt(join37(directory, "runs", entry));
|
|
45988
46230
|
if (record !== null) {
|
|
45989
46231
|
found.push(record);
|
|
45990
46232
|
}
|
|
@@ -46252,7 +46494,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
|
|
|
46252
46494
|
if (!transcript && !entry.endsWith(".partial")) {
|
|
46253
46495
|
continue;
|
|
46254
46496
|
}
|
|
46255
|
-
const path =
|
|
46497
|
+
const path = join37(directory, entry);
|
|
46256
46498
|
if (kept.has(path) || ageMs(path, now) < keepMs) {
|
|
46257
46499
|
continue;
|
|
46258
46500
|
}
|
|
@@ -46502,18 +46744,18 @@ var init_server_instructions = __esm(() => {
|
|
|
46502
46744
|
|
|
46503
46745
|
// src/test-selection.ts
|
|
46504
46746
|
import { existsSync as existsSync29, readFileSync as readFileSync34 } from "node:fs";
|
|
46505
|
-
import { basename as basename12, join as
|
|
46747
|
+
import { basename as basename12, join as join38 } from "node:path";
|
|
46506
46748
|
function casesIn(projectPath, suite) {
|
|
46507
|
-
const text = readFileSync34(
|
|
46749
|
+
const text = readFileSync34(join38(projectPath, suite.slice("res://".length)), "utf8");
|
|
46508
46750
|
return [...text.matchAll(/^func\s+(test_\w*)\s*\(/gm)].map((match) => match[1] ?? "");
|
|
46509
46751
|
}
|
|
46510
46752
|
function scriptsWithoutCases(projectPath, asked) {
|
|
46511
|
-
return asked.filter((path) => path.endsWith(".gd") && existsSync29(
|
|
46753
|
+
return asked.filter((path) => path.endsWith(".gd") && existsSync29(join38(projectPath, path.slice("res://".length))) && casesIn(projectPath, path).length === 0);
|
|
46512
46754
|
}
|
|
46513
46755
|
function suitesUnder(projectPath, asked) {
|
|
46514
46756
|
const scripts = projectFiles(projectPath, [".gd"]).map((path) => `res://${path}`);
|
|
46515
46757
|
return [
|
|
46516
|
-
...new Set(asked.flatMap((path) => path.endsWith(".gd") ? existsSync29(
|
|
46758
|
+
...new Set(asked.flatMap((path) => path.endsWith(".gd") ? existsSync29(join38(projectPath, path.slice("res://".length))) ? [path] : [] : scripts.filter((script) => script.startsWith(`${path}/`))))
|
|
46517
46759
|
];
|
|
46518
46760
|
}
|
|
46519
46761
|
function selectTests(projectPath, asked, given) {
|
|
@@ -46576,7 +46818,7 @@ import {
|
|
|
46576
46818
|
copyFileSync,
|
|
46577
46819
|
existsSync as existsSync30,
|
|
46578
46820
|
fstatSync as fstatSync2,
|
|
46579
|
-
mkdirSync as
|
|
46821
|
+
mkdirSync as mkdirSync21,
|
|
46580
46822
|
openSync as openSync6,
|
|
46581
46823
|
readdirSync as readdirSync17,
|
|
46582
46824
|
readFileSync as readFileSync35,
|
|
@@ -46584,9 +46826,9 @@ import {
|
|
|
46584
46826
|
realpathSync as realpathSync5,
|
|
46585
46827
|
rmdirSync as rmdirSync2,
|
|
46586
46828
|
statSync as statSync19,
|
|
46587
|
-
writeFileSync as
|
|
46829
|
+
writeFileSync as writeFileSync20
|
|
46588
46830
|
} from "node:fs";
|
|
46589
|
-
import { basename as basename13, dirname as dirname24, extname as extname4, isAbsolute as isAbsolute8, join as
|
|
46831
|
+
import { basename as basename13, dirname as dirname24, extname as extname4, isAbsolute as isAbsolute8, join as join39, normalize as normalize2, relative as relative7 } from "node:path";
|
|
46590
46832
|
import { setTimeout as delay7 } from "node:timers/promises";
|
|
46591
46833
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
46592
46834
|
function editorRestartTimeoutMs() {
|
|
@@ -46645,7 +46887,7 @@ function writtenFileRefusal(path, rule) {
|
|
|
46645
46887
|
if (!existsSync30(directory)) {
|
|
46646
46888
|
return `${named}'s directory ${dirname24(path)} does not exist; ${what} creates no directories.`;
|
|
46647
46889
|
}
|
|
46648
|
-
const where =
|
|
46890
|
+
const where = join39(directory, basename13(path));
|
|
46649
46891
|
const inside = rule.projects.find((project) => project !== "" && isWithinRoot(realAsFarAsItExists(project), where));
|
|
46650
46892
|
if (inside !== undefined) {
|
|
46651
46893
|
return `${named} ${path} is inside the project at ${inside}; ${what} is saved outside it, so it never becomes part of the game.`;
|
|
@@ -47292,7 +47534,7 @@ function notRunNote(notRun, failFast) {
|
|
|
47292
47534
|
function presetsIn(projectPath) {
|
|
47293
47535
|
let text;
|
|
47294
47536
|
try {
|
|
47295
|
-
text = readFileSync35(
|
|
47537
|
+
text = readFileSync35(join39(projectPath, "export_presets.cfg"), "utf8");
|
|
47296
47538
|
} catch {
|
|
47297
47539
|
return null;
|
|
47298
47540
|
}
|
|
@@ -47373,7 +47615,7 @@ function filesIn(folder) {
|
|
|
47373
47615
|
try {
|
|
47374
47616
|
for (const entry of readdirSync17(folder, { withFileTypes: true })) {
|
|
47375
47617
|
if (entry.isFile()) {
|
|
47376
|
-
found.set(entry.name, statSync19(
|
|
47618
|
+
found.set(entry.name, statSync19(join39(folder, entry.name)).mtimeMs);
|
|
47377
47619
|
}
|
|
47378
47620
|
}
|
|
47379
47621
|
} catch {}
|
|
@@ -47413,7 +47655,7 @@ function unsavedInEditorNote(scripts) {
|
|
|
47413
47655
|
return `${named} ${one ? "is" : "are"} open in the editor's script ${one ? "tab" : "tabs"} with unsaved edits, and saving ${one ? "that tab" : "them"} there writes ${one ? "its" : "their"} text over what was written here: close ${one ? "it" : "them"} without saving, or save first and edit again.`;
|
|
47414
47656
|
}
|
|
47415
47657
|
function editorAddonMissing(projectPath) {
|
|
47416
|
-
if (!existsSync30(
|
|
47658
|
+
if (!existsSync30(join39(projectPath, "project.godot"))) {
|
|
47417
47659
|
return null;
|
|
47418
47660
|
}
|
|
47419
47661
|
const report = inspectProject(projectPath);
|
|
@@ -47441,7 +47683,7 @@ function rewriteBusNamed(projectPath, from, to) {
|
|
|
47441
47683
|
const giving = [`bus = &"${quoted(from)}"`, `bus = "${quoted(from)}"`];
|
|
47442
47684
|
const rewritten = [];
|
|
47443
47685
|
for (const file of projectFiles(projectPath, [".tscn", ".tres"])) {
|
|
47444
|
-
const absolute =
|
|
47686
|
+
const absolute = join39(projectPath, file);
|
|
47445
47687
|
const lines = readFileSync35(absolute, "utf8").split(`
|
|
47446
47688
|
`);
|
|
47447
47689
|
const at = [];
|
|
@@ -47454,14 +47696,14 @@ function rewriteBusNamed(projectPath, from, to) {
|
|
|
47454
47696
|
return `bus = &"${quoted(to)}"${ending}`;
|
|
47455
47697
|
});
|
|
47456
47698
|
if (at.length > 0) {
|
|
47457
|
-
|
|
47699
|
+
writeFileSync20(absolute, after.join(`
|
|
47458
47700
|
`));
|
|
47459
47701
|
rewritten.push({ file: `res://${file}`, lines: at });
|
|
47460
47702
|
}
|
|
47461
47703
|
}
|
|
47462
47704
|
const namedInScripts = [];
|
|
47463
47705
|
for (const file of projectFiles(projectPath, [".gd"])) {
|
|
47464
|
-
readFileSync35(
|
|
47706
|
+
readFileSync35(join39(projectPath, file), "utf8").split(`
|
|
47465
47707
|
`).forEach((line, index) => {
|
|
47466
47708
|
if (line.includes(`"${from}"`)) {
|
|
47467
47709
|
namedInScripts.push({ file: `res://${file}`, line: index + 1, text: line.trim() });
|
|
@@ -47736,7 +47978,7 @@ function profileFrameFunctions(projectPath) {
|
|
|
47736
47978
|
let projectGodot = null;
|
|
47737
47979
|
if (projectPath !== null) {
|
|
47738
47980
|
try {
|
|
47739
|
-
projectGodot = readFileSync35(
|
|
47981
|
+
projectGodot = readFileSync35(join39(projectPath, "project.godot"), "utf8");
|
|
47740
47982
|
} catch {}
|
|
47741
47983
|
}
|
|
47742
47984
|
return frameFunctionsFor(queuedMessagesOf(projectGodot));
|
|
@@ -47919,7 +48161,7 @@ function foldedEntriesAnswer(log, floor = "warning") {
|
|
|
47919
48161
|
}
|
|
47920
48162
|
function gdUnitFailsOnScriptErrors(projectPath) {
|
|
47921
48163
|
try {
|
|
47922
|
-
return settingKeys(readFileSync35(
|
|
48164
|
+
return settingKeys(readFileSync35(join39(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
|
|
47923
48165
|
} catch {
|
|
47924
48166
|
return true;
|
|
47925
48167
|
}
|
|
@@ -47934,7 +48176,7 @@ function uncaughtNote(uncaught) {
|
|
|
47934
48176
|
}
|
|
47935
48177
|
function elsewhereIn(projectPath, asked) {
|
|
47936
48178
|
const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
|
|
47937
|
-
if (wanted === "" || existsSync30(
|
|
48179
|
+
if (wanted === "" || existsSync30(join39(projectPath, wanted))) {
|
|
47938
48180
|
return "";
|
|
47939
48181
|
}
|
|
47940
48182
|
const root = (wanted.split("/")[0] ?? "").toLowerCase();
|
|
@@ -47957,7 +48199,7 @@ function windowNoteFor(needing, sharing) {
|
|
|
47957
48199
|
return `${named(needing)} ${one ? "needs" : "need"} a window, as gdharness test reads ${one ? "it" : "them"}, so the run had one: the engine ran without --headless, at the size project.godot gives its viewport, with no sound${process.platform === "win32" ? ", on a desktop of its own, where its window neither shows nor takes the keyboard" : ""}. ${why}${shared}`;
|
|
47958
48200
|
}
|
|
47959
48201
|
function testPathsIn(projectPath, given) {
|
|
47960
|
-
const defaults = TEST_DIRECTORIES.filter((directory) => existsSync30(
|
|
48202
|
+
const defaults = TEST_DIRECTORIES.filter((directory) => existsSync30(join39(projectPath, directory)));
|
|
47961
48203
|
const entries = given === undefined || given === null || given === "" ? defaults.length > 0 ? defaults : TEST_DIRECTORIES : given;
|
|
47962
48204
|
const listed = Array.isArray(entries) ? entries : [entries];
|
|
47963
48205
|
if (listed.length === 0) {
|
|
@@ -48069,7 +48311,7 @@ function needsItsOwnProcess(asked) {
|
|
|
48069
48311
|
class GodotServer {
|
|
48070
48312
|
stdio = null;
|
|
48071
48313
|
locator = new GodotLocator;
|
|
48072
|
-
operationsScript =
|
|
48314
|
+
operationsScript = join39(__dirname2, "godot", "operations", "godot_operations.gd");
|
|
48073
48315
|
exportOptionsRead = new Map;
|
|
48074
48316
|
heldWhilePending = new Set;
|
|
48075
48317
|
evaluatedWhilePending = new Set;
|
|
@@ -48340,7 +48582,7 @@ class GodotServer {
|
|
|
48340
48582
|
}
|
|
48341
48583
|
noteTheStart() {
|
|
48342
48584
|
const cwd = process.cwd();
|
|
48343
|
-
const mcpJson =
|
|
48585
|
+
const mcpJson = join39(cwd, ".mcp.json");
|
|
48344
48586
|
const announced = this.ownProject === null ? null : readAnnouncement(announcementPath(this.ownProject));
|
|
48345
48587
|
const predecessor = announced !== null && announced.pid !== process.pid && alive(announced.pid) ? announced.pid : null;
|
|
48346
48588
|
writeLifecycle("started", {
|
|
@@ -48488,7 +48730,7 @@ class GodotServer {
|
|
|
48488
48730
|
return checked.response;
|
|
48489
48731
|
}
|
|
48490
48732
|
const named = args["projectPath"];
|
|
48491
|
-
if (typeof named === "string" && existsSync30(
|
|
48733
|
+
if (typeof named === "string" && existsSync30(join39(named, "project.godot"))) {
|
|
48492
48734
|
this.lastProjectPath = named;
|
|
48493
48735
|
}
|
|
48494
48736
|
this.updates.refresh();
|
|
@@ -48524,8 +48766,8 @@ class GodotServer {
|
|
|
48524
48766
|
let notSaved;
|
|
48525
48767
|
try {
|
|
48526
48768
|
savedAt = answer.content.map((item, index) => {
|
|
48527
|
-
const file =
|
|
48528
|
-
|
|
48769
|
+
const file = join39(directory, item.type === "text" ? `${stem}-${index}.txt` : `${stem}-${index}.${item.mimeType.split("/")[1] ?? "bin"}`);
|
|
48770
|
+
writeFileSync20(file, item.type === "text" ? item.text : Buffer.from(item.data, "base64"));
|
|
48529
48771
|
return file;
|
|
48530
48772
|
});
|
|
48531
48773
|
} catch (error) {
|
|
@@ -49208,7 +49450,7 @@ class GodotServer {
|
|
|
49208
49450
|
if (path === undefined) {
|
|
49209
49451
|
return { ok: false, response: this.createErrorResponse("projectPath is required.") };
|
|
49210
49452
|
}
|
|
49211
|
-
const file =
|
|
49453
|
+
const file = join39(path, "project.godot");
|
|
49212
49454
|
if (!existsSync30(file)) {
|
|
49213
49455
|
return {
|
|
49214
49456
|
ok: false,
|
|
@@ -50077,7 +50319,7 @@ class GodotServer {
|
|
|
50077
50319
|
const platform = presetPlatform(project.value.path, preset);
|
|
50078
50320
|
if (platform !== null) {
|
|
50079
50321
|
const known = await this.exportOptions(platform, {
|
|
50080
|
-
presetsFile:
|
|
50322
|
+
presetsFile: join39(project.value.path, "export_presets.cfg"),
|
|
50081
50323
|
preset,
|
|
50082
50324
|
asked: options
|
|
50083
50325
|
});
|
|
@@ -50128,7 +50370,7 @@ ${platform}`;
|
|
|
50128
50370
|
}
|
|
50129
50371
|
const empty = scratchDirectory("export");
|
|
50130
50372
|
try {
|
|
50131
|
-
|
|
50373
|
+
writeFileSync20(join39(empty, "project.godot"), `config_version=5
|
|
50132
50374
|
`);
|
|
50133
50375
|
const outcome = await runOperation({ godotPath: engine.value, script: this.operationsScript, debug: DEBUG_MODE, editor: true }, "export_options", { platform, ...standing }, empty);
|
|
50134
50376
|
const options = outcome.ok ? outcome.payload["options"] : undefined;
|
|
@@ -50167,7 +50409,7 @@ ${platform}`;
|
|
|
50167
50409
|
}
|
|
50168
50410
|
const folder = dirname24(output.absolutePath);
|
|
50169
50411
|
try {
|
|
50170
|
-
|
|
50412
|
+
mkdirSync21(folder, { recursive: true });
|
|
50171
50413
|
} catch (error) {
|
|
50172
50414
|
return this.createErrorResponse(`Cannot create ${dirname24(output.relativePath)} to export into: ${errorMessage(error)}`);
|
|
50173
50415
|
}
|
|
@@ -50184,7 +50426,7 @@ ${platform}`;
|
|
|
50184
50426
|
const exportArgs = [
|
|
50185
50427
|
"--headless",
|
|
50186
50428
|
"--log-file",
|
|
50187
|
-
|
|
50429
|
+
join39(exportLogs, "engine.log"),
|
|
50188
50430
|
"--path",
|
|
50189
50431
|
project.value.path,
|
|
50190
50432
|
pack ? "--export-pack" : debug ? "--export-debug" : "--export-release",
|
|
@@ -50275,8 +50517,9 @@ ${platform}`;
|
|
|
50275
50517
|
if (movie !== null) {
|
|
50276
50518
|
return this.createErrorResponse(movie);
|
|
50277
50519
|
}
|
|
50278
|
-
const missing = paths.value.filter((path) => !existsSync30(
|
|
50279
|
-
const
|
|
50520
|
+
const missing = paths.value.filter((path) => !existsSync30(join39(project.value.path, path.slice("res://".length))));
|
|
50521
|
+
const projectScripts = new ProjectScripts(project.value.path);
|
|
50522
|
+
const asked = projectScripts.searchedAsTold(paths.value.filter((path) => !missing.includes(path)));
|
|
50280
50523
|
const unusable = gdUnit4Unusable(project.value.path);
|
|
50281
50524
|
if (asked.length === 0) {
|
|
50282
50525
|
const verdict = `nothing at ${missing.join(" and ")}`;
|
|
@@ -50315,7 +50558,7 @@ ${platform}`;
|
|
|
50315
50558
|
return this.createErrorResponse(selection.reason);
|
|
50316
50559
|
}
|
|
50317
50560
|
const leftOut = new Set(readStringArray(args, "ignore") ?? []);
|
|
50318
|
-
const needs = windowNeedsOf(
|
|
50561
|
+
const needs = windowNeedsOf(projectScripts, selection.value?.suites ?? asked);
|
|
50319
50562
|
const needing = needs.needing.filter((path) => !leftOut.has(basename13(path, ".gd")));
|
|
50320
50563
|
const noDisplay = resolveHeadless(undefined, { platform: process.platform, variables: process.env });
|
|
50321
50564
|
const windowed = needing.length > 0 && !noDisplay;
|
|
@@ -50347,14 +50590,14 @@ ${platform}`;
|
|
|
50347
50590
|
const reports = `res://.godot/${ours}`;
|
|
50348
50591
|
const ignored = readStringArray(args, "ignore") ?? [];
|
|
50349
50592
|
const userData = scratchDirectory("tests");
|
|
50350
|
-
const suitesFile =
|
|
50351
|
-
const skippingFile =
|
|
50352
|
-
const engineLog =
|
|
50353
|
-
const expectedFile =
|
|
50354
|
-
|
|
50593
|
+
const suitesFile = join39(userData, "suites.txt");
|
|
50594
|
+
const skippingFile = join39(userData, "skipping.txt");
|
|
50595
|
+
const engineLog = join39(userData, "engine.log");
|
|
50596
|
+
const expectedFile = join39(userData, "expected.json");
|
|
50597
|
+
writeFileSync20(suitesFile, `${(selection.value?.suites ?? asked).join(`
|
|
50355
50598
|
`)}
|
|
50356
50599
|
`);
|
|
50357
|
-
|
|
50600
|
+
writeFileSync20(skippingFile, `${[...ignored, ...selection.value?.ignore ?? []].join(`
|
|
50358
50601
|
`)}
|
|
50359
50602
|
`);
|
|
50360
50603
|
const viewport = windowed ? viewportOf(project.value.path) : null;
|
|
@@ -50391,11 +50634,11 @@ ${platform}`;
|
|
|
50391
50634
|
const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? 600000;
|
|
50392
50635
|
this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
50393
50636
|
const helperFiles = {
|
|
50394
|
-
spec:
|
|
50395
|
-
pid:
|
|
50396
|
-
error:
|
|
50397
|
-
stop:
|
|
50398
|
-
step:
|
|
50637
|
+
spec: join39(userData, "spec"),
|
|
50638
|
+
pid: join39(userData, "pid"),
|
|
50639
|
+
error: join39(userData, "error"),
|
|
50640
|
+
stop: join39(userData, "stop"),
|
|
50641
|
+
step: join39(userData, "step")
|
|
50399
50642
|
};
|
|
50400
50643
|
const onDesktop = windowed && process.platform === "win32";
|
|
50401
50644
|
const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData), onDesktop ? { directory: project.value.path, files: helperFiles } : undefined);
|
|
@@ -50446,12 +50689,12 @@ ${platform}`;
|
|
|
50446
50689
|
await run.settled;
|
|
50447
50690
|
const helperFailed = onDesktop ? helperFailure(helperFiles.error) ?? (existsSync30(helperFiles.pid) ? null : `it never started, and ${helperStep(helperFiles.step)}`) : null;
|
|
50448
50691
|
if (helperFailed !== null) {
|
|
50449
|
-
discard(
|
|
50692
|
+
discard(join39(project.value.path, ".godot", ...ours.split("/")), userData);
|
|
50450
50693
|
return this.createErrorResponse(`No tests ran: ${named(needing)} ${needing.length === 1 ? "needs" : "need"} a window, and the engine could not be started on a desktop of its own: ${helperFailed}`);
|
|
50451
50694
|
}
|
|
50452
50695
|
const tookMs = Date.now() - began;
|
|
50453
50696
|
const notCompiled = stopped.for ?? run.log.all.map((entry) => autoloadNotCompiled(entry, autoloaded)).find((found) => found !== null) ?? null;
|
|
50454
|
-
const reportsDir =
|
|
50697
|
+
const reportsDir = join39(project.value.path, ".godot", ...ours.split("/"));
|
|
50455
50698
|
let report = null;
|
|
50456
50699
|
let reportProblem = null;
|
|
50457
50700
|
const casePrinted = withNestedErrors(readRun(existsSync30(engineLog) ? readFileSync35(engineLog, "utf8") : ""), run.log.all);
|
|
@@ -50460,7 +50703,7 @@ ${platform}`;
|
|
|
50460
50703
|
const written = existsSync30(reportsDir) && notCompiled === null ? readdirSync17(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
|
|
50461
50704
|
const newest = written.at(-1);
|
|
50462
50705
|
if (newest !== undefined) {
|
|
50463
|
-
report = parseJUnit(readFileSync35(
|
|
50706
|
+
report = parseJUnit(readFileSync35(join39(reportsDir, newest, "results.xml"), "utf8"));
|
|
50464
50707
|
}
|
|
50465
50708
|
} catch (error) {
|
|
50466
50709
|
reportProblem = errorMessage(error);
|
|
@@ -51202,7 +51445,7 @@ ${error.text}`;
|
|
|
51202
51445
|
...Array.isArray(stack) ? {
|
|
51203
51446
|
stack: stack.map((frame) => ({
|
|
51204
51447
|
...frame,
|
|
51205
|
-
...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path:
|
|
51448
|
+
...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join39(projectPath, frame["script"].slice("res://".length)) } : {}
|
|
51206
51449
|
}))
|
|
51207
51450
|
} : {}
|
|
51208
51451
|
});
|
|
@@ -51531,7 +51774,7 @@ ${error.text}`;
|
|
|
51531
51774
|
return null;
|
|
51532
51775
|
}
|
|
51533
51776
|
try {
|
|
51534
|
-
return settingKeys(readFileSync35(
|
|
51777
|
+
return settingKeys(readFileSync35(join39(projectPath, "project.godot"), "utf8"));
|
|
51535
51778
|
} catch {
|
|
51536
51779
|
return null;
|
|
51537
51780
|
}
|
|
@@ -51983,7 +52226,7 @@ ${error.text}`;
|
|
|
51983
52226
|
}
|
|
51984
52227
|
async runtimeUp(projectPath, before, budget, withArgs) {
|
|
51985
52228
|
const budgetMs = budget.ms;
|
|
51986
|
-
if (!existsSync30(
|
|
52229
|
+
if (!existsSync30(join39(projectPath, RUNTIME_AUTOLOAD.path))) {
|
|
51987
52230
|
return runtimeVerdict(null, {
|
|
51988
52231
|
addon: false,
|
|
51989
52232
|
budgetMs,
|
|
@@ -52455,7 +52698,7 @@ ${error.text}`;
|
|
|
52455
52698
|
if (onDesktop === undefined) {
|
|
52456
52699
|
child.kill();
|
|
52457
52700
|
} else {
|
|
52458
|
-
|
|
52701
|
+
writeFileSync20(onDesktop.files.stop, "");
|
|
52459
52702
|
}
|
|
52460
52703
|
};
|
|
52461
52704
|
const cancelled = callSignal();
|
|
@@ -52982,7 +53225,7 @@ ${error.text}`;
|
|
|
52982
53225
|
};
|
|
52983
53226
|
const going = await this.runStillGoing(run);
|
|
52984
53227
|
const projectPath = run.projectPath;
|
|
52985
|
-
const projectGone = projectPath !== null && !existsSync30(
|
|
53228
|
+
const projectGone = projectPath !== null && !existsSync30(join39(projectPath, "project.godot"));
|
|
52986
53229
|
if (projectGone && callersName !== undefined) {
|
|
52987
53230
|
return this.createErrorResponse(`callersOf reads the project's scripts, and the run's project at ${projectPath} has no project.godot any more.`, ["editor_run profile without callersOf answers with the profile itself"]);
|
|
52988
53231
|
}
|
|
@@ -53367,7 +53610,7 @@ ${error.text}`;
|
|
|
53367
53610
|
notes.push(endedWithoutACode({
|
|
53368
53611
|
throughEditor: run.throughEditor,
|
|
53369
53612
|
announced: announced !== undefined || run.announcedPid !== undefined,
|
|
53370
|
-
couldAnnounce: run.projectPath !== null && existsSync30(
|
|
53613
|
+
couldAnnounce: run.projectPath !== null && existsSync30(join39(run.projectPath, RUNTIME_AUTOLOAD.path)),
|
|
53371
53614
|
endedHere: typeof run.endedHere === "string",
|
|
53372
53615
|
pickedUp: run.pickedUp === true
|
|
53373
53616
|
}));
|
|
@@ -54784,7 +55027,7 @@ ${error.text}`;
|
|
|
54784
55027
|
async ownRunOnItsWay(projectPath, budgetMs) {
|
|
54785
55028
|
const run = await this.currentRun();
|
|
54786
55029
|
const project = projectPath ?? run?.projectPath ?? null;
|
|
54787
|
-
if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync30(
|
|
55030
|
+
if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync30(join39(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
|
|
54788
55031
|
return null;
|
|
54789
55032
|
}
|
|
54790
55033
|
const already = runtimesAnnounced().running;
|
|
@@ -54894,7 +55137,7 @@ ${error.text}`;
|
|
|
54894
55137
|
return this.createErrorResponse(staleNote);
|
|
54895
55138
|
}
|
|
54896
55139
|
const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
|
|
54897
|
-
const screenshotPath = screenshotDir ?
|
|
55140
|
+
const screenshotPath = screenshotDir ? join39(screenshotDir, "capture.png") : null;
|
|
54898
55141
|
const refused = (message) => this.createErrorResponse(message);
|
|
54899
55142
|
const onGame = {
|
|
54900
55143
|
pid: choice.endpoint.pid,
|
|
@@ -55229,7 +55472,7 @@ var init_server = __esm(() => {
|
|
|
55229
55472
|
// src/cli.ts
|
|
55230
55473
|
init_bridge_announce();
|
|
55231
55474
|
import { existsSync as existsSync31 } from "node:fs";
|
|
55232
|
-
import { join as
|
|
55475
|
+
import { join as join40, resolve as resolve10 } from "node:path";
|
|
55233
55476
|
|
|
55234
55477
|
// src/error-mode.ts
|
|
55235
55478
|
import process3 from "node:process";
|
|
@@ -55254,9 +55497,6 @@ async function letWindowsReportCrashes() {
|
|
|
55254
55497
|
}
|
|
55255
55498
|
} catch {}
|
|
55256
55499
|
}
|
|
55257
|
-
function crashesReported() {
|
|
55258
|
-
return cleared;
|
|
55259
|
-
}
|
|
55260
55500
|
|
|
55261
55501
|
// src/cli.ts
|
|
55262
55502
|
init_errors();
|
|
@@ -56226,7 +56466,7 @@ init_setup();
|
|
|
56226
56466
|
init_setup();
|
|
56227
56467
|
init_tool_definitions();
|
|
56228
56468
|
init_tool_reference();
|
|
56229
|
-
import { existsSync as existsSync15, mkdirSync as
|
|
56469
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync6, readdirSync as readdirSync10, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "node:fs";
|
|
56230
56470
|
import { basename as basename8, dirname as dirname9, join as join17 } from "node:path";
|
|
56231
56471
|
var SKILL_NAME = "gdharness";
|
|
56232
56472
|
var SHARED_SKILLS = join17(".agents", "skills");
|
|
@@ -56521,8 +56761,8 @@ function writeSkill(directories, version) {
|
|
|
56521
56761
|
rmSync6(directory, { recursive: true, force: true });
|
|
56522
56762
|
for (const [name, contents] of files) {
|
|
56523
56763
|
const path = join17(directory, name);
|
|
56524
|
-
|
|
56525
|
-
|
|
56764
|
+
mkdirSync6(dirname9(path), { recursive: true });
|
|
56765
|
+
writeFileSync8(path, contents, "utf8");
|
|
56526
56766
|
}
|
|
56527
56767
|
return { path: directory, replaced };
|
|
56528
56768
|
});
|
|
@@ -56530,30 +56770,31 @@ function writeSkill(directories, version) {
|
|
|
56530
56770
|
|
|
56531
56771
|
// src/suite-run.ts
|
|
56532
56772
|
init_alive();
|
|
56773
|
+
init_class_cache();
|
|
56533
56774
|
init_desktop();
|
|
56534
56775
|
init_engine_leaks();
|
|
56535
56776
|
import {
|
|
56536
56777
|
closeSync as closeSync3,
|
|
56537
56778
|
existsSync as existsSync19,
|
|
56538
56779
|
fstatSync,
|
|
56539
|
-
mkdirSync as
|
|
56780
|
+
mkdirSync as mkdirSync9,
|
|
56540
56781
|
openSync as openSync3,
|
|
56541
56782
|
readdirSync as readdirSync12,
|
|
56542
56783
|
readFileSync as readFileSync20,
|
|
56543
56784
|
readSync,
|
|
56544
56785
|
rmSync as rmSync10,
|
|
56545
56786
|
statSync as statSync13,
|
|
56546
|
-
writeFileSync as
|
|
56787
|
+
writeFileSync as writeFileSync11
|
|
56547
56788
|
} from "node:fs";
|
|
56548
56789
|
import { availableParallelism, freemem, totalmem } from "node:os";
|
|
56549
|
-
import { isAbsolute as isAbsolute6, join as
|
|
56790
|
+
import { isAbsolute as isAbsolute6, join as join23, relative as relative3, resolve as resolve5, sep as sep2 } from "node:path";
|
|
56550
56791
|
import process12 from "node:process";
|
|
56551
56792
|
import { setTimeout as delay3 } from "node:timers/promises";
|
|
56552
56793
|
|
|
56553
56794
|
// src/engine-slots.ts
|
|
56554
56795
|
init_alive();
|
|
56555
56796
|
init_process_children();
|
|
56556
|
-
import { closeSync as closeSync2, mkdirSync as
|
|
56797
|
+
import { closeSync as closeSync2, mkdirSync as mkdirSync7, openSync as openSync2, readFileSync as readFileSync15, rmSync as rmSync8, statSync as statSync11, writeSync as writeSync2 } from "node:fs";
|
|
56557
56798
|
import { join as join19 } from "node:path";
|
|
56558
56799
|
import process11 from "node:process";
|
|
56559
56800
|
import { setTimeout as delay2 } from "node:timers/promises";
|
|
@@ -56601,7 +56842,7 @@ class EngineSlots {
|
|
|
56601
56842
|
this.holder = holder;
|
|
56602
56843
|
}
|
|
56603
56844
|
async take(signal) {
|
|
56604
|
-
|
|
56845
|
+
mkdirSync7(this.directory, { recursive: true });
|
|
56605
56846
|
for (;; ) {
|
|
56606
56847
|
signal?.throwIfAborted();
|
|
56607
56848
|
for (let slot = 0;slot < this.count; slot += 1) {
|
|
@@ -56695,6 +56936,8 @@ class EngineSlots {
|
|
|
56695
56936
|
|
|
56696
56937
|
// src/suite-run.ts
|
|
56697
56938
|
init_errors();
|
|
56939
|
+
init_import_freshness();
|
|
56940
|
+
init_library_copies();
|
|
56698
56941
|
init_setup();
|
|
56699
56942
|
init_spawn();
|
|
56700
56943
|
|
|
@@ -56840,13 +57083,13 @@ init_suite_plan();
|
|
|
56840
57083
|
|
|
56841
57084
|
// src/version.ts
|
|
56842
57085
|
import { readFileSync as readFileSync19 } from "node:fs";
|
|
56843
|
-
import { dirname as dirname11, join as
|
|
57086
|
+
import { dirname as dirname11, join as join22 } from "node:path";
|
|
56844
57087
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
56845
57088
|
function getLocalVersion() {
|
|
56846
57089
|
const currentDirectory = dirname11(fileURLToPath2(import.meta.url));
|
|
56847
57090
|
const packageCandidates = [
|
|
56848
|
-
|
|
56849
|
-
|
|
57091
|
+
join22(currentDirectory, "..", "package.json"),
|
|
57092
|
+
join22(currentDirectory, "..", "..", "package.json")
|
|
56850
57093
|
];
|
|
56851
57094
|
for (const packagePath of packageCandidates) {
|
|
56852
57095
|
try {
|
|
@@ -56864,6 +57107,8 @@ init_windows_ask();
|
|
|
56864
57107
|
|
|
56865
57108
|
class TestUsageError extends Refusal {
|
|
56866
57109
|
}
|
|
57110
|
+
var WINDOWS_EXCEPTION = 3221225472;
|
|
57111
|
+
var SLOWEST_NAMED = 5;
|
|
56867
57112
|
var MOST_ENGINES = 4;
|
|
56868
57113
|
var MEMORY_KEPT = 4 * 1024 ** 3;
|
|
56869
57114
|
var MEMORY_PER_ENGINE = 1.5 * 1024 ** 3;
|
|
@@ -56878,6 +57123,10 @@ var VALUED = new Set([
|
|
|
56878
57123
|
"--slots",
|
|
56879
57124
|
"--slot-count",
|
|
56880
57125
|
"--silence",
|
|
57126
|
+
"--deadline",
|
|
57127
|
+
"--godot",
|
|
57128
|
+
"--godot-version",
|
|
57129
|
+
"--cores",
|
|
56881
57130
|
"--tests",
|
|
56882
57131
|
"--reach-also",
|
|
56883
57132
|
"--order"
|
|
@@ -56906,9 +57155,9 @@ function testOptionsOf(args, cwd) {
|
|
|
56906
57155
|
}
|
|
56907
57156
|
const first = positional[0];
|
|
56908
57157
|
const named = first === undefined ? null : resolve5(cwd, first);
|
|
56909
|
-
const projectNamed = named !== null && existsSync19(
|
|
57158
|
+
const projectNamed = named !== null && existsSync19(join23(named, "project.godot"));
|
|
56910
57159
|
const projectPath = projectNamed ? named : resolve5(cwd);
|
|
56911
|
-
if (!existsSync19(
|
|
57160
|
+
if (!existsSync19(join23(projectPath, "project.godot"))) {
|
|
56912
57161
|
throw new TestUsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
|
|
56913
57162
|
}
|
|
56914
57163
|
const last = (flag) => valued.get(flag)?.at(-1);
|
|
@@ -56920,6 +57169,10 @@ function testOptionsOf(args, cwd) {
|
|
|
56920
57169
|
slotsDirectory: last("--slots") === undefined ? null : resolve5(cwd, last("--slots") ?? ""),
|
|
56921
57170
|
slotCount: whole(last("--slot-count"), "--slot-count"),
|
|
56922
57171
|
silenceSeconds: whole(last("--silence"), "--silence") ?? SILENCE_FLOOR_SECONDS,
|
|
57172
|
+
deadlineSeconds: whole(last("--deadline"), "--deadline"),
|
|
57173
|
+
godotPath: godotNamed(last("--godot"), cwd),
|
|
57174
|
+
godotVersion: versionNamed(last("--godot-version")),
|
|
57175
|
+
cores: coresNamed(last("--cores")),
|
|
56923
57176
|
roots: (valued.get("--tests") ?? []).map((root) => resPathOf(root, projectPath, cwd).replace(/\/$/, "")),
|
|
56924
57177
|
changedOnly: switches.has("--changed-only"),
|
|
56925
57178
|
reachAlso: (valued.get("--reach-also") ?? []).map((path) => resolve5(projectPath, path)),
|
|
@@ -56938,6 +57191,34 @@ function whole(value, flag) {
|
|
|
56938
57191
|
}
|
|
56939
57192
|
return number;
|
|
56940
57193
|
}
|
|
57194
|
+
function godotNamed(value, cwd) {
|
|
57195
|
+
if (value === undefined) {
|
|
57196
|
+
return null;
|
|
57197
|
+
}
|
|
57198
|
+
const path = resolve5(cwd, value);
|
|
57199
|
+
if (!existsSync19(path) || statSync13(path).isDirectory()) {
|
|
57200
|
+
throw new TestUsageError(`--godot names ${path}, which is not an engine executable.`);
|
|
57201
|
+
}
|
|
57202
|
+
return path;
|
|
57203
|
+
}
|
|
57204
|
+
function coresNamed(value) {
|
|
57205
|
+
const cores = whole(value, "--cores");
|
|
57206
|
+
const most = Math.min(availableParallelism(), 64);
|
|
57207
|
+
if (cores !== null && cores > most) {
|
|
57208
|
+
throw new TestUsageError(`--cores ${String(cores)} is more than the ${String(most)} processors a run can be held to here.`);
|
|
57209
|
+
}
|
|
57210
|
+
return cores;
|
|
57211
|
+
}
|
|
57212
|
+
function versionNamed(value) {
|
|
57213
|
+
if (value === undefined) {
|
|
57214
|
+
return null;
|
|
57215
|
+
}
|
|
57216
|
+
const version = value.replace(/^v/, "");
|
|
57217
|
+
if (!/^\d+\.\d+(?:\.\d+)?(?:\.[\w-]+)*$/.test(version)) {
|
|
57218
|
+
throw new TestUsageError(`--godot-version takes a version such as 4.7.2 or 4.7.2.stable, not ${value}.`);
|
|
57219
|
+
}
|
|
57220
|
+
return version;
|
|
57221
|
+
}
|
|
56941
57222
|
function seedOf(value) {
|
|
56942
57223
|
const seed = Number(value);
|
|
56943
57224
|
if (!Number.isInteger(seed) || seed < 0) {
|
|
@@ -56965,7 +57246,7 @@ function homeOf(projectPath) {
|
|
|
56965
57246
|
return mainCheckoutOf(projectPath) ?? projectPath;
|
|
56966
57247
|
}
|
|
56967
57248
|
function selectedSuites(scripts, options) {
|
|
56968
|
-
const roots = options.roots.length > 0 ? options.roots : TEST_DIRECTORIES.filter((root) => existsSync19(
|
|
57249
|
+
const roots = options.roots.length > 0 ? options.roots : TEST_DIRECTORIES.filter((root) => existsSync19(join23(options.projectPath, root))).map((root) => `res://${root}`);
|
|
56969
57250
|
if (options.selected.length === 0) {
|
|
56970
57251
|
const found = scripts.suitesUnder(roots);
|
|
56971
57252
|
if (found.length === 0) {
|
|
@@ -56977,7 +57258,7 @@ function selectedSuites(scripts, options) {
|
|
|
56977
57258
|
for (const entry of options.selected) {
|
|
56978
57259
|
const caseAt = /^(res:\/\/.*\.gd):(\w+)$/.exec(entry);
|
|
56979
57260
|
const path = caseAt?.[1] ?? entry;
|
|
56980
|
-
const directory =
|
|
57261
|
+
const directory = join23(options.projectPath, path.slice(6));
|
|
56981
57262
|
const suites = existsSync19(directory) && statSync13(directory).isDirectory() ? scripts.suitesUnder([path.replace(/\/$/, "")]) : [scripts.suite(path)].filter((suite) => suite !== null);
|
|
56982
57263
|
if (suites.length === 0) {
|
|
56983
57264
|
throw new TestUsageError(existsSync19(directory) ? `${path} holds no gdUnit4 suite: nothing there extends GdUnitTestSuite and declares a test_ function.` : `Nothing at ${path} in the project at ${options.projectPath}.`);
|
|
@@ -56999,7 +57280,7 @@ function selectedSuites(scripts, options) {
|
|
|
56999
57280
|
cases: suite.cases.filter((c) => cases.has(c))
|
|
57000
57281
|
}));
|
|
57001
57282
|
}
|
|
57002
|
-
function roomFor(most, processors, available, said = "of memory free") {
|
|
57283
|
+
function roomFor(most, processors, available, said = "of memory free", processorsSaid = `the machine has ${plural(processors, "processor")}`) {
|
|
57003
57284
|
const byMemory = Math.floor((available - MEMORY_KEPT) / MEMORY_PER_ENGINE);
|
|
57004
57285
|
const engines = Math.max(1, Math.min(most, processors, byMemory));
|
|
57005
57286
|
if (engines >= most) {
|
|
@@ -57008,7 +57289,7 @@ function roomFor(most, processors, available, said = "of memory free") {
|
|
|
57008
57289
|
const gigabytes = (bytes) => `${(bytes / 1024 ** 3).toFixed(1)} GB`;
|
|
57009
57290
|
return {
|
|
57010
57291
|
engines,
|
|
57011
|
-
held: byMemory < processors ? `the machine has ${gigabytes(Math.max(0, available))} ${said}, ${gigabytes(MEMORY_KEPT)} of it kept for everything else and ${gigabytes(MEMORY_PER_ENGINE)} taken for each engine` :
|
|
57292
|
+
held: byMemory < processors ? `the machine has ${gigabytes(Math.max(0, available))} ${said}, ${gigabytes(MEMORY_KEPT)} of it kept for everything else and ${gigabytes(MEMORY_PER_ENGINE)} taken for each engine` : processorsSaid
|
|
57012
57293
|
};
|
|
57013
57294
|
}
|
|
57014
57295
|
async function memoryAvailable() {
|
|
@@ -57070,6 +57351,9 @@ function ordered(units, order) {
|
|
|
57070
57351
|
}
|
|
57071
57352
|
return sorted;
|
|
57072
57353
|
}
|
|
57354
|
+
function heldToCores(cores, command, args, platform = process12.platform) {
|
|
57355
|
+
return cores === null || platform !== "linux" ? { command, args: [...args] } : { command: "taskset", args: ["-c", `0-${String(cores - 1)}`, command, ...args] };
|
|
57356
|
+
}
|
|
57073
57357
|
async function runTestCommand(options, godotPath) {
|
|
57074
57358
|
const unusable = gdUnit4Unusable(options.projectPath);
|
|
57075
57359
|
if (unusable !== null) {
|
|
@@ -57078,17 +57362,17 @@ async function runTestCommand(options, godotPath) {
|
|
|
57078
57362
|
const startedAt = Date.now();
|
|
57079
57363
|
const scripts = new ProjectScripts(options.projectPath);
|
|
57080
57364
|
const home = homeOf(options.projectPath);
|
|
57081
|
-
const state =
|
|
57082
|
-
const timesFile =
|
|
57083
|
-
const passedFile =
|
|
57365
|
+
const state = join23(home, ".godot", "gdharness");
|
|
57366
|
+
const timesFile = join23(state, "suite-times.json");
|
|
57367
|
+
const passedFile = join23(state, "suite-passed.json");
|
|
57084
57368
|
let suites = selectedSuites(scripts, options);
|
|
57085
57369
|
const wholeSuites = suites.map((suite) => scripts.suite(suite.path) ?? suite);
|
|
57086
57370
|
const declared = new Map(wholeSuites.map((suite) => [suite.path, suite.cases]));
|
|
57087
|
-
const testRuns =
|
|
57371
|
+
const testRuns = join23(options.projectPath, ".godot", "gdharness", "test-runs");
|
|
57088
57372
|
sweepRunDirectories(testRuns);
|
|
57089
|
-
const runDirectory =
|
|
57090
|
-
|
|
57091
|
-
const slots = new EngineSlots(options.slotsDirectory ??
|
|
57373
|
+
const runDirectory = join23(testRuns, `${String(startedAt)}-${String(process12.pid)}`);
|
|
57374
|
+
mkdirSync9(runDirectory, { recursive: true });
|
|
57375
|
+
const slots = new EngineSlots(options.slotsDirectory ?? join23(home, ".godot", "gate", "slots"), options.slotCount ?? Math.min(MOST_ENGINES, availableParallelism()), { pid: process12.pid, started: await ownStart() });
|
|
57092
57376
|
const running = new Set;
|
|
57093
57377
|
const interrupted = () => {
|
|
57094
57378
|
for (const end of running) {
|
|
@@ -57105,8 +57389,11 @@ async function runTestCommand(options, godotPath) {
|
|
|
57105
57389
|
runDirectory,
|
|
57106
57390
|
slots,
|
|
57107
57391
|
silenceFloorSeconds: options.silenceSeconds,
|
|
57392
|
+
deadlineSeconds: options.deadlineSeconds,
|
|
57108
57393
|
running,
|
|
57109
|
-
selfTyped: selfTypedExports(scripts.sources)
|
|
57394
|
+
selfTyped: selfTypedExports(scripts.sources),
|
|
57395
|
+
godotVersion: options.godotVersion,
|
|
57396
|
+
cores: options.cores
|
|
57110
57397
|
};
|
|
57111
57398
|
try {
|
|
57112
57399
|
if (options.importFirst) {
|
|
@@ -57145,7 +57432,10 @@ ${existsSync19(path) ? readFileSync20(path, "latin1") : "missing"}`)
|
|
|
57145
57432
|
if (memory.unread !== undefined) {
|
|
57146
57433
|
console.log(`Windows did not say how much memory it can still commit (${memory.unread}), so engines are sized by the physical memory free.`);
|
|
57147
57434
|
}
|
|
57148
|
-
const room = roomFor(options.engines, availableParallelism(), memory.bytes, memory.said);
|
|
57435
|
+
const room = options.cores === null ? roomFor(options.engines, availableParallelism(), memory.bytes, memory.said) : roomFor(options.engines, options.cores, memory.bytes, memory.said, `--cores holds it to ${plural(options.cores, "processor")}`);
|
|
57436
|
+
if (options.cores !== null && process12.platform === "darwin") {
|
|
57437
|
+
console.log("macOS offers no way to hold a process to some processors, so --cores holds only the number of engines here.");
|
|
57438
|
+
}
|
|
57149
57439
|
const plans = options.order === null ? dealt(units, declared, room.engines) : [inOneEngine(ordered(units, options.order), declared)];
|
|
57150
57440
|
const functions = suites.reduce((sum, suite) => sum + suite.cases.length, 0);
|
|
57151
57441
|
const how = options.order === null ? `${windowedSaid(plans)}${roomSaid(plans.length, lanesOf(plans), lanesOf(dealt(units, declared, options.engines)), room.held, options.engines)}` : options.order === "reversed" ? ", in reverse order of path" : `, shuffled by ${String(options.order)}`;
|
|
@@ -57155,10 +57445,16 @@ ${existsSync19(path) ? readFileSync20(path, "latin1") : "missing"}`)
|
|
|
57155
57445
|
const one = windowedAnyway.length === 1;
|
|
57156
57446
|
console.log(`${named(windowedAnyway)} ${one ? "says it runs" : "say they run"} headless and ${one ? "runs" : "run"} with a window here, since an ordered run is one engine and a suite in it needs a window. Run ${one ? "it" : "them"} without the windowed suites to run ${one ? "it" : "them"} headless.`);
|
|
57157
57447
|
}
|
|
57158
|
-
const split = [...new Set(units.map((unit) => unit.suite))].filter((path) => units.filter((unit) => unit.suite === path).length > 1);
|
|
57448
|
+
const split = [...new Set(units.map((unit) => unit.suite))].filter((path) => units.filter((unit) => unit.suite === path && unit.row === undefined).length > 1);
|
|
57159
57449
|
if (plans.length > 1 && split.length > 0) {
|
|
57160
57450
|
console.log(`Dealt out case by case, since ${split.length === 1 ? "it" : "each"} took more than ${String(SPLIT_PAST_SECONDS)} s last time: ${named(split)}.`);
|
|
57161
57451
|
}
|
|
57452
|
+
const byRows = [
|
|
57453
|
+
...new Set(units.flatMap((unit) => unit.row === undefined ? [] : [`${unit.suite} > ${unit.cases[0] ?? ""}`]))
|
|
57454
|
+
];
|
|
57455
|
+
if (plans.length > 1 && byRows.length > 0) {
|
|
57456
|
+
console.log(`Dealt out row by row, since ${byRows.length === 1 ? "it" : "each"} took more than ${String(SPLIT_PAST_SECONDS)} s last time: ${named(byRows)}.`);
|
|
57457
|
+
}
|
|
57162
57458
|
const lanes = [...new Set(plans.map((plan) => plan.lane))].map((lane) => plans.flatMap((plan, index) => plan.lane === lane ? [{ plan, index }] : []));
|
|
57163
57459
|
let ran = (await inTurns(lanes, room.engines, (lane) => runLane(launch, lane, units))).flat().sort((a, b) => a.index - b.index);
|
|
57164
57460
|
if (ran.some((engine) => engine.verdict.starved) && ran.some((engine) => !engine.verdict.passed)) {
|
|
@@ -57169,9 +57465,10 @@ ${existsSync19(path) ? readFileSync20(path, "latin1") : "missing"}`)
|
|
|
57169
57465
|
}
|
|
57170
57466
|
ran = again;
|
|
57171
57467
|
}
|
|
57172
|
-
writeJson(timesFile, timesAfter(readTimes(timesFile), wholeSuites, ran.map((engine) => engine.printed), (path) => scripts.suite(path)));
|
|
57173
|
-
|
|
57174
|
-
|
|
57468
|
+
writeJson(timesFile, timesAfter(readTimes(timesFile), wholeSuites, ran.map((engine) => engine.printed), (path) => scripts.suite(path), new Set(ran.flatMap((engine) => engine.plan.suites.filter((suite) => (engine.printed.suites.get(suite.path)?.closed ?? null) === null).flatMap((suite) => suite.cases.map((func) => `${suite.path}:${func}`))))));
|
|
57469
|
+
const across = rowsAcross(ran.map((engine) => ({ handed: engine.plan.suites, printed: engine.printed })));
|
|
57470
|
+
recordPassed(passedFile, ran, printsOf, new Set(across.map((fault) => fault.suite)));
|
|
57471
|
+
return report(ran, across.map((fault) => fault.fault), startedAt, runDirectory, options.printOutput);
|
|
57175
57472
|
} finally {
|
|
57176
57473
|
process12.off("SIGINT", interrupted);
|
|
57177
57474
|
process12.off("SIGTERM", interrupted);
|
|
@@ -57207,9 +57504,9 @@ function readRecord(file) {
|
|
|
57207
57504
|
return {};
|
|
57208
57505
|
}
|
|
57209
57506
|
}
|
|
57210
|
-
function recordPassed(file, ran, printsOf) {
|
|
57507
|
+
function recordPassed(file, ran, printsOf, failedAcross) {
|
|
57211
57508
|
const passed = readRecord(file);
|
|
57212
|
-
const failedSomewhere = new Set;
|
|
57509
|
+
const failedSomewhere = new Set(failedAcross);
|
|
57213
57510
|
const passedSomewhere = new Set;
|
|
57214
57511
|
for (const engine of ran) {
|
|
57215
57512
|
const passing = new Set(engine.verdict.passedSuites);
|
|
@@ -57234,19 +57531,38 @@ function sweepRunDirectories(testRuns) {
|
|
|
57234
57531
|
for (const entry of readdirSync12(testRuns)) {
|
|
57235
57532
|
const pid = Number(/^\d+-(\d+)$/.exec(entry)?.[1]);
|
|
57236
57533
|
if (!Number.isInteger(pid) || !alive(pid)) {
|
|
57237
|
-
rmSync10(
|
|
57534
|
+
rmSync10(join23(testRuns, entry), { recursive: true, force: true });
|
|
57238
57535
|
}
|
|
57239
57536
|
}
|
|
57240
57537
|
}
|
|
57241
57538
|
async function importProject(launch) {
|
|
57242
|
-
const
|
|
57243
|
-
|
|
57244
|
-
|
|
57539
|
+
const seeded = seedExtensionList(launch.projectPath);
|
|
57540
|
+
if (seeded !== null && seeded.length > 0) {
|
|
57541
|
+
console.log(`Wrote .godot/extension_list.cfg naming ${named(seeded)}, so the import loads them.`);
|
|
57542
|
+
}
|
|
57543
|
+
const cached = cachedClasses(launch.projectPath);
|
|
57544
|
+
if ((seeded === null || seeded.length === 0) && cached !== null && staleAgainst(cached, launch.projectPath).length === 0 && staleImports(launch.projectPath).length === 0) {
|
|
57545
|
+
console.log("The import was skipped: every imported file and the class cache are current.");
|
|
57546
|
+
return null;
|
|
57547
|
+
}
|
|
57548
|
+
const directory = join23(launch.runDirectory, "import");
|
|
57549
|
+
mkdirSync9(join23(directory, "user"), { recursive: true });
|
|
57550
|
+
const log = join23(directory, "import.log");
|
|
57245
57551
|
const lock = await launch.slots.take();
|
|
57246
57552
|
try {
|
|
57247
|
-
|
|
57553
|
+
freeLibraryCopies(launch.projectPath);
|
|
57554
|
+
const ended = await watched(launch, ["--headless", "--audio-driver", "Dummy", "--path", launch.projectPath, "--import"], directory, log, launch.silenceFloorSeconds);
|
|
57248
57555
|
if (ended.ended !== null || ended.code !== 0) {
|
|
57249
|
-
return `${ended.ended ?? `the engine exited ${
|
|
57556
|
+
return `${ended.ended ?? `the engine exited ${exitNamed(ended.code ?? 0)}`}; everything it printed is in ${log}`;
|
|
57557
|
+
}
|
|
57558
|
+
const printed = readRun(readFileSync20(log, "utf8"));
|
|
57559
|
+
const wrongEngine = launch.godotVersion === null ? null : versionFault(printed.engineVersion, launch.godotVersion);
|
|
57560
|
+
if (wrongEngine !== null) {
|
|
57561
|
+
return `${wrongEngine}; everything it printed is in ${log}`;
|
|
57562
|
+
}
|
|
57563
|
+
const errors = printed.errors.filter((error) => !error.warning);
|
|
57564
|
+
if (errors.length > 0) {
|
|
57565
|
+
return `it printed ${errors.length === 1 ? "an engine error" : `${String(errors.length)} engine errors`}, the first ${JSON.stringify(errors[0]?.text ?? "")}; everything it printed is in ${log}`;
|
|
57250
57566
|
}
|
|
57251
57567
|
return null;
|
|
57252
57568
|
} finally {
|
|
@@ -57266,15 +57582,22 @@ async function runLane(launch, lane, units) {
|
|
|
57266
57582
|
}
|
|
57267
57583
|
}
|
|
57268
57584
|
async function runEngine2(launch, plan, index, units, again = "", slot = null) {
|
|
57269
|
-
const directory =
|
|
57270
|
-
|
|
57271
|
-
const suitesFile =
|
|
57272
|
-
const skippingFile =
|
|
57273
|
-
const
|
|
57274
|
-
|
|
57585
|
+
const directory = join23(launch.runDirectory, `engine-${String(index)}${again === "" ? "" : `-${again}`}`);
|
|
57586
|
+
mkdirSync9(join23(directory, "user"), { recursive: true });
|
|
57587
|
+
const suitesFile = join23(directory, "suites.txt");
|
|
57588
|
+
const skippingFile = join23(directory, "skipping.txt");
|
|
57589
|
+
const rowsFile = join23(directory, "rows.txt");
|
|
57590
|
+
const expectedFile = join23(directory, "expected.json");
|
|
57591
|
+
writeFileSync11(suitesFile, `${plan.suites.map((suite) => suite.path).join(`
|
|
57592
|
+
`)}
|
|
57593
|
+
`);
|
|
57594
|
+
writeFileSync11(skippingFile, `${plan.skipping.join(`
|
|
57275
57595
|
`)}
|
|
57276
57596
|
`);
|
|
57277
|
-
|
|
57597
|
+
writeFileSync11(rowsFile, `${plan.suites.flatMap((suite) => Object.entries(suite.rows ?? {}).map(([func, share]) => [
|
|
57598
|
+
`${suite.path}:${func}`,
|
|
57599
|
+
..."only" in share ? ["only", ...share.only] : ["besides", ...share.besides]
|
|
57600
|
+
].join("\t"))).join(`
|
|
57278
57601
|
`)}
|
|
57279
57602
|
`);
|
|
57280
57603
|
const viewport = viewportOf(launch.projectPath);
|
|
@@ -57290,7 +57613,7 @@ async function runEngine2(launch, plan, index, units, again = "", slot = null) {
|
|
|
57290
57613
|
...plan.windowed ? [] : ["--ignoreHeadlessMode"],
|
|
57291
57614
|
"-c",
|
|
57292
57615
|
"-rd",
|
|
57293
|
-
`res://${relative3(launch.projectPath,
|
|
57616
|
+
`res://${relative3(launch.projectPath, join23(directory, "reports")).split(sep2).join("/")}`,
|
|
57294
57617
|
"-rc",
|
|
57295
57618
|
"1",
|
|
57296
57619
|
"--",
|
|
@@ -57298,6 +57621,8 @@ async function runEngine2(launch, plan, index, units, again = "", slot = null) {
|
|
|
57298
57621
|
suitesFile,
|
|
57299
57622
|
"--skipping-in",
|
|
57300
57623
|
skippingFile,
|
|
57624
|
+
"--rows-in",
|
|
57625
|
+
rowsFile,
|
|
57301
57626
|
"--html-report-off",
|
|
57302
57627
|
"--junit-report-off",
|
|
57303
57628
|
"--compile-together",
|
|
@@ -57307,12 +57632,12 @@ async function runEngine2(launch, plan, index, units, again = "", slot = null) {
|
|
|
57307
57632
|
const held = new Set(plan.suites.map((suite) => suite.path));
|
|
57308
57633
|
const slowest = Math.max(0, ...units.filter((unit) => held.has(unit.suite)).map((unit) => unit.cost));
|
|
57309
57634
|
const silence = Math.max(launch.silenceFloorSeconds, SILENCE_TIMES_SLOWEST * slowest);
|
|
57310
|
-
const log =
|
|
57635
|
+
const log = join23(directory, "engine.log");
|
|
57311
57636
|
const lock = slot ?? await launch.slots.take();
|
|
57312
57637
|
const began = Date.now();
|
|
57313
57638
|
let end;
|
|
57314
57639
|
try {
|
|
57315
|
-
end = await watched(launch, args, directory, log,
|
|
57640
|
+
end = await watched(launch, args, directory, log, silence);
|
|
57316
57641
|
} finally {
|
|
57317
57642
|
if (slot === null) {
|
|
57318
57643
|
launch.slots.release(lock);
|
|
@@ -57322,7 +57647,8 @@ async function runEngine2(launch, plan, index, units, again = "", slot = null) {
|
|
|
57322
57647
|
const assignment = {
|
|
57323
57648
|
suites: plan.suites,
|
|
57324
57649
|
expected: readExpected(expectedFile),
|
|
57325
|
-
selfTyped: launch.selfTyped
|
|
57650
|
+
selfTyped: launch.selfTyped,
|
|
57651
|
+
godotVersion: launch.godotVersion
|
|
57326
57652
|
};
|
|
57327
57653
|
return {
|
|
57328
57654
|
index,
|
|
@@ -57330,29 +57656,34 @@ async function runEngine2(launch, plan, index, units, again = "", slot = null) {
|
|
|
57330
57656
|
printed,
|
|
57331
57657
|
verdict: judgeEngine(printed, assignment, end),
|
|
57332
57658
|
log,
|
|
57333
|
-
seconds: (Date.now() - began) / 1000
|
|
57659
|
+
seconds: (Date.now() - began) / 1000,
|
|
57660
|
+
again: again !== ""
|
|
57334
57661
|
};
|
|
57335
57662
|
}
|
|
57336
|
-
async function watched(launch, args, directory, log,
|
|
57337
|
-
const user =
|
|
57663
|
+
async function watched(launch, args, directory, log, silenceSeconds) {
|
|
57664
|
+
const user = join23(directory, "user");
|
|
57338
57665
|
const env = { ...process12.env, APPDATA: user, XDG_DATA_HOME: user };
|
|
57339
57666
|
const output = openSync3(log, "w");
|
|
57340
57667
|
let child;
|
|
57341
57668
|
let stop;
|
|
57342
57669
|
let helper = null;
|
|
57343
57670
|
try {
|
|
57344
|
-
if (process12.platform === "win32"
|
|
57345
|
-
const where =
|
|
57671
|
+
if (process12.platform === "win32") {
|
|
57672
|
+
const where = "on a desktop of its own";
|
|
57346
57673
|
const compiled = await ensureHelperCompiled();
|
|
57347
57674
|
if (compiled !== null) {
|
|
57348
57675
|
return { code: null, signal: null, ended: `the engine could not be started ${where}: ${compiled}` };
|
|
57349
57676
|
}
|
|
57350
|
-
const stopFile =
|
|
57351
|
-
const error =
|
|
57352
|
-
helper = { error, how: where, pid:
|
|
57353
|
-
const how =
|
|
57677
|
+
const stopFile = join23(directory, "stop");
|
|
57678
|
+
const error = join23(directory, "error");
|
|
57679
|
+
helper = { error, how: where, pid: join23(directory, "pid"), step: join23(directory, "step") };
|
|
57680
|
+
const how = {
|
|
57681
|
+
desktop: HIDDEN_DESKTOP,
|
|
57682
|
+
directory: launch.projectPath,
|
|
57683
|
+
...launch.cores === null ? {} : { affinity: (1n << BigInt(launch.cores)) - 1n }
|
|
57684
|
+
};
|
|
57354
57685
|
const wrapper = throughHelper(how, launch.godotPath, args, {
|
|
57355
|
-
spec:
|
|
57686
|
+
spec: join23(directory, "spec"),
|
|
57356
57687
|
pid: helper.pid,
|
|
57357
57688
|
error,
|
|
57358
57689
|
stop: stopFile,
|
|
@@ -57360,18 +57691,19 @@ async function watched(launch, args, directory, log, windowed, silenceSeconds) {
|
|
|
57360
57691
|
});
|
|
57361
57692
|
child = spawnHidden(wrapper.command, wrapper.args, {
|
|
57362
57693
|
stdio: ["ignore", output, output],
|
|
57363
|
-
env:
|
|
57694
|
+
env: { ...env, [DESKTOP_VARIABLE]: HIDDEN_DESKTOP },
|
|
57364
57695
|
cwd: directory
|
|
57365
57696
|
});
|
|
57366
57697
|
stop = () => {
|
|
57367
|
-
|
|
57698
|
+
writeFileSync11(stopFile, "");
|
|
57368
57699
|
};
|
|
57369
57700
|
} else {
|
|
57370
|
-
|
|
57701
|
+
const held = heldToCores(launch.cores, launch.godotPath, args);
|
|
57702
|
+
child = spawnHidden(held.command, held.args, {
|
|
57371
57703
|
stdio: ["ignore", output, output],
|
|
57372
57704
|
env,
|
|
57373
57705
|
cwd: launch.projectPath,
|
|
57374
|
-
detached:
|
|
57706
|
+
detached: true
|
|
57375
57707
|
});
|
|
57376
57708
|
stop = () => {
|
|
57377
57709
|
endTree(child);
|
|
@@ -57398,10 +57730,17 @@ async function watched(launch, args, directory, log, windowed, silenceSeconds) {
|
|
|
57398
57730
|
const reading = openSync3(log, "r");
|
|
57399
57731
|
try {
|
|
57400
57732
|
let size = 0;
|
|
57401
|
-
|
|
57733
|
+
const started = Date.now();
|
|
57734
|
+
let lastGrowth = started;
|
|
57402
57735
|
let text = "";
|
|
57403
57736
|
while (!finished.now) {
|
|
57404
57737
|
await delay3(LOOK_MS);
|
|
57738
|
+
const deadline = launch.deadlineSeconds;
|
|
57739
|
+
if (ended === null && deadline !== null && Date.now() - started > deadline * 1000) {
|
|
57740
|
+
const stoppedIn = readRun(text).stoppedIn;
|
|
57741
|
+
ended = `the engine ran past the ${String(deadline)} s deadline${stoppedIn === null ? "" : ` while running ${stoppedIn}`}, so it was ended`;
|
|
57742
|
+
stop();
|
|
57743
|
+
}
|
|
57405
57744
|
const now = fstatSync(reading).size;
|
|
57406
57745
|
if (now > size) {
|
|
57407
57746
|
const chunk = Buffer.alloc(now - size);
|
|
@@ -57425,11 +57764,12 @@ async function watched(launch, args, directory, log, windowed, silenceSeconds) {
|
|
|
57425
57764
|
launch.running.delete(stop);
|
|
57426
57765
|
}
|
|
57427
57766
|
const { code, signal } = await exited;
|
|
57767
|
+
const freeBytes = code !== null && code >>> 0 >= WINDOWS_EXCEPTION ? freemem() : undefined;
|
|
57428
57768
|
const failed = helper === null ? null : helperFailure(helper.error);
|
|
57429
57769
|
if (failed !== null && helper !== null) {
|
|
57430
57770
|
ended ??= `the engine could not be started ${helper.how}: ${failed}`;
|
|
57431
57771
|
}
|
|
57432
|
-
return { code, signal, ended };
|
|
57772
|
+
return { code, signal, ended, ...freeBytes === undefined ? {} : { freeBytes } };
|
|
57433
57773
|
}
|
|
57434
57774
|
function endTree(child) {
|
|
57435
57775
|
if (child.pid === undefined) {
|
|
@@ -57443,7 +57783,7 @@ function endTree(child) {
|
|
|
57443
57783
|
process12.kill(-child.pid, "SIGKILL");
|
|
57444
57784
|
} catch {}
|
|
57445
57785
|
}
|
|
57446
|
-
function report(ran, startedAt, runDirectory, printOutput) {
|
|
57786
|
+
function report(ran, across, startedAt, runDirectory, printOutput) {
|
|
57447
57787
|
if (printOutput) {
|
|
57448
57788
|
for (const engine of ran) {
|
|
57449
57789
|
console.log("");
|
|
@@ -57453,11 +57793,22 @@ function report(ran, startedAt, runDirectory, printOutput) {
|
|
|
57453
57793
|
}
|
|
57454
57794
|
const failed = ran.filter((engine) => !engine.verdict.passed);
|
|
57455
57795
|
for (const engine of failed) {
|
|
57796
|
+
const heading = `--- engine ${String(engine.index)}: ${plural(engine.plan.suites.length, "suite")}, ${engine.seconds.toFixed(1)} s${engine.again ? ", run again alone after an engine ran out of memory, and failed again" : ""}`;
|
|
57456
57797
|
console.log("");
|
|
57457
|
-
console.log(
|
|
57798
|
+
console.log(heading);
|
|
57458
57799
|
for (const fault of engine.verdict.faults) {
|
|
57459
57800
|
console.log(` ${fault}`);
|
|
57460
57801
|
}
|
|
57802
|
+
if (existsSync19(engine.log)) {
|
|
57803
|
+
const verdict = [
|
|
57804
|
+
`gdharness: ${heading.slice(4)}`,
|
|
57805
|
+
...engine.verdict.faults.map((fault) => ` ${fault}`)
|
|
57806
|
+
];
|
|
57807
|
+
writeFileSync11(engine.log, `${verdict.join(`
|
|
57808
|
+
`)}
|
|
57809
|
+
---
|
|
57810
|
+
${readFileSync20(engine.log, "utf8")}`);
|
|
57811
|
+
}
|
|
57461
57812
|
if (engine.verdict.shown.length > 0) {
|
|
57462
57813
|
console.log("");
|
|
57463
57814
|
for (const line of engine.verdict.shown) {
|
|
@@ -57466,18 +57817,33 @@ function report(ran, startedAt, runDirectory, printOutput) {
|
|
|
57466
57817
|
}
|
|
57467
57818
|
console.log(`everything it printed: ${engine.log}`);
|
|
57468
57819
|
}
|
|
57820
|
+
if (across.length > 0) {
|
|
57821
|
+
console.log("");
|
|
57822
|
+
console.log("--- the cases dealt by their rows, over every engine holding them:");
|
|
57823
|
+
for (const fault of across) {
|
|
57824
|
+
console.log(` ${fault}`);
|
|
57825
|
+
}
|
|
57826
|
+
}
|
|
57469
57827
|
const cases = ran.reduce((sum, engine) => sum + [...engine.printed.suites.values()].reduce((n, s) => n + s.cases.length, 0), 0);
|
|
57470
57828
|
const suites = new Set(ran.flatMap((engine) => engine.plan.suites.map((suite) => suite.path))).size;
|
|
57471
57829
|
const longest = Math.max(0, ...ran.map((engine) => engine.seconds));
|
|
57472
57830
|
const took = (Date.now() - startedAt) / 1000;
|
|
57831
|
+
if (ran.length > 1) {
|
|
57832
|
+
console.log("");
|
|
57833
|
+
console.log(`The engines took ${ran.map((engine) => `${engine.seconds.toFixed(1)} s`).join(", ")}, dealt ${ran.map((engine) => `${engine.plan.cost.toFixed(1)} s`).join(", ")}.`);
|
|
57834
|
+
}
|
|
57835
|
+
const slowest = ran.flatMap((engine) => [...engine.printed.suites.values()].flatMap((suite) => suite.closed === null ? [] : [{ path: suite.path, seconds: suite.closed.seconds }])).sort((a, b) => b.seconds - a.seconds).slice(0, SLOWEST_NAMED);
|
|
57836
|
+
if (slowest.length > 1) {
|
|
57837
|
+
console.log(`The slowest suites: ${slowest.map((suite) => `${suite.path} ${suite.seconds.toFixed(1)} s`).join(", ")}.`);
|
|
57838
|
+
}
|
|
57473
57839
|
const totals = `${plural(cases, "case")} in ${plural(suites, "suite")} across ${plural(ran.length, "engine")}, ${took.toFixed(1)} s; the longest engine took ${longest.toFixed(1)} s.`;
|
|
57474
57840
|
console.log("");
|
|
57475
|
-
if (failed.length === 0) {
|
|
57841
|
+
if (failed.length === 0 && across.length === 0) {
|
|
57476
57842
|
rmSync10(runDirectory, { recursive: true, force: true });
|
|
57477
57843
|
console.log(`Passed: ${totals}`);
|
|
57478
57844
|
return 0;
|
|
57479
57845
|
}
|
|
57480
|
-
const problems = failed.reduce((sum, engine) => sum + engine.verdict.faults.length,
|
|
57846
|
+
const problems = failed.reduce((sum, engine) => sum + engine.verdict.faults.length, across.length);
|
|
57481
57847
|
console.log(`Failed: ${plural(problems, "problem")}, listed above. Ran ${totals}`);
|
|
57482
57848
|
console.log(`Every engine's output is kept under ${runDirectory} until the next run.`);
|
|
57483
57849
|
return 1;
|
|
@@ -57492,7 +57858,7 @@ class UsageError extends Refusal {
|
|
|
57492
57858
|
function projectArgument(at) {
|
|
57493
57859
|
const named = args.slice(at).find((value) => !value.startsWith("--"));
|
|
57494
57860
|
const projectPath = resolve10(named ?? ".");
|
|
57495
|
-
if (!existsSync31(
|
|
57861
|
+
if (!existsSync31(join40(projectPath, "project.godot"))) {
|
|
57496
57862
|
throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
|
|
57497
57863
|
}
|
|
57498
57864
|
return projectPath;
|
|
@@ -57537,7 +57903,7 @@ async function registerRuntime(godot, projectPath) {
|
|
|
57537
57903
|
return;
|
|
57538
57904
|
}
|
|
57539
57905
|
console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
|
|
57540
|
-
if (!existsSync31(
|
|
57906
|
+
if (!existsSync31(join40(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
|
|
57541
57907
|
console.log(` that file is not in this project, so it boots with a missing script: restore it, or point the entry at res://${RUNTIME_AUTOLOAD.path}`);
|
|
57542
57908
|
}
|
|
57543
57909
|
}
|
|
@@ -57909,7 +58275,8 @@ Usage:
|
|
|
57909
58275
|
an export if it is left on
|
|
57910
58276
|
gdharness classes [project] Rebuild .godot/global_script_class_cache.cfg from disk
|
|
57911
58277
|
gdharness test [project] [suite ...] [--engines N] [--slots DIR] [--slot-count N]
|
|
57912
|
-
[--silence S] [--tests DIR] [--changed-only] [--reach-also PATH]
|
|
58278
|
+
[--silence S] [--deadline S] [--tests DIR] [--changed-only] [--reach-also PATH]
|
|
58279
|
+
[--import] [--godot PATH] [--godot-version V] [--cores N]
|
|
57913
58280
|
[--order reversed|SEED] [--print-output] [--runner]
|
|
57914
58281
|
Run the project's gdUnit4 suites over several engines and
|
|
57915
58282
|
judge every line they print: exit 1 on a failing case, an
|
|
@@ -57943,7 +58310,7 @@ async function test() {
|
|
|
57943
58310
|
return;
|
|
57944
58311
|
}
|
|
57945
58312
|
const options = testOptionsOf(rest, process.cwd());
|
|
57946
|
-
const
|
|
58313
|
+
const godotPath = options.godotPath ?? (await engine(options.projectPath)).godotPath;
|
|
57947
58314
|
process.exitCode = await runTestCommand(options, godotPath);
|
|
57948
58315
|
}
|
|
57949
58316
|
async function main() {
|