gdharness 1.3.38 → 1.3.39
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
|
@@ -11334,7 +11334,8 @@ import {
|
|
|
11334
11334
|
readSync,
|
|
11335
11335
|
rmSync as rmSync4,
|
|
11336
11336
|
statSync as statSync5,
|
|
11337
|
-
writeFileSync as writeFileSync4
|
|
11337
|
+
writeFileSync as writeFileSync4,
|
|
11338
|
+
writeSync as writeSync2
|
|
11338
11339
|
} from "node:fs";
|
|
11339
11340
|
import { availableParallelism, freemem, totalmem } from "node:os";
|
|
11340
11341
|
import { isAbsolute as isAbsolute3, join as join7, relative as relative3, resolve as resolve3, sep as sep2 } from "node:path";
|
|
@@ -11489,6 +11490,7 @@ async function watchedEngine(launch, args, directory, log, silenceSeconds, silen
|
|
|
11489
11490
|
const output = openSync2(log, "w");
|
|
11490
11491
|
let child;
|
|
11491
11492
|
let stop;
|
|
11493
|
+
let piped = null;
|
|
11492
11494
|
let helper = null;
|
|
11493
11495
|
try {
|
|
11494
11496
|
if (process6.platform === "win32") {
|
|
@@ -11512,11 +11514,13 @@ async function watchedEngine(launch, args, directory, log, silenceSeconds, silen
|
|
|
11512
11514
|
stop: stopFile,
|
|
11513
11515
|
step: helper.step
|
|
11514
11516
|
});
|
|
11515
|
-
|
|
11516
|
-
stdio: ["ignore",
|
|
11517
|
+
const helped = spawnHidden(wrapper.command, wrapper.args, {
|
|
11518
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
11517
11519
|
env: { ...env, [DESKTOP_VARIABLE]: HIDDEN_DESKTOP },
|
|
11518
11520
|
cwd: directory
|
|
11519
11521
|
});
|
|
11522
|
+
piped = [helped.stdout, helped.stderr];
|
|
11523
|
+
child = helped;
|
|
11520
11524
|
stop = () => {
|
|
11521
11525
|
writeFileSync4(stopFile, "");
|
|
11522
11526
|
};
|
|
@@ -11533,7 +11537,9 @@ async function watchedEngine(launch, args, directory, log, silenceSeconds, silen
|
|
|
11533
11537
|
};
|
|
11534
11538
|
}
|
|
11535
11539
|
} finally {
|
|
11536
|
-
|
|
11540
|
+
if (piped === null) {
|
|
11541
|
+
closeSync2(output);
|
|
11542
|
+
}
|
|
11537
11543
|
}
|
|
11538
11544
|
launch.running.add(stop);
|
|
11539
11545
|
let ended = null;
|
|
@@ -11546,6 +11552,7 @@ async function watchedEngine(launch, args, directory, log, silenceSeconds, silen
|
|
|
11546
11552
|
});
|
|
11547
11553
|
})
|
|
11548
11554
|
]);
|
|
11555
|
+
const copied = piped === null ? null : copiedInto(output, piped, exited);
|
|
11549
11556
|
const finished = { now: false };
|
|
11550
11557
|
exited.then(() => {
|
|
11551
11558
|
finished.now = true;
|
|
@@ -11598,12 +11605,42 @@ async function watchedEngine(launch, args, directory, log, silenceSeconds, silen
|
|
|
11598
11605
|
}
|
|
11599
11606
|
const { code, signal } = await exited;
|
|
11600
11607
|
const freeBytes = code !== null && code >>> 0 >= WINDOWS_EXCEPTION ? freemem() : undefined;
|
|
11608
|
+
if (copied !== null) {
|
|
11609
|
+
await copied;
|
|
11610
|
+
closeSync2(output);
|
|
11611
|
+
}
|
|
11601
11612
|
const failed = helper === null ? null : helperFailure(helper.error);
|
|
11602
11613
|
if (failed !== null && helper !== null) {
|
|
11603
11614
|
ended ??= `the engine could not be started ${helper.how}: ${failed}`;
|
|
11604
11615
|
}
|
|
11605
11616
|
return { code, signal, ended, ...freeBytes === undefined ? {} : { freeBytes } };
|
|
11606
11617
|
}
|
|
11618
|
+
function copiedInto(output, streams, exited) {
|
|
11619
|
+
let writing = true;
|
|
11620
|
+
const ended = streams.map((stream) => new Promise((settle) => {
|
|
11621
|
+
stream.on("data", (chunk) => {
|
|
11622
|
+
if (!writing) {
|
|
11623
|
+
return;
|
|
11624
|
+
}
|
|
11625
|
+
try {
|
|
11626
|
+
writeSync2(output, chunk);
|
|
11627
|
+
} catch {
|
|
11628
|
+
writing = false;
|
|
11629
|
+
}
|
|
11630
|
+
});
|
|
11631
|
+
stream.once("close", settle);
|
|
11632
|
+
}));
|
|
11633
|
+
const all = Promise.all(ended).then(() => {
|
|
11634
|
+
return;
|
|
11635
|
+
});
|
|
11636
|
+
const drained = exited.then(() => delay2(DRAIN_MS, undefined, { ref: false }));
|
|
11637
|
+
return Promise.race([all, drained]).then(() => {
|
|
11638
|
+
writing = false;
|
|
11639
|
+
for (const stream of streams) {
|
|
11640
|
+
stream.destroy();
|
|
11641
|
+
}
|
|
11642
|
+
});
|
|
11643
|
+
}
|
|
11607
11644
|
function endTree(child) {
|
|
11608
11645
|
if (child.pid === undefined) {
|
|
11609
11646
|
return;
|
|
@@ -11616,7 +11653,7 @@ function endTree(child) {
|
|
|
11616
11653
|
process6.kill(-child.pid, "SIGKILL");
|
|
11617
11654
|
} catch {}
|
|
11618
11655
|
}
|
|
11619
|
-
var EngineUsageError, MOST_ENGINES = 4, SILENCE_FLOOR_SECONDS = 300, CONTROL_SEQUENCE, WINDOWS_EXCEPTION = 3221225472, MEMORY_KEPT, MEMORY_PER_ENGINE, LOOK_MS = 500, COMMIT_ASK_MS = 15000, AUTOLOAD_FAILED = "Failed to instantiate an autoload", KEPT_RUN_DIRECTORIES = 5;
|
|
11656
|
+
var EngineUsageError, MOST_ENGINES = 4, SILENCE_FLOOR_SECONDS = 300, CONTROL_SEQUENCE, WINDOWS_EXCEPTION = 3221225472, MEMORY_KEPT, MEMORY_PER_ENGINE, LOOK_MS = 500, COMMIT_ASK_MS = 15000, AUTOLOAD_FAILED = "Failed to instantiate an autoload", KEPT_RUN_DIRECTORIES = 5, DRAIN_MS = 5000;
|
|
11620
11657
|
var init_engine_launch = __esm(() => {
|
|
11621
11658
|
init_alive();
|
|
11622
11659
|
init_desktop();
|
|
@@ -11744,7 +11781,7 @@ import {
|
|
|
11744
11781
|
rmSync as rmSync6,
|
|
11745
11782
|
statSync as statSync6,
|
|
11746
11783
|
watch,
|
|
11747
|
-
writeSync as
|
|
11784
|
+
writeSync as writeSync3
|
|
11748
11785
|
} from "node:fs";
|
|
11749
11786
|
import { availableParallelism as availableParallelism2, homedir as homedir2 } from "node:os";
|
|
11750
11787
|
import { join as join8, resolve as resolve4 } from "node:path";
|
|
@@ -11970,7 +12007,7 @@ class EngineSlots {
|
|
|
11970
12007
|
return false;
|
|
11971
12008
|
}
|
|
11972
12009
|
try {
|
|
11973
|
-
|
|
12010
|
+
writeSync3(made, JSON.stringify(this.holder));
|
|
11974
12011
|
} finally {
|
|
11975
12012
|
closeSync3(made);
|
|
11976
12013
|
}
|
|
@@ -12084,7 +12121,7 @@ async function inASlot(body) {
|
|
|
12084
12121
|
held.release();
|
|
12085
12122
|
}
|
|
12086
12123
|
}
|
|
12087
|
-
var SLOTS_VARIABLE = "GODOT_ENGINE_SLOTS", SLOT_COUNT_VARIABLE = "GODOT_ENGINE_SLOT_COUNT", PROCESSORS_A_SLOT =
|
|
12124
|
+
var SLOTS_VARIABLE = "GODOT_ENGINE_SLOTS", SLOT_COUNT_VARIABLE = "GODOT_ENGINE_SLOT_COUNT", PROCESSORS_A_SLOT = 2, START_TOLERANCE_MS = 2000, UNWRITTEN_MS = 1e4, SLOT_LOOK_MS = 1000, SLOT_SETTLE_MS = 50, WAIT_SAID_MS = 20000, COMMAND_WAIT_SAID_MS = 2000, START_READ_MS = 1e4, WAITING_FOLDER = "waiting", waitsMarked = 0, ours, saidTo, takenBy;
|
|
12088
12125
|
var init_engine_slots = __esm(() => {
|
|
12089
12126
|
init_alive();
|
|
12090
12127
|
init_call_signal();
|
|
@@ -17364,7 +17401,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17364
17401
|
name: "script_diagnostics",
|
|
17365
17402
|
title: "Script diagnostics",
|
|
17366
17403
|
effect: "read",
|
|
17367
|
-
description: "Errors and warnings for a script, or for several, from the editor's language server, and whether each is clean. With no editor of this project open, so no language server of it to ask, a short headless engine compiles the scripts instead and the answer says checkedBy: engine rather than language server: every error that stops a script compiling, up to its first syntax error, with its line and no column (a call to a method another class lacks is not one, since GDScript checks those only on self: the object may be a subclass that has it, so it fails at run time instead), dependenciesWithErrors naming the other scripts that failed while it loaded, typesMissingFromTheClassCache naming a class the project declares that the engine could not resolve because the class cache lacks it, with staleAnalysis saying what rewrites the cache, and warnings
|
|
17404
|
+
description: "Errors and warnings for a script, or for several, from the editor's language server, and whether each is clean. With no editor of this project open, so no language server of it to ask, a short headless engine compiles the scripts instead and the answer says checkedBy: engine rather than language server: every error that stops a script compiling, up to its first syntax error, with its line and no column (a call to a method another class lacks is not one, since GDScript checks those only on self: the object may be a subclass that has it, so it fails at run time instead), dependenciesWithErrors naming the other scripts that failed while it loaded, typesMissingFromTheClassCache naming a class the project declares that the engine could not resolve because the class cache lacks it, with staleAnalysis saying what rewrites the cache, and the warnings at the levels the project sets, which a second engine reports by raising each warning set to warn as an error for that run only, each at its line with no column, warnings being null when that engine could not answer. Of several scripts, a language server that stops answering partway leaves the rest to the engine, and checkedBy then says language server, then engine, with checkedByLanguageServer counting the scripts it answered. Each diagnostic carries its severity (error or warning), its message, and where it is as the editor shows it: line and column counted from 1, and endLine and endColumn just past its end. Diagnostics are checked against the project's own files, and the two that the files disprove are named rather than passed on: a member the class cache's file declares, or a project class it extends does, a method, property, constant, static function or enum member, comes back under contradictedByTheFile with the script declaring it, and so does one a diagnostic names alone as an external class member, read against the class it is looked up on in the line it is on; and a class this project declares that the diagnostic could not resolve comes back under typesTheEditorHasNotLoaded, with inTheClassCache saying whether a launched game would resolve it. staleAnalysis then says what to do, and for both states it is editor_rescan first: it asks the editor already running, starts nothing, and has cleared each in every reproduction measured. Two exceptions are named when they apply. For an enum member, constant or static function of a global class the reload comes first: in the one project that has reproduced a stale enum member, editor_rescan with reloadScript on the declaring script cleared it and a plain rescan did not. And an editor whose last scan wrote the class cache shorter than the file holds a list behind the files and writes it on every scan, so on that editor the rescan loads nothing and staleAnalysis says editor_launch restart instead. What follows differs: for a stale analysed type, editor_rescan with reloadScript recompiles the held copy and editor_launch restart is the last resort that costs a window; for a class missing from the cache, project_import refresh_classes rewrites the cache from disk at the cost of a short headless engine, which matters beside a running bench.",
|
|
17368
17405
|
parameters: {
|
|
17369
17406
|
projectPath: PROJECT_PATH,
|
|
17370
17407
|
scriptPath: {
|
|
@@ -18665,6 +18702,7 @@ class ProjectScripts {
|
|
|
18665
18702
|
suite(path) {
|
|
18666
18703
|
const cases = new Set;
|
|
18667
18704
|
let said;
|
|
18705
|
+
let together;
|
|
18668
18706
|
let guessed = false;
|
|
18669
18707
|
const seen = new Set;
|
|
18670
18708
|
let at = path;
|
|
@@ -18679,13 +18717,21 @@ class ProjectScripts {
|
|
|
18679
18717
|
}
|
|
18680
18718
|
const declared = NEEDS_WINDOW.exec(read.code)?.[1];
|
|
18681
18719
|
said ??= declared === undefined ? undefined : declared === "true";
|
|
18720
|
+
const kept = TOGETHER.exec(read.code)?.[1];
|
|
18721
|
+
together ??= kept === undefined ? undefined : kept === "true";
|
|
18682
18722
|
guessed ||= this.guessWindow && SCENE_RUNNER.test(read.code);
|
|
18683
18723
|
const base = this.base(at);
|
|
18684
18724
|
if (base === null) {
|
|
18685
18725
|
return null;
|
|
18686
18726
|
}
|
|
18687
18727
|
if ("name" in base ? base.name === SUITE_BASE : base.path === this.classes.get(SUITE_BASE)) {
|
|
18688
|
-
return cases.size > 0 ? {
|
|
18728
|
+
return cases.size > 0 ? {
|
|
18729
|
+
path,
|
|
18730
|
+
cases: [...cases].sort(),
|
|
18731
|
+
windowed: said ?? guessed,
|
|
18732
|
+
headless: said === false,
|
|
18733
|
+
together: together === true
|
|
18734
|
+
} : null;
|
|
18689
18735
|
}
|
|
18690
18736
|
if ("name" in base) {
|
|
18691
18737
|
return null;
|
|
@@ -18808,6 +18854,10 @@ function rowsAfter(before, measured, cutShort) {
|
|
|
18808
18854
|
function round(seconds) {
|
|
18809
18855
|
return Math.round(seconds * 1000) / 1000;
|
|
18810
18856
|
}
|
|
18857
|
+
function splitPastFor(suites, times, engines) {
|
|
18858
|
+
const whole = unitsOf(suites, times, Number.POSITIVE_INFINITY).reduce((sum, unit) => sum + unit.cost, 0);
|
|
18859
|
+
return Math.max(SPLIT_PAST_SECONDS, whole / Math.max(1, engines));
|
|
18860
|
+
}
|
|
18811
18861
|
function median(values) {
|
|
18812
18862
|
if (values.length === 0) {
|
|
18813
18863
|
return;
|
|
@@ -18834,7 +18884,7 @@ function unitsOf(suites, times, splitPast = SPLIT_PAST_SECONDS) {
|
|
|
18834
18884
|
windowed: suite.windowed,
|
|
18835
18885
|
headless: suite.headless
|
|
18836
18886
|
};
|
|
18837
|
-
if (suite.windowed || whole <= splitPast) {
|
|
18887
|
+
if (suite.windowed || suite.together || whole <= splitPast) {
|
|
18838
18888
|
return [wholly];
|
|
18839
18889
|
}
|
|
18840
18890
|
const units = suite.cases.flatMap((func) => {
|
|
@@ -19003,13 +19053,14 @@ function viewportOf(projectPath) {
|
|
|
19003
19053
|
const height = /^window\/size\/viewport_height\s*=\s*(\d+)/m.exec(text)?.[1];
|
|
19004
19054
|
return width === undefined || height === undefined ? null : { width: Number(width), height: Number(height) };
|
|
19005
19055
|
}
|
|
19006
|
-
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, SHARE_FLOOR_SECONDS;
|
|
19056
|
+
var SUITE_BASE = "GdUnitTestSuite", CASE_PREFIX = "test_", NEEDS_WINDOW, TOGETHER, 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, SHARE_FLOOR_SECONDS;
|
|
19007
19057
|
var init_suite_plan = __esm(() => {
|
|
19008
19058
|
init_class_cache();
|
|
19009
19059
|
init_gdscript_source();
|
|
19010
19060
|
init_resources();
|
|
19011
19061
|
init_whole_file();
|
|
19012
19062
|
NEEDS_WINDOW = /^const\s+GDHARNESS_NEEDS_WINDOW\b[^=\n]*=\s*(true|false)\b/m;
|
|
19063
|
+
TOGETHER = /^const\s+GDHARNESS_TOGETHER\b[^=\n]*=\s*(true|false)\b/m;
|
|
19013
19064
|
TEST_DIRECTORIES = ["tests", "test"];
|
|
19014
19065
|
SCENE_RUNNER = /\bscene_runner\s*\(/;
|
|
19015
19066
|
SHARE_FLOOR_SECONDS = 6 * ENGINE_START_SECONDS;
|
|
@@ -50416,9 +50467,13 @@ function engineChecked(why) {
|
|
|
50416
50467
|
return {
|
|
50417
50468
|
checkedBy: "engine",
|
|
50418
50469
|
languageServer: why,
|
|
50419
|
-
note: "No editor of this project is open to ask, so a headless engine compiled the scripts the way loading them does. It reports every error that stops a script compiling, at its line and without a column, but stops reading a script at its first syntax error, so an error after one shows once it is fixed.
|
|
50470
|
+
note: "No editor of this project is open to ask, so a headless engine compiled the scripts the way loading them does. It reports every error that stops a script compiling, at its line and without a column, but stops reading a script at its first syntax error, so an error after one shows once it is fixed. A second engine reports the warnings, at the levels the project sets, by raising each warning set to warn as an error for that run only, since the engine tells a warning as one only to an editor; each comes at its line without a column, and warnings is null when that engine could not answer. editor_launch with hidden: true opens this project's editor without a window, and its language server reports both with their columns."
|
|
50420
50471
|
};
|
|
50421
50472
|
}
|
|
50473
|
+
function asWarning(raised) {
|
|
50474
|
+
const bare = raised.endsWith(RAISED_WARNING) ? raised.slice(0, -RAISED_WARNING.length) : raised;
|
|
50475
|
+
return bare.startsWith(PARSE_ERROR2) ? bare.slice(PARSE_ERROR2.length) : bare;
|
|
50476
|
+
}
|
|
50422
50477
|
function isDirectory(path) {
|
|
50423
50478
|
try {
|
|
50424
50479
|
return statSync20(path).isDirectory();
|
|
@@ -53206,6 +53261,7 @@ ${error.text}`;
|
|
|
53206
53261
|
const nothingReported = [];
|
|
53207
53262
|
let errors = 0;
|
|
53208
53263
|
let warnings = 0;
|
|
53264
|
+
let engineWarned = 0;
|
|
53209
53265
|
let unread = 0;
|
|
53210
53266
|
let byEngine = null;
|
|
53211
53267
|
let byServer = 0;
|
|
@@ -53224,6 +53280,7 @@ ${error.text}`;
|
|
|
53224
53280
|
}
|
|
53225
53281
|
for (const answer of checked.answers) {
|
|
53226
53282
|
errors += Number(answer["errors"]);
|
|
53283
|
+
engineWarned = answer["warnings"] === null || engineWarned === null ? null : engineWarned + Number(answer["warnings"]);
|
|
53227
53284
|
if (readArray(answer, "diagnostics")?.length === 0) {
|
|
53228
53285
|
nothingReported.push(String(answer["scriptPath"]));
|
|
53229
53286
|
} else {
|
|
@@ -53254,7 +53311,7 @@ ${error.text}`;
|
|
|
53254
53311
|
clean: errors === 0 && unread === 0,
|
|
53255
53312
|
scriptsChecked: scripts.value.length,
|
|
53256
53313
|
errors,
|
|
53257
|
-
warnings:
|
|
53314
|
+
warnings: engineWarned === null ? null : warnings + engineWarned,
|
|
53258
53315
|
...unread > 0 ? { scriptsNotRead: unread } : {},
|
|
53259
53316
|
scripts: reported,
|
|
53260
53317
|
nothingReported,
|
|
@@ -53289,6 +53346,20 @@ ${error.text}`;
|
|
|
53289
53346
|
}
|
|
53290
53347
|
return { ok: true, payload, extras: engineExtras({ messages }) };
|
|
53291
53348
|
}
|
|
53349
|
+
async engineWarnings(projectPath, paths) {
|
|
53350
|
+
const ran = await this.operation("check_warnings", { paths: [...paths] }, projectPath);
|
|
53351
|
+
if (!ran.ok) {
|
|
53352
|
+
this.logDebug(`check_warnings did not answer: ${ran.message}`);
|
|
53353
|
+
return null;
|
|
53354
|
+
}
|
|
53355
|
+
return new Map((readArray(ran.payload, "scripts") ?? []).map((entry) => [
|
|
53356
|
+
String(asParams(entry)["path"]),
|
|
53357
|
+
(readArray(asParams(entry), "warnings") ?? []).map((warning) => ({
|
|
53358
|
+
line: Number(asParams(warning)["line"]),
|
|
53359
|
+
message: String(asParams(warning)["message"])
|
|
53360
|
+
}))
|
|
53361
|
+
]));
|
|
53362
|
+
}
|
|
53292
53363
|
async engineDiagnostics(projectPath, scripts, why) {
|
|
53293
53364
|
const named = scripts.map((script) => {
|
|
53294
53365
|
const contained = resolveWithinProject(projectPath, script);
|
|
@@ -53305,21 +53376,25 @@ ${error.text}`;
|
|
|
53305
53376
|
asParams(entry)
|
|
53306
53377
|
]));
|
|
53307
53378
|
const errorsOf = scripts.map((_, at) => readArray(failures.get(named[at] ?? "") ?? {}, "errors") ?? []);
|
|
53379
|
+
const warned = await this.engineWarnings(projectPath, named);
|
|
53308
53380
|
const uncachedPer = uncachedFor(projectPath, errorsOf.map((errors) => errors.map((entry) => String(asParams(entry)["message"]))));
|
|
53309
53381
|
return {
|
|
53310
53382
|
answers: scripts.map((scriptPath, at) => {
|
|
53311
53383
|
const failure = failures.get(named[at] ?? "");
|
|
53312
|
-
const
|
|
53384
|
+
const errors = (errorsOf[at] ?? []).map((entry) => {
|
|
53313
53385
|
const { line, message } = asParams(entry);
|
|
53314
53386
|
return { severity: "error", message, ...typeof line === "number" && line > 0 ? { line } : {} };
|
|
53315
53387
|
});
|
|
53388
|
+
const raised = new Set(errors.map(({ message, line }) => `${line ?? 0}:${String(message)}`));
|
|
53389
|
+
const warnings = warned === null ? [] : (warned.get(named[at] ?? "") ?? []).filter(({ line, message }) => !raised.has(`${line}:${message}`)).map(({ line, message }) => ({ severity: "warning", message: asWarning(message), line }));
|
|
53390
|
+
const diagnostics = [...errors, ...warnings].sort((a, b) => (a.line ?? 0) - (b.line ?? 0));
|
|
53316
53391
|
const dependencies = readArray(failure ?? {}, "dependencies_with_errors");
|
|
53317
53392
|
const uncached = uncachedPer[at] ?? [];
|
|
53318
53393
|
return {
|
|
53319
53394
|
scriptPath,
|
|
53320
|
-
clean:
|
|
53321
|
-
errors:
|
|
53322
|
-
warnings: null,
|
|
53395
|
+
clean: errors.length === 0,
|
|
53396
|
+
errors: errors.length,
|
|
53397
|
+
warnings: warned === null ? null : warnings.length,
|
|
53323
53398
|
diagnostics,
|
|
53324
53399
|
...dependencies === undefined ? {} : { dependenciesWithErrors: dependencies },
|
|
53325
53400
|
...uncached.length === 0 ? {} : {
|
|
@@ -57669,7 +57744,7 @@ async function runGodotServer() {
|
|
|
57669
57744
|
const server = new GodotServer;
|
|
57670
57745
|
await server.run();
|
|
57671
57746
|
}
|
|
57672
|
-
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, FAILED_BUDGET = 1e6, ANSWER_BUDGET = 4000000, __dirname2, PARENT_ASKED_AFTER_MS = 5000, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, ALONGSIDE_NOTE = "Another call on this game was running while this one was: calls sent together run together and are answered as each finishes, so this answer is not from after the other. Send a call meant to come after another once that one has answered.", OWN_DEBUGGER = " Its debugger is the keeper holding it rather than the editor, so the debug_* tools answer for it all the same: breakpoints stop it, and debug_control pause breaks into it wherever it is, which the editor offers no way to do.", PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, KEEPER_DEBUG_MS, EXIT_REPORTED_WITHIN_MS = 2000, SUCCESSOR_SOUGHT_WITHIN_MS = 20000, CLOSE_ASK_MS = 3000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, PROFILE_SEND_MS = 5000, MOST_GROUPS = 20, RUNTIME_LISTENING_LINE = "[gdharness] runtime listening on ", VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, OPS_ELSEWHERE, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", LOOPING_AFTER = 100, WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, DESTINATION_ARGUMENTS, OUTPUT_PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, PROPERTY_PLACES = "It is a property, so each place says whether it writes it (access write), which runs its set block, or reads it (access read), which runs its get block: withinCalls on a read says nothing of the set block, nor on a write of the get block unless the write is a compound one such as +=. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not listed.", CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, MOST_PRINTED_LINES = 50, MOST_UNDECLARED_ERRORS = 20, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
|
|
57747
|
+
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, FAILED_BUDGET = 1e6, ANSWER_BUDGET = 4000000, __dirname2, PARENT_ASKED_AFTER_MS = 5000, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, ALONGSIDE_NOTE = "Another call on this game was running while this one was: calls sent together run together and are answered as each finishes, so this answer is not from after the other. Send a call meant to come after another once that one has answered.", OWN_DEBUGGER = " Its debugger is the keeper holding it rather than the editor, so the debug_* tools answer for it all the same: breakpoints stop it, and debug_control pause breaks into it wherever it is, which the editor offers no way to do.", PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, KEEPER_DEBUG_MS, EXIT_REPORTED_WITHIN_MS = 2000, SUCCESSOR_SOUGHT_WITHIN_MS = 20000, CLOSE_ASK_MS = 3000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, PROFILE_SEND_MS = 5000, MOST_GROUPS = 20, RUNTIME_LISTENING_LINE = "[gdharness] runtime listening on ", VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, OPS_ELSEWHERE, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", LOOPING_AFTER = 100, WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, DESTINATION_ARGUMENTS, OUTPUT_PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, PROPERTY_PLACES = "It is a property, so each place says whether it writes it (access write), which runs its set block, or reads it (access read), which runs its get block: withinCalls on a read says nothing of the set block, nor on a write of the get block unless the write is a compound one such as +=. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not listed.", CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, MOST_PRINTED_LINES = 50, MOST_UNDECLARED_ERRORS = 20, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", RAISED_WARNING = " (Warning treated as error.)", PARSE_ERROR2 = "Parse Error: ", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
|
|
57673
57748
|
var init_server = __esm(() => {
|
|
57674
57749
|
init_dist2();
|
|
57675
57750
|
init_stdio();
|
|
@@ -59116,7 +59191,6 @@ ${existsSync23(path) ? readFileSync22(path, "latin1") : "missing"}`)
|
|
|
59116
59191
|
}
|
|
59117
59192
|
}
|
|
59118
59193
|
const times = readTimes(timesFile);
|
|
59119
|
-
const units = unitsOf(suites, times);
|
|
59120
59194
|
const memory = await memoryAvailable();
|
|
59121
59195
|
if (memory.unread !== undefined) {
|
|
59122
59196
|
console.log(`Windows did not say how much memory it can still commit (${memory.unread}), so engines are sized by the physical memory free.`);
|
|
@@ -59125,9 +59199,13 @@ ${existsSync23(path) ? readFileSync22(path, "latin1") : "missing"}`)
|
|
|
59125
59199
|
if (options.cores !== null && process14.platform === "darwin") {
|
|
59126
59200
|
console.log("macOS offers no way to hold a process to some processors, so --cores holds only the number of engines here.");
|
|
59127
59201
|
}
|
|
59202
|
+
const splitPast = splitPastFor(suites, times, room.engines);
|
|
59203
|
+
const units = unitsOf(suites, times, splitPast);
|
|
59204
|
+
const past = splitPast > SPLIT_PAST_SECONDS ? `an engine's share of this run (${String(Math.round(splitPast))} s)` : `${String(SPLIT_PAST_SECONDS)} s`;
|
|
59128
59205
|
const plans = options.order === null ? dealt(units, declared, room.engines) : [inOneEngine(ordered(units, options.order), declared)];
|
|
59129
59206
|
const functions = suites.reduce((sum, suite) => sum + suite.cases.length, 0);
|
|
59130
|
-
const
|
|
59207
|
+
const asked = unitsOf(suites, times, splitPastFor(suites, times, options.engines));
|
|
59208
|
+
const how = options.order === null ? `${windowedSaid(plans)}${roomSaid(plans.length, lanesOf(plans), lanesOf(dealt(asked, declared, options.engines)), room.held, options.engines)}` : options.order === "reversed" ? ", in reverse order of path" : `, shuffled by ${String(options.order)}`;
|
|
59131
59209
|
console.log(`${plural(functions, "test function")} in ${plural(suites.length, "suite")} over ${plural(plans.length, "engine")}${how}.`);
|
|
59132
59210
|
const windowedAnyway = headlessInAWindow(plans, units);
|
|
59133
59211
|
if (windowedAnyway.length > 0) {
|
|
@@ -59136,13 +59214,18 @@ ${existsSync23(path) ? readFileSync22(path, "latin1") : "missing"}`)
|
|
|
59136
59214
|
}
|
|
59137
59215
|
const split = [...new Set(units.map((unit) => unit.suite))].filter((path) => units.filter((unit) => unit.suite === path && unit.row === undefined).length > 1);
|
|
59138
59216
|
if (plans.length > 1 && split.length > 0) {
|
|
59139
|
-
console.log(`Dealt out case by case, since ${split.length === 1 ? "it" : "each"} took more than ${
|
|
59217
|
+
console.log(`Dealt out case by case, since ${split.length === 1 ? "it" : "each"} took more than ${past} last time: ${named(split)}.`);
|
|
59140
59218
|
}
|
|
59141
59219
|
const byRows = [
|
|
59142
59220
|
...new Set(units.flatMap((unit) => unit.row === undefined ? [] : [`${unit.suite} > ${unit.cases[0] ?? ""}`]))
|
|
59143
59221
|
];
|
|
59144
59222
|
if (plans.length > 1 && byRows.length > 0) {
|
|
59145
|
-
console.log(`Dealt out row by row, since ${byRows.length === 1 ? "it" : "each"} took more than ${
|
|
59223
|
+
console.log(`Dealt out row by row, since ${byRows.length === 1 ? "it" : "each"} took more than ${past} last time: ${named(byRows)}.`);
|
|
59224
|
+
}
|
|
59225
|
+
const keptWhole = units.flatMap((unit) => unit.cost > splitPast && !unit.windowed && suites.some((s) => s.path === unit.suite && s.together) ? [unit.suite] : []);
|
|
59226
|
+
if (options.order === null && room.engines > 1 && keptWhole.length > 0) {
|
|
59227
|
+
const one = keptWhole.length === 1;
|
|
59228
|
+
console.log(`Kept whole in one engine, since ${one ? "it declares" : "each declares"} GDHARNESS_TOGETHER: ${named(keptWhole)}.`);
|
|
59146
59229
|
}
|
|
59147
59230
|
const lanes = [...new Set(plans.map((plan) => plan.lane))].map((lane) => plans.flatMap((plan, index) => plan.lane === lane ? [{ plan, index }] : []));
|
|
59148
59231
|
const taken = [];
|
|
@@ -51,6 +51,7 @@ const BEGUN: String = "[gdharness] begun"
|
|
|
51
51
|
## scene or a resource, loads the scripts of the file it opens.
|
|
52
52
|
const AFTER_THE_AUTOLOADS: Array[String] = [
|
|
53
53
|
"check_scripts",
|
|
54
|
+
"check_warnings",
|
|
54
55
|
"create_script",
|
|
55
56
|
# An exporter made in _init, before an editor's main loop has started, crashed the engine.
|
|
56
57
|
"export_options",
|
|
@@ -99,6 +100,8 @@ func _init() -> void:
|
|
|
99
100
|
var params: Dictionary = read
|
|
100
101
|
|
|
101
102
|
_log.info("Executing operation: " + operation)
|
|
103
|
+
if operation == "check_warnings":
|
|
104
|
+
GdhScriptCheck.raise_warnings()
|
|
102
105
|
|
|
103
106
|
var answer_path: String = args[answer_index]
|
|
104
107
|
if operation in AFTER_THE_AUTOLOADS:
|
|
@@ -279,6 +282,8 @@ func _run(operation: String, params: Dictionary) -> Dictionary:
|
|
|
279
282
|
payload = GdhRenameChecks.new(_log).names_taken(params)
|
|
280
283
|
"check_scripts":
|
|
281
284
|
payload = GdhScriptCheck.new(_log).check_scripts(params)
|
|
285
|
+
"check_warnings":
|
|
286
|
+
payload = GdhScriptCheck.new(_log).check_warnings(params)
|
|
282
287
|
|
|
283
288
|
# Plugins and input
|
|
284
289
|
"list_plugins":
|
|
@@ -19,6 +19,15 @@ const GdhLog = preload("logger.gd")
|
|
|
19
19
|
## Where the engine raises a script's compile errors from.
|
|
20
20
|
const RELOAD: String = "GDScript::reload"
|
|
21
21
|
|
|
22
|
+
## What the engine puts on the end of a warning it raised as an error. Kept in step with
|
|
23
|
+
## `RAISED_WARNING` in src/server.ts.
|
|
24
|
+
const RAISED: String = " (Warning treated as error.)"
|
|
25
|
+
|
|
26
|
+
## Where the project's warning levels are, each 0 to ignore, 1 to warn and 2 to raise as an error.
|
|
27
|
+
const WARNING_LEVELS: String = "debug/gdscript/warnings/"
|
|
28
|
+
const WARN: int = 1
|
|
29
|
+
const ERROR_LEVEL: int = 2
|
|
30
|
+
|
|
22
31
|
var _log: GdhLog
|
|
23
32
|
|
|
24
33
|
|
|
@@ -112,3 +121,52 @@ func check_scripts(params: Dictionary) -> Dictionary:
|
|
|
112
121
|
break
|
|
113
122
|
OS.remove_logger(caught)
|
|
114
123
|
return {"checked": checked, "failed": failed, "scripts": scripts}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
## Raises every warning the project sets to warn so that the engine reports it as an error, which is
|
|
127
|
+
## the one way it reports a warning outside an editor or a debugger. Not saved. The analyser reads
|
|
128
|
+
## the levels again only once the settings change has been flushed, at the next frame, so this is
|
|
129
|
+
## called in `_init` and the scripts are loaded after it; the project's autoloads, compiled before
|
|
130
|
+
## that frame, compile under the project's own levels. A warning set to ignore stays ignored, and
|
|
131
|
+
## @warning_ignore and the directory rules hold as they do in the editor, since the analyser applies
|
|
132
|
+
## them itself.
|
|
133
|
+
static func raise_warnings() -> void:
|
|
134
|
+
for property: Dictionary in ProjectSettings.get_property_list():
|
|
135
|
+
var setting: String = str(property.get("name", ""))
|
|
136
|
+
if (
|
|
137
|
+
not setting.begins_with(WARNING_LEVELS)
|
|
138
|
+
or GdhRead.as_int(property.get("type", TYPE_NIL)) != TYPE_INT
|
|
139
|
+
):
|
|
140
|
+
continue
|
|
141
|
+
if GdhRead.as_int(ProjectSettings.get_setting(setting)) == WARN:
|
|
142
|
+
ProjectSettings.set_setting(setting, ERROR_LEVEL)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
## The warnings of each script in [code]paths[/code], under the levels [method raise_warnings] left:
|
|
146
|
+
## [code]scripts[/code] holds each that has any, with its warnings' lines and the engine's messages
|
|
147
|
+
## as raised. The analyser runs before anything is compiled, so a script whose dependency failed on a
|
|
148
|
+
## warning still reports its own, and loading on past a failure loses nothing; only what is raised at
|
|
149
|
+
## the script's own path is its, since a dependency is read on its own entry as well.
|
|
150
|
+
func check_warnings(params: Dictionary) -> Dictionary:
|
|
151
|
+
var asked: Array = params.get("paths", [])
|
|
152
|
+
var caught: GdhCaught = GdhCaught.new()
|
|
153
|
+
OS.add_logger(caught)
|
|
154
|
+
var checked: Array[String] = []
|
|
155
|
+
var scripts: Array[Dictionary] = []
|
|
156
|
+
for entry: Variant in asked:
|
|
157
|
+
var path: String = str(entry)
|
|
158
|
+
checked.append(path)
|
|
159
|
+
caught.raised.clear()
|
|
160
|
+
var _loaded: Resource = ResourceLoader.load(path, "", ResourceLoader.CACHE_MODE_IGNORE)
|
|
161
|
+
var warnings: Array[Dictionary] = []
|
|
162
|
+
for error: Dictionary in caught.raised:
|
|
163
|
+
var message: String = str(error["message"])
|
|
164
|
+
if str(error["path"]) != path or not message.ends_with(RAISED):
|
|
165
|
+
continue
|
|
166
|
+
var found: Dictionary = {"line": GdhRead.as_int(error["line"]), "message": message}
|
|
167
|
+
if not found in warnings:
|
|
168
|
+
warnings.append(found)
|
|
169
|
+
if not warnings.is_empty():
|
|
170
|
+
scripts.append({"path": path, "warnings": warnings})
|
|
171
|
+
OS.remove_logger(caught)
|
|
172
|
+
return {"checked": checked, "scripts": scripts}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.39",
|
|
4
4
|
"mcpName": "io.github.Aureliolo/gdharness",
|
|
5
5
|
"description": "A harness for driving a Godot 4 project from an agent: editor addons, a runtime bridge into the running game, an MCP server in front of them, and a CLI that installs and diagnoses the Godot side.",
|
|
6
6
|
"type": "module",
|