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 CHANGED
@@ -10094,7 +10094,15 @@ var init_import_freshness = __esm(() => {
10094
10094
  });
10095
10095
 
10096
10096
  // src/library-copies.ts
10097
- import { existsSync as existsSync9, readdirSync as readdirSync7, readFileSync as readFileSync7, renameSync, unlinkSync } from "node:fs";
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 writeFileSync3 } from "node:fs";
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
- writeFileSync3(sidecar, readFileSync8(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
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 writeFileSync4 } from "node:fs";
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
- writeFileSync4(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
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 mkdirSync3, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "node:fs";
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
- mkdirSync3(directory, { recursive: true, mode: 448 });
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
- writeFileSync5(path, JSON.stringify(entry), { encoding: "utf8", mode: 384 });
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 mkdirSync4,
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 writeFileSync6
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
- mkdirSync4(dirname8(target), { recursive: true });
12167
+ mkdirSync5(dirname8(target), { recursive: true });
12122
12168
  cpSync(source, target, { recursive: true });
12123
- writeFileSync6(join16(target, VERSION_MARKER), `${SERVER_VERSION}
12169
+ writeFileSync7(join16(target, VERSION_MARKER), `${SERVER_VERSION}
12124
12170
  `);
12125
12171
  installed.push({ name, path: target, replaced });
12126
12172
  }
12127
- writeFileSync6(join16(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
12173
+ writeFileSync7(join16(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
12128
12174
  `);
12129
- writeFileSync6(join16(projectPath, "addons", RUNTIME_ADDON, DIGEST_MARKER), `${runtimeAddonDigest(from)}
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 writeFileSync8,
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
- writeFileSync8(source, SOURCE, "utf8");
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
- writeFileSync8(files.spec, fields.map((field) => Buffer.from(field, "utf8").toString("base64")).join(`
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
- return Run(field(0), field(1), field(2), field(3) == "1", field(4), field(5), field(6));
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
- public static int Run(string desktop, string commandLine, string pidFile, bool noActivate, string stopFile, string directory, string stepFile) {
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
- if (!CreateProcessW(null, new StringBuilder(commandLine), IntPtr.Zero, IntPtr.Zero, true, DetachedProcess, IntPtr.Zero, directory, ref startup, out started)) {
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
- faults.push(end.code === null ? `the engine was ended by ${end.signal ?? "a signal"}` : `the engine exited ${exitNamed(end.code)}`);
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 writeFileSync9 } from "node:fs";
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
- writeFileSync9(partial, text, "utf8");
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 mkdirSync7, readFileSync as readFileSync18 } from "node:fs";
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 prefixes = roots.map((root) => root.endsWith("/") ? root : `${root}/`);
15574
- return [...this.sources.keys()].filter((path) => prefixes.some((prefix) => path.startsWith(prefix))).sort().flatMap((path) => this.suite(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
- mkdirSync7(dirname10(file), { recursive: true });
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
- next[path] = { seconds: round(Math.max(0, time.seconds - dropped)), cases };
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) ?? { cases: new Map, around: 0 };
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 seconds = entry.cases.get(func) ?? times[path]?.cases[func];
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] = { seconds: round(entry.around + inCases), cases };
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
- if (suite.windowed || whole <= splitPast || suite.cases.length < 2) {
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: [func],
15669
- cost: around + costOf(func),
15670
- windowed: false,
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
- held.set(unit.suite, [...held.get(unit.suite) ?? [], ...unit.cases]);
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]) => ({ path, cases: [...cases].sort() })),
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(projectPath, paths) {
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 join23 } from "node:path";
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(join23(projectPath, "project.godot"), "utf8"));
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 = join23(projectPath, path);
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 join24, resolve as resolve6 } from "node:path";
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 join24(perUser ?? tmpdir5(), "gdharness");
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(join24(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
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 = join24(directory, `runtime-${pid}.log`);
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(join24(directory, entry), Number.parseInt(report[1] ?? "", 10));
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 = join24(directory, entry);
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 = join24(directory, ".");
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) => join24(directory, `runtime-${pid}.json`)).filter((file) => existsSync21(file));
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 mkdirSync9, readFileSync as readFileSync23, writeFileSync as writeFileSync11 } from "node:fs";
38044
- import { dirname as dirname13, join as join25 } from "node:path";
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 join25(projectPath, ".godot", "gdharness-boot.json");
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
- mkdirSync9(dirname13(path), { recursive: true });
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
- writeFileSync11(path, JSON.stringify({ scenes }, null, 2), "utf8");
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 mkdirSync10, readFileSync as readFileSync24, rmSync as rmSync11, writeFileSync as writeFileSync12 } from "node:fs";
38093
- import { dirname as dirname14, isAbsolute as isAbsolute7, join as join26, relative as relative4 } from "node:path";
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 join26(projectPath, ".godot", "gdharness-breakpoints.json");
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
- mkdirSync10(dirname14(path), { recursive: true });
38113
- writeFileSync12(path, `${JSON.stringify(note, null, 2)}
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(join26(projectPath, script)), lines });
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 mkdirSync11, readFileSync as readFileSync25 } from "node:fs";
38155
- import { dirname as dirname15, join as join27 } from "node:path";
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 join27(projectPath, ".godot", "gdharness-classes.json");
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
- mkdirSync11(dirname15(path), { recursive: true });
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 join28 } from "node:path";
38446
+ import { join as join29 } from "node:path";
38205
38447
  function versionsNamedInConfig(projectPath) {
38206
- const config = join28(projectPath, ".mcp.json");
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 mkdirSync12, readFileSync as readFileSync27, rmSync as rmSync12, writeFileSync as writeFileSync13 } from "node:fs";
38234
- import { dirname as dirname16, join as join29 } from "node:path";
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 join29(projectPath, ".godot", "gdharness-reports", "cut-run.json");
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
- mkdirSync12(dirname16(path), { recursive: true });
38242
- writeFileSync13(path, `${JSON.stringify(run, null, 2)}
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 mkdirSync13,
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 writeFileSync14
39360
+ writeFileSync as writeFileSync15
39119
39361
  } from "node:fs";
39120
- import { dirname as dirname17, join as join30 } from "node:path";
39362
+ import { dirname as dirname17, join as join31 } from "node:path";
39121
39363
  function editorLogPath(projectPath) {
39122
- return join30(projectPath, ".godot", "gdharness-editor.log");
39364
+ return join31(projectPath, ".godot", "gdharness-editor.log");
39123
39365
  }
39124
39366
  function editorLogNotePath(projectPath) {
39125
- return join30(projectPath, ".godot", "gdharness-editor.json");
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
- mkdirSync13(dirname17(path), { recursive: true });
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
- mkdirSync13(dirname17(path), { recursive: true });
39150
- writeFileSync14(path, JSON.stringify({
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 mkdirSync14, readFileSync as readFileSync29, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync15 } from "node:fs";
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
- mkdirSync14(dirname18(rewrite.writeTo), { recursive: true });
40352
+ mkdirSync15(dirname18(rewrite.writeTo), { recursive: true });
40111
40353
  const staging = `${rewrite.writeTo}.gdharness-rename`;
40112
- writeFileSync15(staging, rewrite.after, "utf8");
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
- writeFileSync15(rewrite.absolute, rewrite.before, "utf8");
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 join31, resolve as resolve7 } from "node:path";
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(join31(projectPath, "project.godot"))) {
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 mkdirSync15, readdirSync as readdirSync14, realpathSync as realpathSync4, rmSync as rmSync14, writeFileSync as writeFileSync16 } from "node:fs";
41005
- import { basename as basename9, dirname as dirname20, join as join32, relative as relative5, resolve as resolve8 } from "node:path";
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 join32(realpathSync4.native(at), ...missing.reverse());
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 = join32(projectPath, ".godot", "gdharness-renames");
41448
- mkdirSync15(directory, { recursive: true });
41689
+ const directory = join33(projectPath, ".godot", "gdharness-renames");
41690
+ mkdirSync16(directory, { recursive: true });
41449
41691
  detailWrites += 1;
41450
- const path = join32(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
41451
- writeFileSync16(path, `${JSON.stringify(detail, null, 2)}
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(join32(directory, old), { force: true });
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 mkdirSync16,
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 writeFileSync17
41744
+ writeFileSync as writeFileSync18
41503
41745
  } from "node:fs";
41504
- import { basename as basename10, dirname as dirname21, extname as extname3, join as join33, relative as relative6 } from "node:path";
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 = join33(directory, entry.name);
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 = join33(directory, step);
41529
- if (existsSync28(join33(directory, ".gdignore"))) {
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(join33(projectPath, "addons", steps[1], VERSION_MARKER)) ? `res://addons/${steps[1]}` : null;
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(join33(directory, ".gdignore"))) {
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(join33(directory, entry.name), `${path}/`);
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), join33(onDiskSpelling(dirname21(absolute)), basename10(absolute)));
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(join33(projectPath, path.slice("res://".length))));
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(join33(projectPath, path.slice("res://".length)))
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(join33(projectPath, path.slice("res://".length)));
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) => join33(projectPath, path.slice("res://".length));
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
- mkdirSync16(dirname21(target), { recursive: true });
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
- writeFileSync17(staging, rewrite.after, "utf8");
41988
+ writeFileSync18(staging, rewrite.after, "utf8");
41747
41989
  renameSync4(staging, rewrite.absolute);
41748
41990
  undo.push(() => {
41749
- writeFileSync17(rewrite.absolute, rewrite.before, "utf8");
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
- writeFileSync17(file.absoluteTo, after, "utf8");
42003
+ writeFileSync18(file.absoluteTo, after, "utf8");
41762
42004
  undo.push(() => {
41763
- writeFileSync17(file.absoluteTo, before, "utf8");
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 mkdirSync17, renameSync as renameSync5, statSync as statSync17 } from "node:fs";
45466
- import { dirname as dirname22, join as join34 } from "node:path";
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 join34(runtimeDirectory(), "servers.log");
45711
+ return join35(runtimeDirectory(), "servers.log");
45470
45712
  }
45471
45713
  function writeLifecycle(event, fields = {}) {
45472
45714
  const path = lifecycleLogPath();
45473
45715
  try {
45474
- mkdirSync17(dirname22(path), { recursive: true });
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 mkdirSync18, readFileSync as readFileSync31, rmSync as rmSync15, writeFileSync as writeFileSync18 } from "node:fs";
45834
- import { dirname as dirname23, join as join35 } from "node:path";
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 join35(projectPath, ".godot", "gdharness-restart.json");
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
- mkdirSync18(dirname23(path), { recursive: true });
45842
- writeFileSync18(path, `${JSON.stringify(note, null, 2)}
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 mkdirSync19, openSync as openSync5, readdirSync as readdirSync16, readFileSync as readFileSync32, rmSync as rmSync16, statSync as statSync18 } from "node:fs";
45877
- import { basename as basename11, join as join36, resolve as resolve9 } from "node:path";
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 join36(runtimeDirectory(), "runs");
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 join36(directory, "runs", `note-${projectKey(projectPath)}.json`);
46129
+ return join37(directory, "runs", `note-${projectKey(projectPath)}.json`);
45888
46130
  }
45889
46131
  function latestRunPath(servedBy, directory = runtimeDirectory()) {
45890
- return join36(directory, "runs", `latest-${projectKey(servedBy)}.json`);
46132
+ return join37(directory, "runs", `latest-${projectKey(servedBy)}.json`);
45891
46133
  }
45892
46134
  function writeLatestRun(servedBy, latest) {
45893
46135
  try {
45894
- mkdirSync19(runsDirectory(), { recursive: true });
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
- mkdirSync19(runsDirectory(), { recursive: true });
45915
- const path = join36(runsDirectory(), `run-${startedAt}.log`);
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 join36(runsDirectory(), `run-${startedAt}.profile.json`);
46161
+ return join37(runsDirectory(), `run-${startedAt}.profile.json`);
45920
46162
  }
45921
46163
  function profileMarkPath(startedAt) {
45922
- return join36(runsDirectory(), `run-${startedAt}.profile-mark.json`);
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 join36(directory, "runs", `editor-${projectKey(projectPath)}.json`);
46170
+ return join37(directory, "runs", `editor-${projectKey(projectPath)}.json`);
45929
46171
  }
45930
46172
  function writeEditorRunNote(note) {
45931
- mkdirSync19(runsDirectory(), { recursive: true });
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(join36(directory, "runs"));
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(join36(directory, "runs", entry));
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 = join36(directory, entry);
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 join37 } from "node:path";
46747
+ import { basename as basename12, join as join38 } from "node:path";
46506
46748
  function casesIn(projectPath, suite) {
46507
- const text = readFileSync34(join37(projectPath, suite.slice("res://".length)), "utf8");
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(join37(projectPath, path.slice("res://".length))) && casesIn(projectPath, path).length === 0);
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(join37(projectPath, path.slice("res://".length))) ? [path] : [] : scripts.filter((script) => script.startsWith(`${path}/`))))
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 mkdirSync20,
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 writeFileSync19
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 join38, normalize as normalize2, relative as relative7 } from "node:path";
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 = join38(directory, basename13(path));
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(join38(projectPath, "export_presets.cfg"), "utf8");
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(join38(folder, entry.name)).mtimeMs);
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(join38(projectPath, "project.godot"))) {
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 = join38(projectPath, file);
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
- writeFileSync19(absolute, after.join(`
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(join38(projectPath, file), "utf8").split(`
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(join38(projectPath, "project.godot"), "utf8");
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(join38(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
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(join38(projectPath, wanted))) {
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(join38(projectPath, directory)));
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 = join38(__dirname2, "godot", "operations", "godot_operations.gd");
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 = join38(cwd, ".mcp.json");
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(join38(named, "project.godot"))) {
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 = join38(directory, item.type === "text" ? `${stem}-${index}.txt` : `${stem}-${index}.${item.mimeType.split("/")[1] ?? "bin"}`);
48528
- writeFileSync19(file, item.type === "text" ? item.text : Buffer.from(item.data, "base64"));
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 = join38(path, "project.godot");
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: join38(project.value.path, "export_presets.cfg"),
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
- writeFileSync19(join38(empty, "project.godot"), `config_version=5
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
- mkdirSync20(folder, { recursive: true });
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
- join38(exportLogs, "engine.log"),
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(join38(project.value.path, path.slice("res://".length))));
50279
- const asked = paths.value.filter((path) => !missing.includes(path));
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(project.value.path, selection.value?.suites ?? asked);
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 = join38(userData, "suites.txt");
50351
- const skippingFile = join38(userData, "skipping.txt");
50352
- const engineLog = join38(userData, "engine.log");
50353
- const expectedFile = join38(userData, "expected.json");
50354
- writeFileSync19(suitesFile, `${(selection.value?.suites ?? asked).join(`
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
- writeFileSync19(skippingFile, `${[...ignored, ...selection.value?.ignore ?? []].join(`
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: join38(userData, "spec"),
50395
- pid: join38(userData, "pid"),
50396
- error: join38(userData, "error"),
50397
- stop: join38(userData, "stop"),
50398
- step: join38(userData, "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(join38(project.value.path, ".godot", ...ours.split("/")), userData);
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 = join38(project.value.path, ".godot", ...ours.split("/"));
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(join38(reportsDir, newest, "results.xml"), "utf8"));
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: join38(projectPath, frame["script"].slice("res://".length)) } : {}
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(join38(projectPath, "project.godot"), "utf8"));
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(join38(projectPath, RUNTIME_AUTOLOAD.path))) {
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
- writeFileSync19(onDesktop.files.stop, "");
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(join38(projectPath, "project.godot"));
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(join38(run.projectPath, RUNTIME_AUTOLOAD.path)),
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(join38(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
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 ? join38(screenshotDir, "capture.png") : null;
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 join39, resolve as resolve10 } from "node:path";
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 mkdirSync5, readdirSync as readdirSync10, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
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
- mkdirSync5(dirname9(path), { recursive: true });
56525
- writeFileSync7(path, contents, "utf8");
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 mkdirSync8,
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 writeFileSync10
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 join22, relative as relative3, resolve as resolve5, sep as sep2 } from "node:path";
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 mkdirSync6, openSync as openSync2, readFileSync as readFileSync15, rmSync as rmSync8, statSync as statSync11, writeSync as writeSync2 } from "node:fs";
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
- mkdirSync6(this.directory, { recursive: true });
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 join21 } from "node:path";
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
- join21(currentDirectory, "..", "package.json"),
56849
- join21(currentDirectory, "..", "..", "package.json")
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(join22(named, "project.godot"));
57158
+ const projectNamed = named !== null && existsSync19(join23(named, "project.godot"));
56910
57159
  const projectPath = projectNamed ? named : resolve5(cwd);
56911
- if (!existsSync19(join22(projectPath, "project.godot"))) {
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(join22(options.projectPath, root))).map((root) => `res://${root}`);
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 = join22(options.projectPath, path.slice(6));
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` : `the machine has ${plural(processors, "processor")}`
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 = join22(home, ".godot", "gdharness");
57082
- const timesFile = join22(state, "suite-times.json");
57083
- const passedFile = join22(state, "suite-passed.json");
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 = join22(options.projectPath, ".godot", "gdharness", "test-runs");
57371
+ const testRuns = join23(options.projectPath, ".godot", "gdharness", "test-runs");
57088
57372
  sweepRunDirectories(testRuns);
57089
- const runDirectory = join22(testRuns, `${String(startedAt)}-${String(process12.pid)}`);
57090
- mkdirSync8(runDirectory, { recursive: true });
57091
- const slots = new EngineSlots(options.slotsDirectory ?? join22(home, ".godot", "gate", "slots"), options.slotCount ?? Math.min(MOST_ENGINES, availableParallelism()), { pid: process12.pid, started: await ownStart() });
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
- recordPassed(passedFile, ran, printsOf);
57174
- return report(ran, startedAt, runDirectory, options.printOutput);
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(join22(testRuns, entry), { recursive: true, force: true });
57534
+ rmSync10(join23(testRuns, entry), { recursive: true, force: true });
57238
57535
  }
57239
57536
  }
57240
57537
  }
57241
57538
  async function importProject(launch) {
57242
- const directory = join22(launch.runDirectory, "import");
57243
- mkdirSync8(join22(directory, "user"), { recursive: true });
57244
- const log = join22(directory, "import.log");
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
- const ended = await watched(launch, ["--headless", "--audio-driver", "Dummy", "--path", launch.projectPath, "--import"], directory, log, false, launch.silenceFloorSeconds);
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 ${String(ended.code)}`}; everything it printed is in ${log}`;
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 = join22(launch.runDirectory, `engine-${String(index)}${again === "" ? "" : `-${again}`}`);
57270
- mkdirSync8(join22(directory, "user"), { recursive: true });
57271
- const suitesFile = join22(directory, "suites.txt");
57272
- const skippingFile = join22(directory, "skipping.txt");
57273
- const expectedFile = join22(directory, "expected.json");
57274
- writeFileSync10(suitesFile, `${plan.suites.map((suite) => suite.path).join(`
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
- writeFileSync10(skippingFile, `${plan.skipping.join(`
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, join22(directory, "reports")).split(sep2).join("/")}`,
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 = join22(directory, "engine.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, plan.windowed, silence);
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, windowed, silenceSeconds) {
57337
- const user = join22(directory, "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" && (windowed || !crashesReported())) {
57345
- const where = windowed ? "on a desktop of its own" : "through the desktop helper";
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 = join22(directory, "stop");
57351
- const error = join22(directory, "error");
57352
- helper = { error, how: where, pid: join22(directory, "pid"), step: join22(directory, "step") };
57353
- const how = windowed ? { desktop: HIDDEN_DESKTOP, directory: launch.projectPath } : { directory: launch.projectPath };
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: join22(directory, "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: windowed ? { ...env, [DESKTOP_VARIABLE]: HIDDEN_DESKTOP } : env,
57694
+ env: { ...env, [DESKTOP_VARIABLE]: HIDDEN_DESKTOP },
57364
57695
  cwd: directory
57365
57696
  });
57366
57697
  stop = () => {
57367
- writeFileSync10(stopFile, "");
57698
+ writeFileSync11(stopFile, "");
57368
57699
  };
57369
57700
  } else {
57370
- child = spawnHidden(launch.godotPath, args, {
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: process12.platform !== "win32"
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
- let lastGrowth = Date.now();
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(`--- engine ${String(engine.index)}: ${plural(engine.plan.suites.length, "suite")}, ${engine.seconds.toFixed(1)} s`);
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, 0);
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(join39(projectPath, "project.godot"))) {
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(join39(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
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] [--import]
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 { godotPath } = await engine(options.projectPath);
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() {