gdharness 1.3.40 → 1.3.42
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 +343 -142
- package/build/godot/addons/gdharness_editor/animation_reads.gd +31 -4
- package/build/godot/addons/gdharness_editor/file_reads.gd +36 -1
- package/build/godot/addons/gdharness_editor/file_walk.gd +33 -1
- package/build/godot/addons/gdharness_editor/headless_check.gd +29 -0
- package/build/godot/addons/gdharness_editor/property_values.gd +6 -9
- package/build/godot/addons/gdharness_editor/scene_file.gd +27 -0
- package/build/godot/addons/gdharness_editor/tools/animation_states.gd +5 -2
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +6 -2
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +15 -0
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +7 -2
- package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd +59 -0
- package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +75 -11
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +77 -22
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +4 -1
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +4 -3
- package/build/godot/addons/gdharness_runtime/runtime_touch.gd +211 -0
- package/build/godot/addons/gdharness_runtime/runtime_touch.gd.uid +1 -0
- package/build/godot/addons/gdharness_runtime/runtime_typing.gd +15 -8
- package/build/godot/operations/animation_reads.gd +31 -4
- package/build/godot/operations/dependencies.gd +5 -2
- package/build/godot/operations/file_reads.gd +36 -1
- package/build/godot/operations/file_walk.gd +33 -1
- package/build/godot/operations/gdscript_analysis.gd +19 -1
- package/build/godot/operations/gdscript_authoring.gd +65 -4
- package/build/godot/operations/import_pipeline.gd +1 -1
- package/build/godot/operations/input_actions.gd +1 -0
- package/build/godot/operations/plugins.gd +39 -1
- package/build/godot/operations/project_config.gd +104 -1
- package/build/godot/operations/property_values.gd +6 -9
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -10478,7 +10478,12 @@ function ignoredLeftOut(report, ignored, declared, rowsIgnored = new Set) {
|
|
|
10478
10478
|
function caseOf(suite, element) {
|
|
10479
10479
|
const of = (kind) => element.children.filter((child) => child.name === kind).map((child) => {
|
|
10480
10480
|
const detail = child.text.trim();
|
|
10481
|
-
|
|
10481
|
+
const message = child.attributes["message"] ?? null;
|
|
10482
|
+
return {
|
|
10483
|
+
kind,
|
|
10484
|
+
message: kind === "error" ? raisedAt(message, detail) : message,
|
|
10485
|
+
detail: detail === "" ? null : detail
|
|
10486
|
+
};
|
|
10482
10487
|
});
|
|
10483
10488
|
const reports = [...of("failure"), ...of("error"), ...of("skipped")];
|
|
10484
10489
|
const status = reports.some((report) => report.kind === "error") ? "error" : reports.some((report) => report.kind === "failure") ? "failed" : reports.length > 0 ? "skipped" : "passed";
|
|
@@ -10492,6 +10497,17 @@ function caseOf(suite, element) {
|
|
|
10492
10497
|
reports
|
|
10493
10498
|
};
|
|
10494
10499
|
}
|
|
10500
|
+
function raisedAt(message, detail) {
|
|
10501
|
+
if (message === null) {
|
|
10502
|
+
return null;
|
|
10503
|
+
}
|
|
10504
|
+
const named = /^ERROR: \S+:\d+(?=\s|$)/.exec(message);
|
|
10505
|
+
const raised = /^\s*at '[^']*' in (\S+):(\d+)\s*$/m.exec(detail);
|
|
10506
|
+
if (named === null || raised === null) {
|
|
10507
|
+
return message;
|
|
10508
|
+
}
|
|
10509
|
+
return `ERROR: ${raised[1] ?? ""}:${raised[2] ?? ""}${message.slice(named[0].length)}`;
|
|
10510
|
+
}
|
|
10495
10511
|
function failedAnAssertion(entry) {
|
|
10496
10512
|
return entry.reports.some((report) => report.kind === "failure");
|
|
10497
10513
|
}
|
|
@@ -12910,7 +12926,7 @@ var init_doc_cache = __esm(() => {
|
|
|
12910
12926
|
|
|
12911
12927
|
// src/project-scan.ts
|
|
12912
12928
|
import { existsSync as existsSync9, readdirSync as readdirSync6, readFileSync as readFileSync8, statSync as statSync9 } from "node:fs";
|
|
12913
|
-
import { basename as basename5, extname, join as join11 } from "node:path";
|
|
12929
|
+
import { basename as basename5, extname, join as join11, relative as relative4, sep as sep3 } from "node:path";
|
|
12914
12930
|
function skipped(name) {
|
|
12915
12931
|
return name.startsWith(".") || SKIPPED.has(name);
|
|
12916
12932
|
}
|
|
@@ -13070,6 +13086,20 @@ function pathsInUidCache(projectPath) {
|
|
|
13070
13086
|
}
|
|
13071
13087
|
return new Set(paths.values());
|
|
13072
13088
|
}
|
|
13089
|
+
function steppedOverAround(projectPath, place) {
|
|
13090
|
+
let at = projectPath;
|
|
13091
|
+
for (const step of relative4(projectPath, place).split(sep3).filter((part) => part !== "")) {
|
|
13092
|
+
at = join11(at, step);
|
|
13093
|
+
if (!statSync9(at).isDirectory()) {
|
|
13094
|
+
break;
|
|
13095
|
+
}
|
|
13096
|
+
const why = skipped(step) ? step.startsWith(".") ? "its name starts with a dot" : `it is ${step}` : gdignored(at) ? "it holds a .gdignore" : installedByUs(at) ? "gdharness installed it" : null;
|
|
13097
|
+
if (why !== null) {
|
|
13098
|
+
return { folder: `res://${relative4(projectPath, at).split(sep3).join("/")}/`, why };
|
|
13099
|
+
}
|
|
13100
|
+
}
|
|
13101
|
+
return null;
|
|
13102
|
+
}
|
|
13073
13103
|
function isBinary(bytes) {
|
|
13074
13104
|
return bytes.subarray(0, 8000).includes(0);
|
|
13075
13105
|
}
|
|
@@ -13195,8 +13225,8 @@ function searchProject(projectPath, options) {
|
|
|
13195
13225
|
result.summary.files_with_matches += 1;
|
|
13196
13226
|
}
|
|
13197
13227
|
};
|
|
13198
|
-
const visit = (directory) => {
|
|
13199
|
-
if (steppedOver(directory)) {
|
|
13228
|
+
const visit = (directory, named = false) => {
|
|
13229
|
+
if (!named && steppedOver(directory)) {
|
|
13200
13230
|
if (gdignored(directory)) {
|
|
13201
13231
|
notSearched.push(`res://${directory.slice(projectPath.length + 1).replace(/\\/g, "/")}/`);
|
|
13202
13232
|
}
|
|
@@ -13219,20 +13249,17 @@ function searchProject(projectPath, options) {
|
|
|
13219
13249
|
};
|
|
13220
13250
|
const within = options.within ?? projectPath;
|
|
13221
13251
|
if (statSync9(within).isDirectory()) {
|
|
13222
|
-
visit(within);
|
|
13252
|
+
visit(within, true);
|
|
13223
13253
|
} else {
|
|
13224
13254
|
searchFile(within, basename5(within));
|
|
13225
13255
|
}
|
|
13226
13256
|
const answered = notSearched.length === 0 ? result : { ...result, not_searched: notSearched };
|
|
13227
|
-
|
|
13228
|
-
|
|
13229
|
-
|
|
13230
|
-
|
|
13231
|
-
|
|
13232
|
-
return {
|
|
13233
|
-
...answered,
|
|
13234
|
-
note: met.size === 0 ? `No file was searched: there is no file there to search.` : `No file was searched: none there has the extension ${asked}. The extensions there are ${there}.`
|
|
13235
|
-
};
|
|
13257
|
+
const around = options.within === undefined ? null : steppedOverAround(projectPath, within);
|
|
13258
|
+
const notes = [
|
|
13259
|
+
around === null ? undefined : `${around.folder} is searched because path names it; a search of the whole project steps over it, since ${around.why}.`,
|
|
13260
|
+
extensions === null || result.summary.files_searched > 0 ? undefined : met.size === 0 ? "No file was searched: there is no file there to search." : `No file was searched: none there has the extension ${[...extensions].join(", ")}. The extensions there are ${[...met].sort().join(", ")}.`
|
|
13261
|
+
].filter((note) => note !== undefined);
|
|
13262
|
+
return notes.length === 0 ? answered : { ...answered, note: notes.join(" ") };
|
|
13236
13263
|
}
|
|
13237
13264
|
var SKIPPED, VERSION_MARKER = ".gdharness-version", SCENE_EXTENSIONS, SIDECAR_EXTENSIONS, ASSET_EXTENSIONS, CARRIES_A_UID, BINARY_EXTENSIONS;
|
|
13238
13265
|
var init_project_scan = __esm(() => {
|
|
@@ -16423,7 +16450,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16423
16450
|
returnType: { type: "string", description: "Functions: the return type." },
|
|
16424
16451
|
body: {
|
|
16425
16452
|
type: "string",
|
|
16426
|
-
description: "Functions: the body, its lines separated by \\n. Indent shared by every line is taken off
|
|
16453
|
+
description: "Functions: the body, its lines separated by \\n. Indent shared by every line is taken off, so it can be written indented or not, and each level is written in the script's own indentation, tabs or spaces."
|
|
16427
16454
|
},
|
|
16428
16455
|
varType: {
|
|
16429
16456
|
type: "string",
|
|
@@ -16433,8 +16460,14 @@ var init_tool_definitions = __esm(() => {
|
|
|
16433
16460
|
type: "string",
|
|
16434
16461
|
description: "Variables: the initial value. Constants: the value, which a constant needs. As written in GDScript."
|
|
16435
16462
|
},
|
|
16436
|
-
isExport: {
|
|
16437
|
-
|
|
16463
|
+
isExport: {
|
|
16464
|
+
type: "boolean",
|
|
16465
|
+
description: "Variables: add @export. Default true when exportHint is given."
|
|
16466
|
+
},
|
|
16467
|
+
exportHint: {
|
|
16468
|
+
type: "string",
|
|
16469
|
+
description: 'Variables: the export hint, such as "range(0, 100)", written as @export_range(0, 100). Refused beside isExport false.'
|
|
16470
|
+
},
|
|
16438
16471
|
isOnready: { type: "boolean", description: "Variables: add @onready." },
|
|
16439
16472
|
position: {
|
|
16440
16473
|
type: "string",
|
|
@@ -16537,7 +16570,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16537
16570
|
{
|
|
16538
16571
|
name: "project_settings",
|
|
16539
16572
|
title: "Project settings",
|
|
16540
|
-
description: "Reads or writes project.godot (settings, autoloads, the main scene, input actions and plugins) and the audio bus layout, default_bus_layout.tres unless audio/buses/default_bus_layout names another. project_info include lists the autoloads, input actions and audio buses as the project holds them. A write with an editor open on the project is handed to the editor, which takes the settings the write changed or names from the file (named under editorAdopted), including a setting the file already held, since the editor can hold another value after the file was put back, or reloads the bus layout, so it answers the new values and does not save the old ones back over them. editorNote is an editor that could not take the write, or one on another release's addon that was not asked, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start. set answers old_value and new_value, with was_new true when project.godot did not set the setting before, and was_default true when what it held was the engine's own default, which old_value then is.
|
|
16573
|
+
description: "Reads or writes project.godot (settings, autoloads, the main scene, input actions and plugins) and the audio bus layout, default_bus_layout.tres unless audio/buses/default_bus_layout names another. project_info include lists the autoloads, input actions and audio buses as the project holds them. A write with an editor open on the project is handed to the editor, which takes the settings the write changed or names from the file (named under editorAdopted), including a setting the file already held, since the editor can hold another value after the file was put back, or reloads the bus layout, so it answers the new values and does not save the old ones back over them. editorNote is an editor that could not take the write, or one on another release's addon that was not asked, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start, and enable_plugin notes a script it would not load. set answers old_value and new_value, with was_new true when project.godot did not set the setting before, and was_default true when what it held was the engine's own default, which old_value then is, and overrides with overridesNote when a feature's override still replaces it. add_autoload refuses a name the editor's Autoload tab refuses: not an identifier, a keyword, a built-in type or an engine class. add_input_action on an existing action, a ui_ one included, replaces its events, and its deadzone when one is given, answering was_new false with old_events and old_deadzone. A set of the value the setting already holds, in the same type, writes nothing and answers unchanged true and saved false. unset takes a setting's line out of project.godot and answers removed, old_value and default_value, the value that holds now (null for a setting the engine does not declare; for an override, name.feature, holds is the base setting's value its builds read now); on one the file does not set it answers removed false and writes nothing, its note naming the nearest settings when the engine does not declare it either. Every write changes only the lines of the settings it names and keeps every other line as the file had it, in its place: the engine's own save would sort a dictionary's keys and write what a project_settings_override file laid over the project. get answers exists, true for any setting the engine knows, at_default, true when the value is the engine's own default and so has no line in project.godot, and overrides, the value each feature's override holds. An override answers override_of, its base setting, and one project.godot does not set answers exists false with holds, the base setting's value. Each setting under prefix carries at_default too, so a family read separates what the project sets from the engine's defaults; it is absent for a setting with no engine default, such as an input action the project added.",
|
|
16541
16574
|
parameters: {
|
|
16542
16575
|
projectPath: PROJECT_PATH,
|
|
16543
16576
|
setting: {
|
|
@@ -16697,7 +16730,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16697
16730
|
name: "project_search",
|
|
16698
16731
|
title: "Search the project",
|
|
16699
16732
|
effect: "read",
|
|
16700
|
-
description: "Searches text or a regular expression across project files and returns file paths with line numbers, one entry per line that holds the query, however often it holds it; total_matches counts those lines. What the engine steps over is not searched: directories spelled with a dot, node_modules, and any directory holding a .gdignore, which not_searched names when the search passed one. A vendored engine or an export directory is therefore absent from the results rather than matching the code you are looking for in a copy nobody runs. The addons gdharness installed are not searched either, since nobody on the project wrote them.",
|
|
16733
|
+
description: "Searches text or a regular expression across project files and returns file paths with line numbers, one entry per line that holds the query, however often it holds it; total_matches counts those lines. What the engine steps over is not searched: directories spelled with a dot, node_modules, and any directory holding a .gdignore, which not_searched names when the search passed one. A vendored engine or an export directory is therefore absent from the results rather than matching the code you are looking for in a copy nobody runs. The addons gdharness installed are not searched either, since nobody on the project wrote them. A path naming one of those folders, or a place inside one, is searched as asked, and note says the whole-project search steps over it. Each line is searched on its own, so a query holding a line break is refused.",
|
|
16701
16734
|
parameters: {
|
|
16702
16735
|
projectPath: PROJECT_PATH,
|
|
16703
16736
|
query: { type: "string", description: "The text or pattern to find." },
|
|
@@ -16712,7 +16745,11 @@ var init_tool_definitions = __esm(() => {
|
|
|
16712
16745
|
},
|
|
16713
16746
|
regex: { type: "boolean", description: "Read the query as a regular expression. Default false." },
|
|
16714
16747
|
caseSensitive: { type: "boolean", description: "Default false." },
|
|
16715
|
-
maxResults: {
|
|
16748
|
+
maxResults: {
|
|
16749
|
+
type: "integer",
|
|
16750
|
+
minimum: 1,
|
|
16751
|
+
description: "How many matching lines to answer. Default 100."
|
|
16752
|
+
}
|
|
16716
16753
|
},
|
|
16717
16754
|
requires: ["projectPath", "query"]
|
|
16718
16755
|
},
|
|
@@ -16729,7 +16766,11 @@ var init_tool_definitions = __esm(() => {
|
|
|
16729
16766
|
enum: ["forward", "reverse"],
|
|
16730
16767
|
description: "forward: what this resource loads, with each reference closing a cycle marked circular and the cycle under circular_references, a script's dependencies including every script whose class_name it uses, marked by_class with the name, and the script of every autoload it names, marked by_autoload with the autoload's name. An ext_resource whose uid names another file than its path is followed to that file, as the engine loads it, with the path written under stale_path; one whose uid names no file the project has is followed by its path, as the engine does after warning, with the uid under unknown_uid; and a load or preload of a uid the project does not know is listed under that uid as missing. A shader's #include is followed as the preprocessor reads it, relative to the shader. A file the depth stops at carries beyond_depth, how many files it names that were not walked. summary.total_resources is the file asked about, so 1; total_dependencies counts each file reached, directly or through another, once however many places load it; total_references counts the entries in the tree, and missing names every file referenced that does not exist, with the files referring to it. reverse: every file that refers to it and how, for a file that is gone as well while anything still refers to it, answered with exists: false (a missing file nothing refers to is refused, naming the nearest files), a scene instancing it, a script extending, preloading or loading it, and for a script with a class_name every use of that name, and for an autoload's script every use of the autoload's name, with autoload naming it. Each reference carries a kind: ext_resource, extends, preload, load, include or path for a reference by path, with stale_path when an ext_resource reaches it by its uid under another path, and unknown_uid when one names it by path beside a uid naming no file; setting, plugin, export_preset and import for one in project.godot, a plugin.cfg, export_presets.cfg or an import sidecar; class_name for a use of the declared name, the declaration itself being the script asked about; autoload for code naming the autoload. Three kinds are not uses: doc is a name inside a ## documentation comment, which renaming would break in the generated docs; comment is one in ordinary prose, which it would only make stale; and stale_path is an ext_resource naming it by a path the engine does not load, since its uid leads to the file under loads. summary.in_code is the count with those left out, which is the number to read when the question is whether anything still uses this. Default forward."
|
|
16731
16768
|
},
|
|
16732
|
-
depth: {
|
|
16769
|
+
depth: {
|
|
16770
|
+
type: "integer",
|
|
16771
|
+
minimum: 1,
|
|
16772
|
+
description: "forward: how many levels to follow, 1 being what the file loads itself. Default unlimited."
|
|
16773
|
+
},
|
|
16733
16774
|
includeBuiltin: {
|
|
16734
16775
|
type: "boolean",
|
|
16735
16776
|
description: "forward: include the engine's own res://. resources. Default false."
|
|
@@ -16899,14 +16940,14 @@ var init_tool_definitions = __esm(() => {
|
|
|
16899
16940
|
name: "project_test",
|
|
16900
16941
|
title: "Run tests",
|
|
16901
16942
|
effect: "additive",
|
|
16902
|
-
description: "Runs the project's gdUnit4 tests and answers with every case that did not pass: where it is, and what the assertion said. Each entry under failed carries its first report as message and detail, and, when it has more than one, its reports under reports in the order the case met them: gdUnit4 goes on past a failed assertion, and a case can fail several and then stop on a runtime error, which makes its status error. Identical reports fold into one with times, at most twenty different ones are kept (reportsLeftOut counts the rest), and past about a megabyte the largest cases keep only their first message (failedWithoutDetail). failures counts the cases that failed an assertion and errors the cases that hit an error, so such a case counts in both. Suites where everything passed are counted, not listed, and an engine message keeps the frames above gdUnit4, not the twenty inside it; identical engine messages come back once under engineEntries, with times saying how often they were printed, and with script, line and function for the project script one points at, as editor_output's entries have. The class list is rebuilt first, so a suite written a moment ago is found, and the rebuild's answer is under classes. What changed on disk since it was imported is then imported before any suite runs, and named under importedFirst. On Windows and Linux the run gets a user:// of its own, away from a player's saves; on macOS the engine reads user:// off HOME, so it writes where the player keeps theirs, and savesNote says so. The run is headless unless a suite in it needs a window as gdharness test reads it, and then has one, on a desktop of its own on Windows. A run that found nothing to run is never called a pass, and names the path it looked in. A script that does not load stops gdUnit4 before any suite runs: its errors come back in full under scriptErrors, and the scripts that failed only because one they depend on did are listed by path under failedThroughDependency. When they failed on a class whose own script has a parser error, which gdUnit4 never names, that script is checked by the engine and its error comes first under scriptErrors, with the class it declares under declares, and the errors a script has only because that class did not resolve, which the engine reads as Variant, are kept apart under followFromUnresolved. An autoload that does not compile stops the run at once, its errors listed the same way. A client sending a progress token is told as each suite finishes, and at least every twenty seconds. A run that ends without a report, at its timeout or otherwise, names the suites it finished under suitesFinished, the failing ones with their failed cases under suitesFailing, and the one it was in under suiteRunning. When a client cuts a run off, that is kept in the project and the next project_test there carries it under previousRunCut. A run where nothing failed but orphan nodes were left comes back under warnings, with the count per suite: gdUnit4 decides its verdict on those and keeps them out of its report, so they are read off what it printed. A run whose every case passed is still not called passed when it printed a script error that no case failed on: gdUnit4 fails a case on the script errors its logger sees, and it cannot see a stack overflow, so the cases pass on whatever the faulted calls returned. Those errors come back under uncaughtScriptErrors, and under engineEntries by index with detailUnder rather than with their backtrace a second time; a backtrace that repeats, as a recursion's does, has each repeated frame or cycle of frames written once with the frames it covers and how many times. One raised inside assert_error(...).is_runtime_error is the test's own and is not listed there, and none are when the project turns off gdUnit4's report/godot/script_error setting; a suite declares them all the same. Needs gdUnit4 under addons/gdUnit4.",
|
|
16943
|
+
description: "Runs the project's gdUnit4 tests and answers with every case that did not pass: where it is, and what the assertion said. Each entry under failed carries its first report as message and detail, and, when it has more than one, its reports under reports in the order the case met them: gdUnit4 goes on past a failed assertion, and a case can fail several and then stop on a runtime error, which makes its status error. Identical reports fold into one with times, at most twenty different ones are kept (reportsLeftOut counts the rest), and past about a megabyte the largest cases keep only their first message (failedWithoutDetail). failures counts the cases that failed an assertion and errors the cases that hit an error, so such a case counts in both. Suites where everything passed are counted, not listed, and an engine message keeps the frames above gdUnit4, not the twenty inside it; identical engine messages come back once under engineEntries, with times saying how often they were printed, and with script, line and function for the project script one points at, as editor_output's entries have. The class list is rebuilt first, so a suite written a moment ago is found, and the rebuild's answer is under classes. What changed on disk since it was imported is then imported before any suite runs, and named under importedFirst. A suite it ran with no .uid beside it is named under uidsMissing, with uidsNote. On Windows and Linux the run gets a user:// of its own, away from a player's saves; on macOS the engine reads user:// off HOME, so it writes where the player keeps theirs, and savesNote says so. The run is headless unless a suite in it needs a window as gdharness test reads it, and then has one, on a desktop of its own on Windows. A run that found nothing to run is never called a pass, and names the path it looked in. A script that does not load stops gdUnit4 before any suite runs: its errors come back in full under scriptErrors, and the scripts that failed only because one they depend on did are listed by path under failedThroughDependency. When they failed on a class whose own script has a parser error, which gdUnit4 never names, that script is checked by the engine and its error comes first under scriptErrors, with the class it declares under declares, and the errors a script has only because that class did not resolve, which the engine reads as Variant, are kept apart under followFromUnresolved. An autoload that does not compile stops the run at once, its errors listed the same way. A client sending a progress token is told as each suite finishes, and at least every twenty seconds. A run that ends without a report, at its timeout or otherwise, names the suites it finished under suitesFinished, the failing ones with their failed cases under suitesFailing, and the one it was in under suiteRunning. When a client cuts a run off, that is kept in the project and the next project_test there carries it under previousRunCut. A run where nothing failed but orphan nodes were left comes back under warnings, with the count per suite: gdUnit4 decides its verdict on those and keeps them out of its report, so they are read off what it printed. A run whose every case passed is still not called passed when it printed a script error that no case failed on: gdUnit4 fails a case on the script errors its logger sees, and it cannot see a stack overflow, so the cases pass on whatever the faulted calls returned. Those errors come back under uncaughtScriptErrors, and under engineEntries by index with detailUnder rather than with their backtrace a second time; a backtrace that repeats, as a recursion's does, has each repeated frame or cycle of frames written once with the frames it covers and how many times. One raised inside assert_error(...).is_runtime_error is the test's own and is not listed there, and none are when the project turns off gdUnit4's report/godot/script_error setting; a suite declares them all the same. Needs gdUnit4 under addons/gdUnit4.",
|
|
16903
16944
|
answer: "passed is the verdict to read. exitCode is the engine's own exit, gdUnit4's code for what it saw (100 a case failed, 101 only warnings, 105 script errors it detected), and is 0 beside passed false when the run is failed for something gdUnit4 does not judge, such as an engine error no suite declared. time is gdUnit4's own sum of the seconds its cases took, from its report. tookMs is the whole call in milliseconds, from the class rebuild to the engine's exit, which is what a caller waits for, and tookMsBy splits it into the class rebuild (classes), the import after it (import) and the engine running the suites (engine), each with any wait for an engine slot or the editor's scan in it. The run is judged as gdharness test judges it: undeclaredErrors names each engine error that no suite declared in its GDHARNESS_EXPECTED_ERRORS constant, and each engine warning a suite printed and did not declare in its GDHARNESS_EXPECTED_WARNINGS, marked warning true, with the suite and the case that printed it, and declaredNotPrinted each declared error or warning its case did not print. gdUnit4 does not fail a case on push_error or push_warning, so a case that printed one undeclared is in neither failed nor errors: it is named here, and the run is failed for it. A declared text must be the whole line after ERROR: or WARNING: and its space, so a longer one its case printed holding the text is named under printedInstead. Either fails the run, and each suite holding one is listed with its count. leakedAtExit holds the engine's lines saying it exited still holding objects, resources, memory pages or a server's RIDs, which fail the run too, and the note names any script of the project Godot itself never frees and, on a run with engineArgs [\"--verbose\"], what the engine held. engineErrors counts every engine error the run printed but those a suite declared. orphans totals the orphan nodes gdUnit4 counted, and each suite that left some has its count, and a warning saying how to free them, under warnings, with left naming each case that left some (before or after for one of the suite's hooks, or the one value \"before or after\" where no case began and the two cannot be told apart) and the nodes, by class, name and script. suites lists only the suites with something to say (a case that failed, erred or was skipped, a hook that failed, orphans, or an error undeclared or declared and not printed), and suitesPassed counts the rest. A before() or after() that failed comes back under hookFailures with its suite, line and whole message; failures and errors count cases and leave it out, so the verdict names it as failed hooks, and the cases after a failed before() still run and are counted. Under classes, the classes each addon declares are counted under addedInAddons and changedInAddons rather than named, and a class the rebuild puts back that the cache had after the last rebuild is named under classes.lostSinceLastRebuild, which is the editor writing its shorter list over the file on every save until restarted. A suite needs a window when it declares const GDHARNESS_NEEDS_WINDOW: bool = true, or calls scene_runner and declares nothing; a run holding one has a window, at the size project.godot gives its viewport and with no sound, and windowed is true and windowNote names those suites and any saying it runs headless that shared the window. On a machine with no display such a run is headless, windowed is false, and windowNote says so. " + IMPORTED_FIRST,
|
|
16904
16945
|
parameters: {
|
|
16905
16946
|
projectPath: PROJECT_PATH,
|
|
16906
16947
|
path: {
|
|
16907
16948
|
type: ["string", "array"],
|
|
16908
16949
|
items: { type: "string" },
|
|
16909
|
-
description:
|
|
16950
|
+
description: 'A test directory or suite file inside the project, or a list of them, all run on one engine: a named set of suites boots once rather than once per suite. An entry given twice, or a suite inside a directory also listed, runs once. An entry "suite.gd:test_name" runs that case alone, as gdharness test reads it and as tests runs it, so it cannot share the list with a whole suite or a directory. An entry that is not there is left out and named under missing, the rest run, and such a run is never called passed; when no entry is there the run is refused before the engine starts, naming each one. A script named that declares no test_ function at the top level is refused the same way, and so is a directory holding no suite. A script that declares test_ functions and does not extend GdUnitTestSuite, which gdUnit4 never runs and never mentions, is refused when named, and under a directory run whole it is named under notSuites and the run is not called passed. Default tests and test, those the project has, which is where gdharness test looks.'
|
|
16910
16951
|
},
|
|
16911
16952
|
ignore: {
|
|
16912
16953
|
type: "array",
|
|
@@ -16949,7 +16990,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16949
16990
|
rootNodeType: {
|
|
16950
16991
|
type: "string",
|
|
16951
16992
|
ops: ["create"],
|
|
16952
|
-
description: "create: the root node class, an engine class or a class_name a project script declares, which the root is made as with that script attached. Default Node."
|
|
16993
|
+
description: "create: the root node class, an engine class or a class_name a project script declares, which the root is made as with that script attached. Default Node. The answer names the root as scene_node get does: rootNodeType its engine class, with scriptClass for a class_name."
|
|
16953
16994
|
},
|
|
16954
16995
|
rootNodeName: {
|
|
16955
16996
|
type: "string",
|
|
@@ -16985,8 +17026,9 @@ var init_tool_definitions = __esm(() => {
|
|
|
16985
17026
|
projectPath: PROJECT_PATH,
|
|
16986
17027
|
scenePath: SCENE_PATH,
|
|
16987
17028
|
depth: {
|
|
16988
|
-
type: "
|
|
16989
|
-
|
|
17029
|
+
type: "integer",
|
|
17030
|
+
minimum: 0,
|
|
17031
|
+
description: "How many levels to descend, 0 being the root alone. Default: all. A node at the last level that has children of its own lists none and says how many under childrenNotListed."
|
|
16990
17032
|
},
|
|
16991
17033
|
includeProperties: {
|
|
16992
17034
|
type: "boolean",
|
|
@@ -16994,7 +17036,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16994
17036
|
},
|
|
16995
17037
|
includeDefaults: {
|
|
16996
17038
|
type: "boolean",
|
|
16997
|
-
description: "
|
|
17039
|
+
description: "List every stored property, defaults included, which turns includeProperties on when it is left out: a Control has about a hundred, and ten of them ran to fifty-seven thousand characters. Default false."
|
|
16998
17040
|
}
|
|
16999
17041
|
},
|
|
17000
17042
|
requires: ["projectPath", "scenePath"]
|
|
@@ -17003,7 +17045,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17003
17045
|
name: "scene_node",
|
|
17004
17046
|
title: "Scene nodes",
|
|
17005
17047
|
description: `One node in a scene file: add, read, set, duplicate, reparent or delete it, paint TileMap cells, or bake a navigation region. Any ClassDB node type or script class can be added, so a NavigationRegion2D, an AnimationTree or a Camera3D is an add with that nodeType and its properties, and another scene is instanced by an add with instancePath. A set with properties {"name": "Boss"} renames a node, and answers nodePath as its new path. A rename or a reparent carries the node paths naming the node, as the editor's rename does: NodePath values on nodes (in arrays, dictionaries, built-in resources, %unique paths) and the tracks its players' animations hold, saving a library kept in a .tres of its own, and answers each rewrite under retargeted and each path it left under notRetargeted. The move is followed into every other .tscn that instances or inherits this scene at any depth: the editable children, values, added nodes and connections named by the old path are rewritten, since Godot finds a moved node's values by unique_id but editable children only by path and drops the values kept below them on the next save, and that scene's node paths and tracks are carried as above. A delete rewrites no path, and answers under stillNaming what named the deleted node and now names nothing. A property holding a Resource takes the res:// path of one, so a texture, a material or a theme is a set like any other. A write is refused while the editor holds unsaved changes to the scene. A node this scene inherits takes values of this scene's own, and so does a node inside an instance of another scene once that instance's editableChildren is true; neither can be moved, renamed, copied or deleted, since the other scene makes it again on load. ${WITHOUT_AN_EDITOR}`,
|
|
17006
|
-
answer: "get answers nodeType, the node's engine class, with scriptClass when its script declares a class_name; nodePath, its path from the root however it was named; setHere, the properties this scene's own file sets on it; and valuesFrom, the scene every other value comes from, when another scene makes the node. A set that gives a property the value the node has without it (what the scene it comes from gives, or its default) leaves it out of the file, as the editor does, and answers it under notStored, with notStoredNote saying what it follows now. retargeted lists each path a rename or a reparent rewrote, as {scenePath, nodePath, property or animation and track, from, to}, and notRetargeted each it left, with a reason: a track kept in another scene or an imported file, or a value another scene gives that the move left naming nothing. followed names the other scenes a move rewrote, and notFollowed any left as they were, with why. scriptsNaming lists each line of a script that reads a node a rename, a reparent or a delete took by the path it had, as {scenePath, nodePath, script, line, from, to}: the script is on the node at nodePath, or is one that node's script extends, in this scene or one instancing or inheriting it, and to is the path that reaches the node from there now, absent after a delete. It reads $Path, %Unique, and a string handed to get_node, get_node_or_null or has_node on the node itself; a path built at run time is not seen. Scripts are left as they were, as the editor's own rename leaves them, so each line listed fails until it is changed. A 2D bake_navigation that read no obstacle says in its note that all its outlines enclose is walkable, and names the nodes giving obstacles that sit under no region. stillNaming lists each node path and track, here and in the scenes instancing or inheriting this one, that named a deleted node, and deletedWithIt each node of this scene's own that went with it, the nodes inside an instance going with the instance. groups lists, sorted, every group a node is in, on a get, an add that gave addGroups and every set that changes them; get adds groupsHere, the groups this file puts it in, when another scene puts it in others, and layers, each layer mask it answers (collision_layer, collision_mask, visibility_layer) as the layers it holds, {layer, name} with the name project.godot gives the layer. Each of these lists is answered only when it names anything. add and set answer each property given under properties as the node holds it once written; alsoChanged gives the other properties the engine changed in step with a set, as a Control's offsets with its size, and those a reparent changed to keep the node where it is drawn, as {was, now}; namesNoNode, each node path written naming no node. get adds placement, a Node3D's position, rotation_degrees and scale from its transform. " + ALSO_SAVED,
|
|
17048
|
+
answer: "get answers nodeType, the node's engine class, with scriptClass when its script declares a class_name; nodePath, its path from the root however it was named; setHere, the properties this scene's own file sets on it; and valuesFrom, the scene every other value comes from, when another scene makes the node. A set that gives a property the value the node has without it (what the scene it comes from gives, or its default) leaves it out of the file, as the editor does, and answers it under notStored, with notStoredNote saying what it follows now. retargeted lists each path a rename or a reparent rewrote, as {scenePath, nodePath, property or animation and track, from, to}, and notRetargeted each it left, with a reason: a track kept in another scene or an imported file, or a value another scene gives that the move left naming nothing. followed names the other scenes a move rewrote, and notFollowed any left as they were, with why. scriptsNaming lists each line of a script that reads a node a rename, a reparent or a delete took by the path it had, as {scenePath, nodePath, script, line, from, to}: the script is on the node at nodePath, or is one that node's script extends, in this scene or one instancing or inheriting it, and to is the path that reaches the node from there now, absent after a delete. It reads $Path, %Unique, and a string handed to get_node, get_node_or_null or has_node on the node itself; a path built at run time is not seen. Scripts are left as they were, as the editor's own rename leaves them, so each line listed fails until it is changed. A 2D bake_navigation that read no obstacle says in its note that all its outlines enclose is walkable, and names the nodes giving obstacles that sit under no region. stillNaming lists each node path and track, here and in the scenes instancing or inheriting this one, that named a deleted node, and deletedWithIt each node of this scene's own that went with it, the nodes inside an instance going with the instance. groups lists, sorted, every group a node is in, on a get, an add that gave addGroups and every set that changes them; get adds groupsHere, the groups this file puts it in, when another scene puts it in others, and layers, each layer mask it answers (collision_layer, collision_mask, visibility_layer) as the layers it holds, {layer, name} with the name project.godot gives the layer. Each of these lists is answered only when it names anything. add and set answer each property given under properties as the node holds it once written; alsoChanged gives the other properties the engine changed in step with a set, as a Control's offsets with its size, and those a reparent changed to keep the node where it is drawn, as {was, now}; namesNoNode, each node path written naming no node; sharedWith, for a property written through a resource built into the scene, each other {node, property} holding it, which changed too; setByScript, what a script that is not a tool set as a headless write ran, which the file does not hold; loadsAs, a value given that the file holds and the node's own script changes as the scene loads, as it loads. get adds placement, a Node3D's position, rotation_degrees and scale from its transform. " + ALSO_SAVED,
|
|
17007
17049
|
parameters: {
|
|
17008
17050
|
projectPath: PROJECT_PATH,
|
|
17009
17051
|
scenePath: SCENE_PATH,
|
|
@@ -17019,7 +17061,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17019
17061
|
nodeType: {
|
|
17020
17062
|
type: "string",
|
|
17021
17063
|
ops: ["add"],
|
|
17022
|
-
description: `add: the node class, such as "CharacterBody2D", or a class_name a project script declares, which the node is made as with that script attached. Give this or instancePath. The answer
|
|
17064
|
+
description: `add: the node class, such as "CharacterBody2D", or a class_name a project script declares, which the node is made as with that script attached. Give this or instancePath. The answer names the node made as get does: nodeType its engine class, for an instance the class of its scene's root, with scriptClass when its script declares a class_name.`
|
|
17023
17065
|
},
|
|
17024
17066
|
instancePath: {
|
|
17025
17067
|
type: "string",
|
|
@@ -17117,7 +17159,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17117
17159
|
{
|
|
17118
17160
|
name: "scene_signal",
|
|
17119
17161
|
title: "Scene signals",
|
|
17120
|
-
description: `Signal connections in a scene file. Each connection answers with its flags as the number and as flagNames; persist is on every connection, since a scene saves no other kind. A connection carries binds, the values passed after the signal's own, and unbinds, how many of the signal's own are dropped first, as the editor's connect dialog offers them; each is answered only when there is one. A connect of a pair already connected says alreadyConnected, with flagsBefore, bindsBefore and unbindsBefore when it changed them, and keeps the binds and unbinds it had unless the call gives new ones. A connect to a method the target does not have is still written, as the editor's own dialog allows, since the method may be written next; methodFound false says so, and until the method exists the signal fails each time it is emitted. One whose method cannot take what the signal passes, with its binds and unbinds (too few or too many arguments, an object where the method takes a value or the reverse, or an engine class unrelated to the one it takes), is written too, and answers argumentsFit false with misfit saying why; list gives each connection its methodFound, and misfit when it has one. list marks editable false on a connection
|
|
17162
|
+
description: `Signal connections in a scene file. Each connection answers with its flags as the number and as flagNames; persist is on every connection, since a scene saves no other kind. A connection carries binds, the values passed after the signal's own, and unbinds, how many of the signal's own are dropped first, as the editor's connect dialog offers them; each is answered only when there is one. A connect of a pair already connected says alreadyConnected, with flagsBefore, bindsBefore and unbindsBefore when it changed them, and keeps the binds and unbinds it had unless the call gives new ones. A connect to a method the target does not have is still written, as the editor's own dialog allows, since the method may be written next; methodFound false says so, and until the method exists the signal fails each time it is emitted. One whose method cannot take what the signal passes, with its binds and unbinds (too few or too many arguments, an object where the method takes a value or the reverse, or an engine class unrelated to the one it takes), is written too, and answers argumentsFit false with misfit saying why; list gives each connection its methodFound, and misfit when it has one. list marks editable false on a connection a scene this one instances or inherits makes, on its own root as well as inside it, and names that scene under madeIn; only that scene can change it, so a disconnect of one is refused. ${WITHOUT_AN_EDITOR} ${ALSO_SAVED}`,
|
|
17121
17163
|
parameters: {
|
|
17122
17164
|
projectPath: PROJECT_PATH,
|
|
17123
17165
|
scenePath: SCENE_PATH,
|
|
@@ -17185,7 +17227,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17185
17227
|
animationName: {
|
|
17186
17228
|
type: "string",
|
|
17187
17229
|
ops: ["read", "create", "add_track", "remove_track", "set", "rename", "remove", "add_state"],
|
|
17188
|
-
description: "The animation in the player. A name before a slash is the library it is in, as moves/walk. read: the one to answer with whole, its tracks a list; without it, read lists them, each with the number of its tracks under tracks. create: a library the player does not have is made in the scene and named under libraryMade. add_state: the one that state plays. One the player does not have yet is written all the same, answering animationFound false and why under animationMissing."
|
|
17230
|
+
description: "The animation in the player. A name before a slash is the library it is in, as moves/walk. read: the one to answer with whole, its tracks a list; without it, read lists them, each with the number of its tracks under tracks. create: a library the player does not have is made in the scene and named under libraryMade, where an empty name is the default library of the player, the one a name without a slash is in. add_state: the one that state plays. One the player does not have yet is written all the same, answering animationFound false and why under animationMissing."
|
|
17189
17231
|
},
|
|
17190
17232
|
newName: {
|
|
17191
17233
|
type: "string",
|
|
@@ -17330,7 +17372,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17330
17372
|
{
|
|
17331
17373
|
name: "script_edit",
|
|
17332
17374
|
title: "Edit scripts",
|
|
17333
|
-
description: "Creates a GDScript file, or adds functions, variables and signals to one. Every declaration written carries a type. Both load what they wrote under the project's own warning settings and answer with parses; the engine's reasons for a refusal come back under engine_messages. create answers with the extends and class_name the engine read (class_name null for a script declaring none), and a script declaring a class_name is put in the project's class list, with registered saying whether it is there, and an editor serving the project scans for it, so it resolves the class at once (editorRescanned); a class_name another script or an engine class holds, or an extends naming no class, is refused with nothing written. modify makes every addition or none, refuses with nothing changed additions that would stop a script that parses from parsing, giving the engine's reasons, and answers with the line each one is on in the file written; with an editor serving the project it reloads the script there too, so the editor's answers read what was written, and editorReloaded says so (editorNote when it could not, or when a script tab holds unsaved edits that saving would write over the file). rename renames a class or a member wherever the engine uses it and writes every file or none. A new name already taken is refused with nothing written, a member's included when a class inheriting it already uses the name in code, as a local or a parameter. changed lists each file with its changed lines as they now read, and leftAlone lists every other whole-word occurrence of the old name with what the tokenizer makes of it (code, string, comment, doc, nodePath, scene, text, or ignored for a file under a .gdignore directory, which is never changed) and, where it is not plain, why: a string that is the old name whole, such as find_children's type or a call's method name, which nothing renames or compiles and which is listed first; a node named like the class; a call on a value whose type the analyser could not tell. A member rename answers under languageServer how many requests it made and how long they took. A comment, documentation line, string or text file line with nothing more to say is given under leftAloneInProse by file, kind and line instead, and code that is certainly another symbol of the same name, a bare name in a file where nothing inherits the member or one the language server resolved to another declaration, under leftAloneOtherSymbols as each why with the files and lines it covers. summary.leftAlone counts each of the three lists beside the total. When those two grouped lists run past 300 lines, they are written with each line's text to a file under the project's .godot directory instead, of which the newest ten are kept, and leftAloneDetail names the file and counts what is in it. Read both before calling the rename done; nothing in them was changed. Afterwards an open editor scans the changed files and reloads every changed script, which is also the compile check, and its answer comes back under editor; with no editor the class cache is rebuilt and the changed scripts are compiled in a fresh engine, under classCache and compiled. written is true once the files are written, and ok is false when what came after the write failed: the files are left changed, and note says so.",
|
|
17375
|
+
description: "Creates a GDScript file, or adds functions, variables and signals to one. Every declaration written carries a type. Both load what they wrote under the project's own warning settings and answer with parses; the engine's reasons for a refusal come back under engine_messages. create answers with the extends and class_name the engine read (class_name null for a script declaring none), and writes the .uid beside the script as the editor does, answering it under uid; a script declaring a class_name is put in the project's class list, with registered saying whether it is there, and an editor serving the project scans for it, so it resolves the class at once (editorRescanned); a class_name another script or an engine class holds, or an extends naming no class, is refused with nothing written. modify makes every addition or none, refuses with nothing changed additions that would stop a script that parses from parsing, giving the engine's reasons, and answers with the line each one is on in the file written; with an editor serving the project it reloads the script there too, so the editor's answers read what was written, and editorReloaded says so (editorNote when it could not, or when a script tab holds unsaved edits that saving would write over the file). rename renames a class or a member wherever the engine uses it and writes every file or none. A new name already taken is refused with nothing written, a member's included when a class inheriting it already uses the name in code, as a local or a parameter. changed lists each file with its changed lines as they now read, and leftAlone lists every other whole-word occurrence of the old name with what the tokenizer makes of it (code, string, comment, doc, nodePath, scene, text, or ignored for a file under a .gdignore directory, which is never changed) and, where it is not plain, why: a string that is the old name whole, such as find_children's type or a call's method name, which nothing renames or compiles and which is listed first; a node named like the class; a call on a value whose type the analyser could not tell. A member rename answers under languageServer how many requests it made and how long they took. A comment, documentation line, string or text file line with nothing more to say is given under leftAloneInProse by file, kind and line instead, and code that is certainly another symbol of the same name, a bare name in a file where nothing inherits the member or one the language server resolved to another declaration, under leftAloneOtherSymbols as each why with the files and lines it covers. summary.leftAlone counts each of the three lists beside the total. When those two grouped lists run past 300 lines, they are written with each line's text to a file under the project's .godot directory instead, of which the newest ten are kept, and leftAloneDetail names the file and counts what is in it. Read both before calling the rename done; nothing in them was changed. Afterwards an open editor scans the changed files and reloads every changed script, which is also the compile check, and its answer comes back under editor; with no editor the class cache is rebuilt and the changed scripts are compiled in a fresh engine, under classCache and compiled. written is true once the files are written, and ok is false when what came after the write failed: the files are left changed, and note says so.",
|
|
17334
17376
|
parameters: {
|
|
17335
17377
|
projectPath: PROJECT_PATH,
|
|
17336
17378
|
scriptPath: SCRIPT_PATH,
|
|
@@ -17448,7 +17490,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17448
17490
|
{
|
|
17449
17491
|
name: "resource_edit",
|
|
17450
17492
|
title: "Edit resources",
|
|
17451
|
-
description: 'Resource files: read one, create any ClassDB resource as .tres, change one, write a shader, build a TileSet, or set a Theme colour or font size. A material is a create with resourceType StandardMaterial3D, ShaderMaterial or CanvasItemMaterial. read answers with the properties the file sets, in the form modify takes, with resourceType the engine class and scriptClass the class_name of its script when it declares one, and a Theme lists its items there as "Button/colors/font_color", so a style box, a font or a constant is set through modify by that name, as "Button/styles/normal" given the res:// path of a StyleBox. A Curve, Curve2D or Curve3D reads as the inspector shows it, point_count and point_0/position and the rest, rather than the _data list the file stores, and so does one built into a resource or a scene. create and modify answer each property given under properties as the file holds it once saved, in the same form. A create or modify whose properties made the engine change others, as a Gradient given more colours than offsets gives each new point an offset of 0, names each of those under alsoChanged with what it held before and holds now. A property holding a resource takes its res:// path; a file never imported is refused with the reimport that imports it. move takes any file of the project, a script or an image as much as a resource, or a directory, and works from the files, so it needs no editor; one serving the project is brought up to it as after a move in its own FileSystem dock, its open scenes and cached resources named for the new paths and the project settings the move rewrote taken from the file. read needs no editor either: with none serving the project, a headless engine reads the file. The ops that write a resource go through the editor when one serves the project; with none, a headless engine runs the tool the editor would and the answer says so under writtenBy. That engine runs the script of the resource, where the editor runs only tool scripts, so what a script changes as the file loads or as a property is set is put back before the save, and a resource where that cannot be done is refused and the file left as it was.',
|
|
17493
|
+
description: 'Resource files: read one, create any ClassDB resource as .tres, change one, write a shader, build a TileSet, or set a Theme colour or font size. A material is a create with resourceType StandardMaterial3D, ShaderMaterial or CanvasItemMaterial. read answers with the properties the file sets, in the form modify takes, with resourceType the engine class and scriptClass the class_name of its script when it declares one, and a Theme lists its items there as "Button/colors/font_color", so a style box, a font or a constant is set through modify by that name, as "Button/styles/normal" given the res:// path of a StyleBox. A Curve, Curve2D or Curve3D reads as the inspector shows it, point_count and point_0/position and the rest, rather than the _data list the file stores, and so does one built into a resource or a scene. create and modify answer each property given under properties as the file holds it once saved, in the same form. A create or modify whose properties made the engine change others, as a Gradient given more colours than offsets gives each new point an offset of 0, names each of those under alsoChanged with what it held before and holds now. A property holding a resource takes its res:// path; a file never imported is refused with the reimport that imports it. move takes any file of the project, a script or an image as much as a resource, or a directory, and works from the files, so it needs no editor; one serving the project is brought up to it as after a move in its own FileSystem dock, its open scenes and cached resources named for the new paths and the project settings the move rewrote taken from the file. read needs no editor either: with none serving the project, a headless engine reads the file. The ops that write a resource go through the editor when one serves the project; with none, a headless engine runs the tool the editor would and the answer says so under writtenBy. That engine runs the script of the resource, where the editor runs only tool scripts, so what a script changes as the file loads or as a property is set is put back before the save, and answered under setByScript rather than alsoChanged, with setByScriptNote; a resource where that cannot be done is refused and the file left as it was. A read with none leaves out what such a script set as the file loaded.',
|
|
17452
17494
|
parameters: {
|
|
17453
17495
|
projectPath: PROJECT_PATH,
|
|
17454
17496
|
resourcePath: {
|
|
@@ -17615,7 +17657,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17615
17657
|
type: ["string", "array"],
|
|
17616
17658
|
items: { type: "string" },
|
|
17617
17659
|
ops: ["start"],
|
|
17618
|
-
description: "start: gdUnit4 suites to play instead of a scene, a suite file or a test directory inside the project or a list of them, as project_test's path takes them. They run in the tests addon's suites scene through the runner gdharness test uses, so with profile: true the profile names what the suites cost by function, which project_test and gdharness test cannot show, since the profiler never sees a script run. The run quits with gdUnit4's exit code; read it with editor_output until running is false, then profile. Takes no scene or executable, and needs gdUnit4 and the tests addon of this release in the project."
|
|
17660
|
+
description: "start: gdUnit4 suites to play instead of a scene, a suite file or a test directory inside the project or a list of them, as project_test's path takes them, though always whole: an entry naming one case is refused. They run in the tests addon's suites scene through the runner gdharness test uses, so with profile: true the profile names what the suites cost by function, which project_test and gdharness test cannot show, since the profiler never sees a script run. The run quits with gdUnit4's exit code; read it with editor_output until running is false, then profile. Takes no scene or executable, and needs gdUnit4 and the tests addon of this release in the project."
|
|
17619
17661
|
},
|
|
17620
17662
|
args: {
|
|
17621
17663
|
type: "array",
|
|
@@ -18146,15 +18188,15 @@ var init_tool_definitions = __esm(() => {
|
|
|
18146
18188
|
{
|
|
18147
18189
|
name: "runtime_input",
|
|
18148
18190
|
title: "Send input to the game",
|
|
18149
|
-
description: "Input to the running game: a whole click on a Control or a 3D node named by path or
|
|
18150
|
-
answer: `click: what was under the pointer, and what became of the control: in_tree, removed or freed. A button answers pressed_signal, whether its pressed signal fired during the click. Named by says, found says which match it was, its index being the index argument that presses the same match again, found.item names the tab, list entry, tree row or menu bar title pressed (kind; index, the item's own number in its control, which is not found.index; says; and for a row the column of the cell saying the words), and found.pressedOn says "the words" for words in a RichTextLabel pressed where they are drawn, or "the middle of the label" with a note saying why they could not be placed; on a rich label link_pressed says whether the press reached a link, with link_meta giving the meta the label reports clicked, an empty one included. A control scrolled into view first is scrolled each container out from it a frame after the one inside, and scrolled_into_view says whether a scroll value changed. A button in toggle mode, a CheckBox or CheckButton included, answers pressed with before and after, its button_pressed either side of the click: the same value twice is a click the game undid, and after is null when the click freed it. A refusal for a pointer turned off names where mouse_behavior_recursive was set. On a 3D node landed says the interface did not swallow the press: no control under the pointer stopped it, a control letting the pointer pass included, and every viewport on the way out from the room had the pointer over what shows the room. choose: what was chosen, read before the press, which is heard rather than assumed; what became of the control named (in_tree, removed or freed, since a game can rebuild its screen on the pick); and what the button in front of the menu shows now when it is still there. A menu is opened rather than clicked because its items are drawn rather than built, and the item is looked for as the menu stands once open, since a game can fill a menu as it opens. action, key, mouse_click and joypad_button: the event as it was sent, with whole saying whether the press was let go of again and pressed whether it is left held down (true only after pressed true), for a key the keycode, physical_keycode and unicode it carried, and for a controller button its number, its name and the device. mouse_motion: the position and the relative movement sent. mouse_drag: from, to, motions sent and relative_total. joypad_motion: the axis by number and name, the value and the device. key, mouse_click, click, joypad_button and joypad_motion answer actions, the InputMap actions the event also presses; for a motion, those its direction reaches past their deadzone, and none for one inside it. mouse_click, mouse_motion and mouse_drag answer hovered, the control under the pointer once the events are handled, or null over none; the other events are aimed at nothing. mouse_click with a press, and mouse_drag, answer press_went_to, the path of what the press went to (a control, or a window embedded over the point), read before it, or null when nothing there takes the pointer; given nodePath naming a Control, landed says whether that is the control or inside it. A click that did not land on a control whose mouse_filter is ignore, as a Label's is, says so under why_not_landed, and one on a control with something drawn over it names that, a window stacked above the one the control is in among them. An action, a key, a joypad_button or a joypad_motion answers focused with the path of the Control holding the keyboard focus when it went in, since that Control gets it before the game's _unhandled_input: Space or Enter on a focused button presses the button, and a stick or dpad pressing ui_down moves a focused list's selection, or the focus, and never reaches the game's handler. Where the press moved the focus, focused_after names the Control holding it once the event was handled, or null when nothing does. A key that a focused LineEdit or TextEdit took as typing names that field under typed_into, with what it holds under holds, since a field being edited keeps the key from the game's _unhandled_input. editing_ended true, on a key or on text, is a LineEdit that stopped being edited, as Enter does by submitting it: it keeps the focus, and nothing typed into it lands until it is clicked again. text with the focus on a control that is no LineEdit or TextEdit answers holds null and a note saying so, since a focused button takes a space or Enter as a press. A mouse_click, mouse_motion or mouse_drag given nodePath answers it under path, its points in the pixels of the window they went to, and that window under window when it is not the game's own. One with a point outside the viewport it went to says so under outside_viewport, with the viewport's rectangle, since no control there receives it.`,
|
|
18191
|
+
description: "Input to the running game: a whole click on a Control or a 3D node named by path or by the words on it, an item chosen out of a menu, typing into the focus, or a raw action, key, mouse button, mouse motion or drag, touch or finger drag. All of it works headless, where the viewport is the project's window size and the GUI only takes what is inside it. On gdharness's own desktop, where a windowed run goes, Windows refuses to say where the pointer is: every pointer answer there carries pointer_on_hidden_desktop, and the run's first pointer_note says why it matters: get_mouse_position() on the root window, and what derives from it, reads nonsense, while what reads the event itself (the control under it, hover, tooltips, input handlers) gets the injected position. A native window no real pointer has entered gets no hover from Godot, so the runtime supplies it there and the answers say hover_supplied.",
|
|
18192
|
+
answer: `click: what was under the pointer, and what became of the control: in_tree, removed or freed. A button answers pressed_signal, whether its pressed signal fired during the click. Named by says, found says which match it was, its index being the index argument that presses the same match again, found.item names the tab, list entry, tree row or menu bar title pressed (kind; index, the item's own number in its control, which is not found.index; says; and for a row the column of the cell saying the words), and found.pressedOn says "the words" for words in a RichTextLabel pressed where they are drawn, or "the middle of the label" with a note saying why they could not be placed; on a rich label link_pressed says whether the press reached a link, with link_meta giving the meta the label reports clicked, an empty one included. A control scrolled into view first is scrolled each container out from it a frame after the one inside, and scrolled_into_view says whether a scroll value changed. A button in toggle mode, a CheckBox or CheckButton included, answers pressed with before and after, its button_pressed either side of the click: the same value twice is a click the game undid, and after is null when the click freed it. A refusal for a pointer turned off names where mouse_behavior_recursive was set. On a 3D node landed says the interface did not swallow the press: no control under the pointer stopped it, a control letting the pointer pass included, and every viewport on the way out from the room had the pointer over what shows the room. choose: what was chosen, read before the press, which is heard rather than assumed; what became of the control named (in_tree, removed or freed, since a game can rebuild its screen on the pick); and what the button in front of the menu shows now when it is still there. A menu is opened rather than clicked because its items are drawn rather than built, and the item is looked for as the menu stands once open, since a game can fill a menu as it opens. action, key, mouse_click and joypad_button: the event as it was sent, with whole saying whether the press was let go of again and pressed whether it is left held down (true only after pressed true), for a key the keycode, physical_keycode and unicode it carried, and for a controller button its number, its name and the device. mouse_motion: the position and the relative movement sent. mouse_drag: from, to, motions sent and relative_total. touch: the finger's index, the position, whole and pressed as a mouse_click has them, and emulates_mouse; touch_drag: what mouse_drag answers, with index and emulates_mouse. joypad_motion: the axis by number and name, the value and the device. key, mouse_click, click, joypad_button and joypad_motion answer actions, the InputMap actions the event also presses; for a motion, those its direction reaches past their deadzone, and none for one inside it. mouse_click, mouse_motion and mouse_drag answer hovered, the control under the pointer once the events are handled, or null over none; the other events are aimed at nothing. mouse_click with a press, and mouse_drag, answer press_went_to, the path of what the press went to (a control, or a window embedded over the point), read before it, or null when nothing there takes the pointer; given nodePath naming a Control, landed says whether that is the control or inside it. A click that did not land on a control whose mouse_filter is ignore, as a Label's is, says so under why_not_landed, and one on a control with something drawn over it names that, a window stacked above the one the control is in among them. An action, a key, a joypad_button or a joypad_motion answers focused with the path of the Control holding the keyboard focus when it went in, since that Control gets it before the game's _unhandled_input: Space or Enter on a focused button presses the button, and a stick or dpad pressing ui_down moves a focused list's selection, or the focus, and never reaches the game's handler. Where the press moved the focus, focused_after names the Control holding it once the event was handled, or null when nothing does. A key that a focused LineEdit or TextEdit took as typing names that field under typed_into, with what it holds under holds, since a field being edited keeps the key from the game's _unhandled_input. editing_ended true, on a key or on text, is a LineEdit that stopped being edited, as Enter does by submitting it: it keeps the focus, and nothing typed into it lands until it is clicked again. text with the focus on a control that is no LineEdit or TextEdit answers holds null and a note saying so, since a focused button takes a space or Enter as a press. A mouse_click, mouse_motion or mouse_drag given nodePath answers it under path, its points in the pixels of the window they went to, and that window under window when it is not the game's own. One with a point outside the viewport it went to says so under outside_viewport, with the viewport's rectangle, since no control there receives it.`,
|
|
18151
18193
|
parameters: {
|
|
18152
18194
|
projectPath: RUNNING_PROJECT_PATH,
|
|
18153
18195
|
pid: RUNNING_PID,
|
|
18154
18196
|
nodePath: {
|
|
18155
18197
|
type: "string",
|
|
18156
|
-
ops: ["click", "choose", "mouse_click", "mouse_motion", "mouse_drag"],
|
|
18157
|
-
description: "click: the Control to click, at its centre, at x and y in its own space when those are given, or at the middle of the part on screen when no scroll brings its centre into view (said under pressed_on), or the 3D node to click, where it is drawn; with says, where to look for the control instead, default the whole tree. choose: the PopupMenu, or the OptionButton or MenuButton in front of one. mouse_click, mouse_motion, mouse_drag: the Control, Node2D or Window whose own space the points are in, a Window's being its own pixels; the events go to the window that shows it, a native one included, which the game's window pixels cannot reach, and the answer names it under window when it is not the game's own. Default the game's window."
|
|
18198
|
+
ops: ["click", "choose", "mouse_click", "mouse_motion", "mouse_drag", "touch", "touch_drag"],
|
|
18199
|
+
description: "click: the Control to click, at its centre, at x and y in its own space when those are given, or at the middle of the part on screen when no scroll brings its centre into view (said under pressed_on), or the 3D node to click, where it is drawn; with says, where to look for the control instead, default the whole tree. choose: the PopupMenu, or the OptionButton or MenuButton in front of one. mouse_click, mouse_motion, mouse_drag, touch, touch_drag: the Control, Node2D or Window whose own space the points are in, a Window's being its own pixels; the events go to the window that shows it, a native one included, which the game's window pixels cannot reach, and the answer names it under window when it is not the game's own. Default the game's window."
|
|
18158
18200
|
},
|
|
18159
18201
|
says: {
|
|
18160
18202
|
type: "string",
|
|
@@ -18168,15 +18210,15 @@ var init_tool_definitions = __esm(() => {
|
|
|
18168
18210
|
},
|
|
18169
18211
|
pressed: {
|
|
18170
18212
|
type: "boolean",
|
|
18171
|
-
ops: ["action", "key", "mouse_click", "joypad_button"],
|
|
18172
|
-
description: "action, key, mouse_click, joypad_button: leave it out and the press is a whole one, down and up a frame apart. true holds it down, false lets go of one being held. A wheel button held down is still a whole step, since a wheel is never held."
|
|
18213
|
+
ops: ["action", "key", "mouse_click", "joypad_button", "touch"],
|
|
18214
|
+
description: "action, key, mouse_click, joypad_button, touch: leave it out and the press is a whole one, down and up a frame apart. true holds it down, false lets go of one being held, a finger lifted at x and y. A wheel button held down is still a whole step, since a wheel is never held."
|
|
18173
18215
|
},
|
|
18174
18216
|
holdFrames: {
|
|
18175
18217
|
type: "integer",
|
|
18176
18218
|
minimum: 1,
|
|
18177
18219
|
maximum: 600,
|
|
18178
|
-
ops: ["action", "key", "mouse_click", "joypad_button"],
|
|
18179
|
-
description: "action, key, mouse_click, joypad_button: hold a whole press down for this many process frames and then let go, both inside the game, so a pour, a charge or a long press runs to a set length. A press, a frames wait and a release sent as three calls hold it for the time between the calls as well. The answer says held_frames, and under held_for the seconds the hold lasted and the physics_frames that ran in it: a process frame is no set time, and a headless game, drawing nothing, runs them far faster than the sixty physics ticks a second that _physics_process sees. Not with pressed, and not on a wheel step. Default 1."
|
|
18220
|
+
ops: ["action", "key", "mouse_click", "joypad_button", "touch"],
|
|
18221
|
+
description: "action, key, mouse_click, joypad_button, touch: hold a whole press down for this many process frames and then let go, both inside the game, so a pour, a charge or a long press runs to a set length. A press, a frames wait and a release sent as three calls hold it for the time between the calls as well. The answer says held_frames, and under held_for the seconds the hold lasted and the physics_frames that ran in it: a process frame is no set time, and a headless game, drawing nothing, runs them far faster than the sixty physics ticks a second that _physics_process sees. Not with pressed, and not on a wheel step. Default 1."
|
|
18180
18222
|
},
|
|
18181
18223
|
strength: {
|
|
18182
18224
|
type: "number",
|
|
@@ -18203,40 +18245,44 @@ var init_tool_definitions = __esm(() => {
|
|
|
18203
18245
|
},
|
|
18204
18246
|
index: {
|
|
18205
18247
|
type: "number",
|
|
18206
|
-
ops: ["click", "choose"],
|
|
18207
|
-
description: "click: which of several controls saying the words, counting from 0 in the order a refusal lists them, best matches first. choose: the item to take, by where it is in the list, when text will not do."
|
|
18248
|
+
ops: ["click", "choose", "touch", "touch_drag"],
|
|
18249
|
+
description: "click: which of several controls saying the words, counting from 0 in the order a refusal lists them, best matches first. choose: the item to take, by where it is in the list, when text will not do. touch, touch_drag: the finger, from 0, as InputEventScreenTouch.index; a second finger down at once is another index. Default 0."
|
|
18208
18250
|
},
|
|
18209
18251
|
shift: { type: "boolean", ops: ["key"], description: "key: hold Shift with it. Default false." },
|
|
18210
18252
|
ctrl: { type: "boolean", ops: ["key"], description: "key: hold Ctrl with it. Default false." },
|
|
18211
18253
|
alt: { type: "boolean", ops: ["key"], description: "key: hold Alt with it. Default false." },
|
|
18212
18254
|
x: {
|
|
18213
18255
|
type: "number",
|
|
18214
|
-
ops: ["click", "mouse_click", "mouse_motion", "mouse_drag"],
|
|
18215
|
-
description: "click: with y, the point to press in the control's own space instead of its centre, for a control that draws its parts itself; not with a 3D node, nor with says finding an item the control draws. mouse_click, mouse_motion: window pixels, or nodePath's own space when that is given. mouse_drag: where the press is, in the same space."
|
|
18256
|
+
ops: ["click", "mouse_click", "mouse_motion", "mouse_drag", "touch", "touch_drag"],
|
|
18257
|
+
description: "click: with y, the point to press in the control's own space instead of its centre, for a control that draws its parts itself; not with a 3D node, nor with says finding an item the control draws. mouse_click, mouse_motion, touch: window pixels, or nodePath's own space when that is given. mouse_drag, touch_drag: where the press is, in the same space."
|
|
18216
18258
|
},
|
|
18217
18259
|
y: {
|
|
18218
18260
|
type: "number",
|
|
18219
|
-
ops: ["click", "mouse_click", "mouse_motion", "mouse_drag"],
|
|
18220
|
-
description: "click, mouse_click, mouse_motion, mouse_drag: as x."
|
|
18261
|
+
ops: ["click", "mouse_click", "mouse_motion", "mouse_drag", "touch", "touch_drag"],
|
|
18262
|
+
description: "click, mouse_click, mouse_motion, mouse_drag, touch, touch_drag: as x."
|
|
18221
18263
|
},
|
|
18222
18264
|
toX: {
|
|
18223
18265
|
type: "number",
|
|
18224
|
-
ops: ["mouse_drag"],
|
|
18225
|
-
description: "mouse_drag: where the release is, in window pixels or nodePath's own space."
|
|
18266
|
+
ops: ["mouse_drag", "touch_drag"],
|
|
18267
|
+
description: "mouse_drag, touch_drag: where the release is, in window pixels or nodePath's own space."
|
|
18268
|
+
},
|
|
18269
|
+
toY: {
|
|
18270
|
+
type: "number",
|
|
18271
|
+
ops: ["mouse_drag", "touch_drag"],
|
|
18272
|
+
description: "mouse_drag, touch_drag: as toX."
|
|
18226
18273
|
},
|
|
18227
|
-
toY: { type: "number", ops: ["mouse_drag"], description: "mouse_drag: as toX." },
|
|
18228
18274
|
via: {
|
|
18229
18275
|
type: "array",
|
|
18230
18276
|
items: { type: "array", items: { type: "number" }, minItems: 2, maxItems: 2 },
|
|
18231
|
-
ops: ["mouse_drag"],
|
|
18232
|
-
description: "mouse_drag: points the stroke passes through on the way, in order, each [x, y] in the space x and y are in. The motions are spread along the whole stroke by distance. Default none: a straight line."
|
|
18277
|
+
ops: ["mouse_drag", "touch_drag"],
|
|
18278
|
+
description: "mouse_drag, touch_drag: points the stroke passes through on the way, in order, each [x, y] in the space x and y are in. The motions are spread along the whole stroke by distance. Default none: a straight line."
|
|
18233
18279
|
},
|
|
18234
18280
|
frames: {
|
|
18235
18281
|
type: "integer",
|
|
18236
18282
|
minimum: 1,
|
|
18237
18283
|
maximum: 600,
|
|
18238
|
-
ops: ["mouse_drag"],
|
|
18239
|
-
description: "mouse_drag: how many process frames the stroke takes, one motion each, as a real mouse sends one a frame; a hand takes about 20 to 60 for a stroke across a screen."
|
|
18284
|
+
ops: ["mouse_drag", "touch_drag"],
|
|
18285
|
+
description: "mouse_drag, touch_drag: how many process frames the stroke takes, one motion each, as a real mouse or finger sends one a frame; a hand takes about 20 to 60 for a stroke across a screen."
|
|
18240
18286
|
},
|
|
18241
18287
|
button: {
|
|
18242
18288
|
type: ["string", "number"],
|
|
@@ -18281,28 +18327,28 @@ var init_tool_definitions = __esm(() => {
|
|
|
18281
18327
|
operations: {
|
|
18282
18328
|
click: {
|
|
18283
18329
|
effect: "additive",
|
|
18284
|
-
summary: "press and release on a Control, a frame apart, named by nodePath or by says
|
|
18330
|
+
summary: "press and release on a Control, a frame apart, named by nodePath or by says (found under nodePath when both are given), or on a 3D node where it is drawn. A control hidden in a ScrollContainer is scrolled to first; a RichTextLabel link is pressed where its words are drawn. A disabled button, a control that does not process and one mouse_behavior_recursive turned off are refused, since the press would do nothing; by says, a reachable one is picked over it",
|
|
18285
18331
|
requires: []
|
|
18286
18332
|
},
|
|
18287
18333
|
choose: {
|
|
18288
18334
|
effect: "additive",
|
|
18289
|
-
summary: "take an item out of a menu, by
|
|
18335
|
+
summary: "take an item out of a menu, by its words or its place in the list: the menu is opened, the item found as it stands then and pressed through the engine's own path, which needs no window and not ui_accept's binding. A disabled or hidden button in front of the menu is refused, and so is a press the menu did not take",
|
|
18290
18336
|
requires: ["nodePath"]
|
|
18291
18337
|
},
|
|
18292
18338
|
action: { effect: "additive", summary: "press an action, or hold it", requires: ["action"] },
|
|
18293
18339
|
key: {
|
|
18294
18340
|
effect: "additive",
|
|
18295
|
-
summary: "press a key, or hold it. Not for a menu a click has opened: an OptionButton or PopupMenu pops up as a window of its own that a key
|
|
18341
|
+
summary: "press a key, or hold it. Not for a menu a click has opened: an OptionButton or PopupMenu pops up as a window of its own that a key never reaches, so arrows move nothing and Enter closes it unchosen; choose selects there. Other windows are not like it: an embedded dialog whose owner reads keys in _input gets them",
|
|
18296
18342
|
requires: ["keycode"]
|
|
18297
18343
|
},
|
|
18298
18344
|
text: {
|
|
18299
18345
|
effect: "additive",
|
|
18300
|
-
summary: "type a string into the field being edited, a character at a time,
|
|
18346
|
+
summary: "type a string into the field being edited, a character at a time, saying what it landed in and what it then holds (a secret field as its mask). With a dialog focused, the field is that dialog's focus, where the keys go",
|
|
18301
18347
|
requires: ["text"]
|
|
18302
18348
|
},
|
|
18303
18349
|
mouse_click: {
|
|
18304
18350
|
effect: "additive",
|
|
18305
|
-
summary: "click a mouse button at a position, pressed and released a frame apart, each event carrying the buttons held
|
|
18351
|
+
summary: "click a mouse button at a position, pressed and released a frame apart, each event carrying the buttons held after it; the answer says whole. A drag is mouse_drag. A wheel button is always a whole step, as a mouse sends one: a lone wheel press holds the viewport on the control that took it, so every later click lands there",
|
|
18306
18352
|
requires: ["x", "y"]
|
|
18307
18353
|
},
|
|
18308
18354
|
mouse_motion: {
|
|
@@ -18312,17 +18358,27 @@ var init_tool_definitions = __esm(() => {
|
|
|
18312
18358
|
},
|
|
18313
18359
|
mouse_drag: {
|
|
18314
18360
|
effect: "additive",
|
|
18315
|
-
summary: "a whole drag in one call
|
|
18361
|
+
summary: "a whole drag in one call: press at x, y, a motion each frame, release at toX, toY; for a stir, a paint stroke or a slow slider",
|
|
18362
|
+
requires: ["x", "y", "toX", "toY", "frames"]
|
|
18363
|
+
},
|
|
18364
|
+
touch: {
|
|
18365
|
+
effect: "additive",
|
|
18366
|
+
summary: "press a finger at x, y and lift it a frame later, or hold it, as an InputEventScreenTouch; in the game's window the engine makes mouse events carrying DEVICE_ID_EMULATION from the first finger down, where emulate_mouse_from_touch is on (emulates_mouse)",
|
|
18367
|
+
requires: ["x", "y"]
|
|
18368
|
+
},
|
|
18369
|
+
touch_drag: {
|
|
18370
|
+
effect: "additive",
|
|
18371
|
+
summary: "mouse_drag for one finger: the touch, an InputEventScreenDrag each frame, and the lift",
|
|
18316
18372
|
requires: ["x", "y", "toX", "toY", "frames"]
|
|
18317
18373
|
},
|
|
18318
18374
|
joypad_button: {
|
|
18319
18375
|
effect: "additive",
|
|
18320
|
-
summary: "press a controller button, or hold it, as an InputEventJoypadButton,
|
|
18376
|
+
summary: "press a controller button, or hold it, as an InputEventJoypadButton, as a controller sends it to _input and _unhandled_input; an action bound to the button fires with it. Pressed and released a frame apart unless pressed says otherwise",
|
|
18321
18377
|
requires: ["button"]
|
|
18322
18378
|
},
|
|
18323
18379
|
joypad_motion: {
|
|
18324
18380
|
effect: "additive",
|
|
18325
|
-
summary: "move a stick or trigger axis to a value, as an InputEventJoypadMotion
|
|
18381
|
+
summary: "move a stick or trigger axis to a value, as an InputEventJoypadMotion; the axis stays there until another motion moves it",
|
|
18326
18382
|
requires: ["axis", "value"]
|
|
18327
18383
|
}
|
|
18328
18384
|
}
|
|
@@ -18419,7 +18475,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
18419
18475
|
{
|
|
18420
18476
|
name: "debug_breakpoint",
|
|
18421
18477
|
title: "Breakpoints",
|
|
18422
|
-
description: "Sets, removes or lists breakpoints. A set on a script that is not there, past its last line, or on a line holding no code, blank or only a comment, is refused, as nothing would ever stop there; a remove takes any line, and with no editor connected answers removed false, saying why, when none was held there. Set them first, then editor_run, and the game stops where you asked. With the editor connected it goes through the editor's debug adapter; with none, it is held here. A game this server starts itself, which editor_run start does for a run given args, savesIn, env or silent, for a headless run of a project whose editor plays its games with a window, or with no editor, is handed every breakpoint held here as it starts, and one set while that game runs reaches it, and the set answers once the game has taken it, which it does between frames, so a call made after the answer stops there (runningGame says so). The editor keeps a breakpoint set this way for one play, so every breakpoint this server holds is sent again before each play editor_run starts, and the start answer lists them under breakpoints. The set is also kept in the project, for the server that comes after a reconnect: a breakpoint stays until it is removed here, whichever server set it. Every answer lists the whole set under held, and the breakpoints the user set in the editor's own gutter under setInEditor; setting one here never takes those away, and removing a line here clears it whoever set it. Godot clears every breakpoint in the editor when a debug session opens unless network/debug_adapter/sync_breakpoints is on, which the addon turns on as it loads; an editor where it was turned off again is said so under breakpointsAtRisk, here and in editor_status.",
|
|
18478
|
+
description: "Sets, removes or lists breakpoints. A set on a script that is not there, past its last line, or on a line holding no code, blank or only a comment, is refused, as nothing would ever stop there; a remove takes any line, and with no editor connected answers removed false, saying why, when none was held there. Set them first, then editor_run, and the game stops where you asked. With the editor connected it goes through the editor's debug adapter; with none, it is held here. A game this server starts itself, which editor_run start does for a run given args, savesIn, env or silent, for a headless run of a project whose editor plays its games with a window, or with no editor, is handed every breakpoint held here as it starts, and one set while that game runs reaches it, and the set answers once the game has taken it, which it does between frames, so a call made after the answer stops there (runningGame says so). Such a game does not stop on a script error, so a run that meets one carries on past it: editor_output has the error with where it happened, and a breakpoint on that line stops the game before the line runs. The editor keeps a breakpoint set this way for one play, so every breakpoint this server holds is sent again before each play editor_run starts, and the start answer lists them under breakpoints. The set is also kept in the project, for the server that comes after a reconnect: a breakpoint stays until it is removed here, whichever server set it. Every answer lists the whole set under held, and the breakpoints the user set in the editor's own gutter under setInEditor; setting one here never takes those away, and removing a line here clears it whoever set it. Godot clears every breakpoint in the editor when a debug session opens unless network/debug_adapter/sync_breakpoints is on, which the addon turns on as it loads; an editor where it was turned off again is said so under breakpointsAtRisk, here and in editor_status.",
|
|
18423
18479
|
parameters: {
|
|
18424
18480
|
projectPath: PROJECT_PATH,
|
|
18425
18481
|
scriptPath: { ...SCRIPT_PATH, ops: ["set", "remove"] },
|
|
@@ -41431,7 +41487,7 @@ var init_boot_note = __esm(() => {
|
|
|
41431
41487
|
|
|
41432
41488
|
// src/breakpoint-note.ts
|
|
41433
41489
|
import { mkdirSync as mkdirSync12, readFileSync as readFileSync26, rmSync as rmSync13, writeFileSync as writeFileSync14 } from "node:fs";
|
|
41434
|
-
import { dirname as dirname16, isAbsolute as isAbsolute7, join as join31, relative as
|
|
41490
|
+
import { dirname as dirname16, isAbsolute as isAbsolute7, join as join31, relative as relative6 } from "node:path";
|
|
41435
41491
|
function breakpointNotePath(projectPath) {
|
|
41436
41492
|
return join31(projectPath, ".godot", "gdharness-breakpoints.json");
|
|
41437
41493
|
}
|
|
@@ -41446,7 +41502,7 @@ function writeBreakpointNote(projectPath, held) {
|
|
|
41446
41502
|
}
|
|
41447
41503
|
const note = {
|
|
41448
41504
|
breakpoints: inside.map((one) => ({
|
|
41449
|
-
script:
|
|
41505
|
+
script: relative6(root, one.scriptPath).replaceAll("\\", "/"),
|
|
41450
41506
|
lines: [...one.lines]
|
|
41451
41507
|
}))
|
|
41452
41508
|
};
|
|
@@ -41483,7 +41539,7 @@ function readBreakpointNote(projectPath) {
|
|
|
41483
41539
|
}
|
|
41484
41540
|
}
|
|
41485
41541
|
function withinProject(projectPath, scriptPath) {
|
|
41486
|
-
const rel =
|
|
41542
|
+
const rel = relative6(projectPath, scriptPath);
|
|
41487
41543
|
return rel !== "" && !rel.startsWith("..") && !isAbsolute7(rel);
|
|
41488
41544
|
}
|
|
41489
41545
|
var init_breakpoint_note = __esm(() => {
|
|
@@ -43376,9 +43432,9 @@ var init_rename = __esm(() => {
|
|
|
43376
43432
|
|
|
43377
43433
|
// src/rename-symbol.ts
|
|
43378
43434
|
import { existsSync as existsSync30, mkdirSync as mkdirSync17, readdirSync as readdirSync16, realpathSync as realpathSync4, rmSync as rmSync16, writeFileSync as writeFileSync18 } from "node:fs";
|
|
43379
|
-
import { basename as basename9, dirname as dirname21, join as join36, relative as
|
|
43435
|
+
import { basename as basename9, dirname as dirname21, join as join36, relative as relative7, resolve as resolve12 } from "node:path";
|
|
43380
43436
|
function resourcePathOf(projectPath, absolute) {
|
|
43381
|
-
const inside =
|
|
43437
|
+
const inside = relative7(onDiskSpelling(projectPath), onDiskSpelling(absolute));
|
|
43382
43438
|
if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
|
|
43383
43439
|
return null;
|
|
43384
43440
|
}
|
|
@@ -43884,7 +43940,7 @@ import {
|
|
|
43884
43940
|
statSync as statSync17,
|
|
43885
43941
|
writeFileSync as writeFileSync19
|
|
43886
43942
|
} from "node:fs";
|
|
43887
|
-
import { basename as basename10, dirname as dirname22, extname as extname3, join as join37, relative as
|
|
43943
|
+
import { basename as basename10, dirname as dirname22, extname as extname3, join as join37, relative as relative8 } from "node:path";
|
|
43888
43944
|
function filesUnder(directory) {
|
|
43889
43945
|
return readdirSync17(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
43890
43946
|
const path = join37(directory, entry.name);
|
|
@@ -43945,7 +44001,7 @@ function binaryResources(projectPath) {
|
|
|
43945
44001
|
return found;
|
|
43946
44002
|
}
|
|
43947
44003
|
function targetPathOf(projectPath, absolute) {
|
|
43948
|
-
const inside =
|
|
44004
|
+
const inside = relative8(onDiskSpelling(projectPath), join37(onDiskSpelling(dirname22(absolute)), basename10(absolute)));
|
|
43949
44005
|
if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
|
|
43950
44006
|
return null;
|
|
43951
44007
|
}
|
|
@@ -47908,7 +47964,14 @@ function similarity(one, other) {
|
|
|
47908
47964
|
const shared = ones.filter((pair) => others.includes(pair)).length;
|
|
47909
47965
|
return 2 * shared / (ones.length + others.length);
|
|
47910
47966
|
}
|
|
47911
|
-
function withNearestFiles(said, wanted, files) {
|
|
47967
|
+
function withNearestFiles(said, wanted, files, beside) {
|
|
47968
|
+
const stem = (path) => (path.split("/").pop() ?? path).replace(/\.[^.]*$/, "");
|
|
47969
|
+
const named = said.split(/[\s,;]+/).map((word) => word.replace(/[.:]+$/, ""));
|
|
47970
|
+
const namesakes = beside.filter((file) => file !== wanted && stem(file) === stem(wanted) && !named.includes(file)).sort();
|
|
47971
|
+
if (namesakes.length > 0) {
|
|
47972
|
+
const alone = namesakes.length === 1;
|
|
47973
|
+
return `${said}. The ${alone ? "file" : "files"} of that name beside it ${alone ? "is" : "are"} ${namesakes.join(", ")}.`;
|
|
47974
|
+
}
|
|
47912
47975
|
const extension = /\.([^./]+)$/.exec(wanted)?.[1];
|
|
47913
47976
|
if (extension === undefined) {
|
|
47914
47977
|
return said;
|
|
@@ -47916,7 +47979,6 @@ function withNearestFiles(said, wanted, files) {
|
|
|
47916
47979
|
if (files.length === 0) {
|
|
47917
47980
|
return `${said}. The project holds no .${extension} file.`;
|
|
47918
47981
|
}
|
|
47919
|
-
const stem = (path) => (path.split("/").pop() ?? path).replace(/\.[^.]*$/, "");
|
|
47920
47982
|
const close = nearest(stem(wanted), files.map((file) => stem(file)));
|
|
47921
47983
|
const offered = files.filter((file) => close.includes(stem(file)));
|
|
47922
47984
|
if (offered.length > 0) {
|
|
@@ -48894,7 +48956,7 @@ var init_server_instructions = __esm(() => {
|
|
|
48894
48956
|
|
|
48895
48957
|
// src/suites-scene.ts
|
|
48896
48958
|
import { existsSync as existsSync32, mkdirSync as mkdirSync22, writeFileSync as writeFileSync21 } from "node:fs";
|
|
48897
|
-
import { join as join41, relative as
|
|
48959
|
+
import { join as join41, relative as relative9, sep as sep5 } from "node:path";
|
|
48898
48960
|
function hasSuitesScene(projectPath) {
|
|
48899
48961
|
return existsSync32(join41(projectPath, SUITES_SCENE));
|
|
48900
48962
|
}
|
|
@@ -48918,7 +48980,7 @@ function suitesSceneArgs(projectPath, suites) {
|
|
|
48918
48980
|
"-c",
|
|
48919
48981
|
"--ignoreHeadlessMode",
|
|
48920
48982
|
"-rd",
|
|
48921
|
-
`res://${
|
|
48983
|
+
`res://${relative9(projectPath, join41(directory, "reports")).split(sep5).join("/")}`,
|
|
48922
48984
|
"-rc",
|
|
48923
48985
|
"1"
|
|
48924
48986
|
];
|
|
@@ -48933,6 +48995,44 @@ var init_suites_scene = __esm(() => {
|
|
|
48933
48995
|
// src/test-selection.ts
|
|
48934
48996
|
import { existsSync as existsSync33 } from "node:fs";
|
|
48935
48997
|
import { basename as basename12, join as join42 } from "node:path";
|
|
48998
|
+
function casesOutOfPath(projectPath, given, tests) {
|
|
48999
|
+
const entries = Array.isArray(given) ? given : [given];
|
|
49000
|
+
const cases = [];
|
|
49001
|
+
const whole = [];
|
|
49002
|
+
const path = entries.map((entry) => {
|
|
49003
|
+
const caseAt = typeof entry === "string" ? CASE_AFTER_SCRIPT.exec(entry.trim()) : null;
|
|
49004
|
+
const suite = caseAt === null ? null : resolveWithinProject(projectPath, caseAt[1] ?? "");
|
|
49005
|
+
if (caseAt === null || suite === null || !suite.ok) {
|
|
49006
|
+
if (typeof entry === "string") {
|
|
49007
|
+
whole.push(entry);
|
|
49008
|
+
}
|
|
49009
|
+
return entry;
|
|
49010
|
+
}
|
|
49011
|
+
cases.push(`res://${suite.relativePath}:${caseAt[2] ?? ""}`);
|
|
49012
|
+
return `res://${suite.relativePath}`;
|
|
49013
|
+
});
|
|
49014
|
+
if (cases.length === 0) {
|
|
49015
|
+
return { ok: true, path: given, tests };
|
|
49016
|
+
}
|
|
49017
|
+
if (whole.length > 0) {
|
|
49018
|
+
return {
|
|
49019
|
+
ok: false,
|
|
49020
|
+
reason: `path names ${cases.length === 1 ? "a case" : "cases"}, ${cases.join(", ")}, beside ${whole.join(", ")}, and a run with cases named runs only those: run the cases on their own, and the rest in a run of its own.`
|
|
49021
|
+
};
|
|
49022
|
+
}
|
|
49023
|
+
const named = tests === undefined || tests === null ? [] : Array.isArray(tests) ? tests : [tests];
|
|
49024
|
+
return { ok: true, path: [...new Set(path)], tests: [...named, ...cases] };
|
|
49025
|
+
}
|
|
49026
|
+
function caseInSuites(given) {
|
|
49027
|
+
const entries = Array.isArray(given) ? given : [given];
|
|
49028
|
+
const cased = entries.filter((entry) => typeof entry === "string" && CASE_AFTER_SCRIPT.test(entry.trim()));
|
|
49029
|
+
const first = cased[0];
|
|
49030
|
+
if (first === undefined) {
|
|
49031
|
+
return null;
|
|
49032
|
+
}
|
|
49033
|
+
const file = CASE_AFTER_SCRIPT.exec(first.trim())?.[1] ?? first;
|
|
49034
|
+
return `suites plays whole suites, and ${cased.join(", ")} ${cased.length === 1 ? "names" : "name"} one case: name the file, ${file}, to play every case in it, or run the case alone with project_test, whose path takes ${first}.`;
|
|
49035
|
+
}
|
|
48936
49036
|
function casesIn(scripts, suite) {
|
|
48937
49037
|
return scripts.suite(suite)?.cases ?? scripts.script(suite)?.funcs.filter((name) => name.startsWith("test_")) ?? [];
|
|
48938
49038
|
}
|
|
@@ -48994,9 +49094,12 @@ function selectTests(projectPath, asked, given) {
|
|
|
48994
49094
|
const ignore = declared.filter((one) => suites.includes(one.suite) && !chosen.has(`${one.suite}:${one.test}`)).map((one) => `${one.suite}:${one.test}`);
|
|
48995
49095
|
return { ok: true, value: { suites, ignore, selected } };
|
|
48996
49096
|
}
|
|
49097
|
+
var CASE_AFTER_SCRIPT;
|
|
48997
49098
|
var init_test_selection = __esm(() => {
|
|
49099
|
+
init_paths();
|
|
48998
49100
|
init_project_scan();
|
|
48999
49101
|
init_suite_plan();
|
|
49102
|
+
CASE_AFTER_SCRIPT = /^(.*\.gd):(\w+)$/;
|
|
49000
49103
|
});
|
|
49001
49104
|
|
|
49002
49105
|
// src/server.ts
|
|
@@ -49018,7 +49121,7 @@ import {
|
|
|
49018
49121
|
statSync as statSync20,
|
|
49019
49122
|
writeFileSync as writeFileSync22
|
|
49020
49123
|
} from "node:fs";
|
|
49021
|
-
import { basename as basename13, dirname as dirname25, extname as extname4, isAbsolute as isAbsolute8, join as join43, normalize as normalize2, relative as
|
|
49124
|
+
import { basename as basename13, dirname as dirname25, extname as extname4, isAbsolute as isAbsolute8, join as join43, normalize as normalize2, relative as relative10 } from "node:path";
|
|
49022
49125
|
import { setTimeout as delay7 } from "node:timers/promises";
|
|
49023
49126
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
49024
49127
|
function editorRestartTimeoutMs() {
|
|
@@ -49175,7 +49278,7 @@ function keptGameRequest(tool, op, args) {
|
|
|
49175
49278
|
}
|
|
49176
49279
|
}
|
|
49177
49280
|
function projectSpelling(projectPath, absolutePath) {
|
|
49178
|
-
return `res://${
|
|
49281
|
+
return `res://${relative10(realAsFarAsItExists(projectPath), realAsFarAsItExists(absolutePath)).replaceAll("\\", "/")}`;
|
|
49179
49282
|
}
|
|
49180
49283
|
function whichGame(args) {
|
|
49181
49284
|
return { projectPath: args["projectPath"], pid: args["pid"] };
|
|
@@ -50082,7 +50185,7 @@ function failedVerdict(report) {
|
|
|
50082
50185
|
const last = parts.pop();
|
|
50083
50186
|
return last === undefined ? "failures" : parts.length === 0 ? last : `${parts.join(", ")} and ${last}`;
|
|
50084
50187
|
}
|
|
50085
|
-
function timedOutVerdict(timeoutMs, silentForMs, repeating = null) {
|
|
50188
|
+
function timedOutVerdict(timeoutMs, silentForMs, repeating = null, printedAnything = true) {
|
|
50086
50189
|
const quiet = Math.round(silentForMs);
|
|
50087
50190
|
if (repeating !== null) {
|
|
50088
50191
|
return {
|
|
@@ -50090,6 +50193,12 @@ function timedOutVerdict(timeoutMs, silentForMs, repeating = null) {
|
|
|
50090
50193
|
hung: false
|
|
50091
50194
|
};
|
|
50092
50195
|
}
|
|
50196
|
+
if (!printedAnything && timeoutMs < STALLED_AFTER_MS) {
|
|
50197
|
+
return {
|
|
50198
|
+
verdict: `timed out after ${timeoutMs} ms before the engine printed anything, so it may still have been starting: timeoutMs counts milliseconds, and the default is ${TEST_TIMEOUT_MS}`,
|
|
50199
|
+
hung: false
|
|
50200
|
+
};
|
|
50201
|
+
}
|
|
50093
50202
|
return quiet >= Math.min(STALLED_AFTER_MS, timeoutMs / 2) ? {
|
|
50094
50203
|
verdict: `hung: killed after ${timeoutMs} ms, having printed nothing for the last ${quiet} ms`,
|
|
50095
50204
|
hung: true
|
|
@@ -51318,13 +51427,20 @@ class GodotServer {
|
|
|
51318
51427
|
return await this.handleRunTests(args);
|
|
51319
51428
|
case "scene_create":
|
|
51320
51429
|
return op === "create" ? await editorOrEngine("create_scene", "writes") : await editorOrEngine("save_scene", "writes");
|
|
51321
|
-
case "scene_tree":
|
|
51430
|
+
case "scene_tree": {
|
|
51431
|
+
const defaults = readBoolean(args, "includeDefaults") ?? false;
|
|
51432
|
+
const asked = readBoolean(args, "includeProperties");
|
|
51433
|
+
if (defaults && asked === false) {
|
|
51434
|
+
return this.createErrorResponse("includeDefaults lists the defaults among the properties, and includeProperties false lists none: leave includeProperties out, or set it true.");
|
|
51435
|
+
}
|
|
51436
|
+
const properties = asked ?? defaults;
|
|
51322
51437
|
return await this.readFromFile(args, "scenePath", "read_scene_tree", (file) => ({
|
|
51323
51438
|
scenePath: file,
|
|
51324
51439
|
depth: readNumber(args, "depth") ?? -1,
|
|
51325
|
-
includeProperties:
|
|
51326
|
-
includeDefaults:
|
|
51327
|
-
}), async () => await bridge("list_scene_nodes"));
|
|
51440
|
+
includeProperties: properties,
|
|
51441
|
+
includeDefaults: defaults
|
|
51442
|
+
}), async () => await bridge("list_scene_nodes", { includeProperties: properties }));
|
|
51443
|
+
}
|
|
51328
51444
|
case "scene_node":
|
|
51329
51445
|
switch (op) {
|
|
51330
51446
|
case "add":
|
|
@@ -51546,7 +51662,7 @@ class GodotServer {
|
|
|
51546
51662
|
}, { tool: "runtime_input", op });
|
|
51547
51663
|
}
|
|
51548
51664
|
{
|
|
51549
|
-
const holding = op === "mouse_drag" ? (readPositiveNumber(args, "frames") ?? 1) + 2 : readPositiveNumber(args, "holdFrames");
|
|
51665
|
+
const holding = op === "mouse_drag" || op === "touch_drag" ? (readPositiveNumber(args, "frames") ?? 1) + 2 : readPositiveNumber(args, "holdFrames");
|
|
51550
51666
|
return await this.handleRuntimeCommand(`inject_${op}`, args, {
|
|
51551
51667
|
tool: "runtime_input",
|
|
51552
51668
|
op,
|
|
@@ -52481,6 +52597,9 @@ class GodotServer {
|
|
|
52481
52597
|
if (!project.ok) {
|
|
52482
52598
|
return project.response;
|
|
52483
52599
|
}
|
|
52600
|
+
if (/[\r\n]/.test(readString(args, "query") ?? "")) {
|
|
52601
|
+
return this.createErrorResponse("query holds a line break, and project_search matches one line at a time: search for one of its lines.");
|
|
52602
|
+
}
|
|
52484
52603
|
const given = readNonEmptyString(args, "path");
|
|
52485
52604
|
const place = given === undefined ? null : resolveWithinProject(project.value.path, given, { root: true });
|
|
52486
52605
|
if (place !== null && !place.ok) {
|
|
@@ -52730,10 +52849,18 @@ ${platform}`;
|
|
|
52730
52849
|
ticker.unref();
|
|
52731
52850
|
return ticker;
|
|
52732
52851
|
}
|
|
52733
|
-
async handleRunTests(
|
|
52852
|
+
async handleRunTests(given) {
|
|
52853
|
+
const project = this.project(given);
|
|
52854
|
+
if (!project.ok) {
|
|
52855
|
+
return project.response;
|
|
52856
|
+
}
|
|
52857
|
+
const split = casesOutOfPath(project.value.path, given["path"], given["tests"]);
|
|
52858
|
+
if (!split.ok) {
|
|
52859
|
+
return this.createErrorResponse(split.reason);
|
|
52860
|
+
}
|
|
52861
|
+
const args = split.path === given["path"] ? given : { ...given, path: split.path, tests: split.tests };
|
|
52734
52862
|
const ran = await this.runTests(args);
|
|
52735
|
-
|
|
52736
|
-
if (!project.ok || callSignal()?.aborted === true) {
|
|
52863
|
+
if (callSignal()?.aborted === true) {
|
|
52737
52864
|
return ran;
|
|
52738
52865
|
}
|
|
52739
52866
|
const paths = testPathsIn(project.value.path, args["path"]);
|
|
@@ -52892,7 +53019,7 @@ ${platform}`;
|
|
|
52892
53019
|
"--expected-out",
|
|
52893
53020
|
expectedFile
|
|
52894
53021
|
];
|
|
52895
|
-
const timeoutMs = readPositiveNumber(args, "timeoutMs") ??
|
|
53022
|
+
const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? TEST_TIMEOUT_MS;
|
|
52896
53023
|
this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
52897
53024
|
const helperFiles = {
|
|
52898
53025
|
spec: join43(userData, "spec"),
|
|
@@ -52905,12 +53032,15 @@ ${platform}`;
|
|
|
52905
53032
|
const run = await this.spawnGame(engine.value, cmdArgs, userDataIn(userData), onDesktop ? { directory: project.value.path, files: helperFiles } : undefined);
|
|
52906
53033
|
const console2 = [];
|
|
52907
53034
|
let lastSaid = Date.now();
|
|
53035
|
+
let saidAnything = false;
|
|
52908
53036
|
run.process.stdout?.on("data", (data) => {
|
|
52909
53037
|
console2.push(data);
|
|
52910
53038
|
lastSaid = Date.now();
|
|
53039
|
+
saidAnything = true;
|
|
52911
53040
|
});
|
|
52912
53041
|
run.process.stderr?.on("data", () => {
|
|
52913
53042
|
lastSaid = Date.now();
|
|
53043
|
+
saidAnything = true;
|
|
52914
53044
|
});
|
|
52915
53045
|
const reporting = this.reportTestProgress(run.log);
|
|
52916
53046
|
const autoloaded = scriptsAutoloaded(project.value.path);
|
|
@@ -53003,7 +53133,7 @@ ${platform}`;
|
|
|
53003
53133
|
}).entries;
|
|
53004
53134
|
const said = printed.map((entry) => entry.text);
|
|
53005
53135
|
const nothingRan = timedOut || notCompiled !== null ? null : whyNoReport(said, asked.join(" and "));
|
|
53006
|
-
const cutShort = timedOut && notCompiled === null ? timedOutVerdict(timeoutMs, silentForMs, repeatingScriptError(run.log.all, (entry) => run.log.raisedByScript(entry))) : null;
|
|
53136
|
+
const cutShort = timedOut && notCompiled === null ? timedOutVerdict(timeoutMs, silentForMs, repeatingScriptError(run.log.all, (entry) => run.log.raisedByScript(entry)), saidAnything) : null;
|
|
53007
53137
|
const hung = cutShort?.hung ?? false;
|
|
53008
53138
|
const printedErrors = notCompiled !== null ? compileErrorsIn(run.log.all) : timedOut || exitCode !== 105 ? [] : scriptErrorsPrinted(said);
|
|
53009
53139
|
const declaring = printedErrors.some((error) => unresolvedClassIn(error.message) !== null) ? await originsOf(printedErrors, declaredClasses(project.value.path), checkWithEngine(engine.value, project.value.path)) : { origins: [], unconfirmed: [] };
|
|
@@ -53065,7 +53195,8 @@ ${platform}`;
|
|
|
53065
53195
|
...printedLines,
|
|
53066
53196
|
savesNote,
|
|
53067
53197
|
...scanned,
|
|
53068
|
-
...windowSaid
|
|
53198
|
+
...windowSaid,
|
|
53199
|
+
classes: withAddonClassesCounted(classes.payload, cachedClasses(project.value.path) ?? new Map)
|
|
53069
53200
|
}, null, 2)
|
|
53070
53201
|
}
|
|
53071
53202
|
],
|
|
@@ -53356,7 +53487,10 @@ ${error.text}`;
|
|
|
53356
53487
|
missingScript(projectPath, relativePath) {
|
|
53357
53488
|
const scripts = projectFiles(projectPath, [".gd"]).map((script) => `res://${script}`);
|
|
53358
53489
|
const wanted = `res://${relativePath}`;
|
|
53359
|
-
|
|
53490
|
+
const folder = dirname25(relativePath.replaceAll("\\", "/"));
|
|
53491
|
+
const inFolder = join43(projectPath, folder);
|
|
53492
|
+
const beside = existsSync34(inFolder) ? readdirSync19(inFolder, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => `res://${folder === "." ? "" : `${folder}/`}${entry.name}`) : [];
|
|
53493
|
+
return this.createErrorResponse(withNearestFiles(`Script file does not exist: ${wanted}`, wanted, scripts, beside));
|
|
53360
53494
|
}
|
|
53361
53495
|
async compileScripts(projectPath, paths) {
|
|
53362
53496
|
const payload = { checked: [], failed: [], scripts: [] };
|
|
@@ -54437,6 +54571,10 @@ ${error.text}`;
|
|
|
54437
54571
|
if (args["scene"] !== undefined || args["executable"] !== undefined) {
|
|
54438
54572
|
return refused("suites plays the suites in the tests addon's own scene, so it takes no scene and no executable: leave those out.");
|
|
54439
54573
|
}
|
|
54574
|
+
const cased = caseInSuites(args["suites"]);
|
|
54575
|
+
if (cased !== null) {
|
|
54576
|
+
return refused(cased);
|
|
54577
|
+
}
|
|
54440
54578
|
const paths = testPathsIn(projectPath, args["suites"], "suites");
|
|
54441
54579
|
if (!paths.ok) {
|
|
54442
54580
|
return refused(paths.reason, PATH_SOLUTIONS);
|
|
@@ -54626,6 +54764,9 @@ ${error.text}`;
|
|
|
54626
54764
|
let editorAskedAt = 0;
|
|
54627
54765
|
let editorSaysGoing = true;
|
|
54628
54766
|
let editorAsks = 0;
|
|
54767
|
+
let holding = null;
|
|
54768
|
+
let keeperAskedAt = 0;
|
|
54769
|
+
let heldHere = null;
|
|
54629
54770
|
const waitBegan = Date.now();
|
|
54630
54771
|
const runBegan = Math.min(this.activeProcess?.startedAt ?? waitBegan, waitBegan);
|
|
54631
54772
|
const lastSaid = () => editorSaysGoing ? "playing" : "not playing";
|
|
@@ -54638,7 +54779,19 @@ ${error.text}`;
|
|
|
54638
54779
|
}
|
|
54639
54780
|
const run = this.activeProcess;
|
|
54640
54781
|
if (run === null || !run.throughEditor) {
|
|
54641
|
-
|
|
54782
|
+
if (!stillRunning(run)) {
|
|
54783
|
+
return true;
|
|
54784
|
+
}
|
|
54785
|
+
const keeper = run?.keeper;
|
|
54786
|
+
if (keeper !== undefined && holding === null && Date.now() - keeperAskedAt >= RUN_POLL_MS) {
|
|
54787
|
+
keeperAskedAt = Date.now();
|
|
54788
|
+
holding = this.keptHold(keeper).then((held) => {
|
|
54789
|
+
heldHere = held;
|
|
54790
|
+
}).finally(() => {
|
|
54791
|
+
holding = null;
|
|
54792
|
+
});
|
|
54793
|
+
}
|
|
54794
|
+
return heldHere !== null;
|
|
54642
54795
|
}
|
|
54643
54796
|
if (asking === null && Date.now() - editorAskedAt >= RUN_POLL_MS) {
|
|
54644
54797
|
editorAskedAt = Date.now();
|
|
@@ -54674,7 +54827,7 @@ ${error.text}`;
|
|
|
54674
54827
|
addon: true,
|
|
54675
54828
|
budgetMs,
|
|
54676
54829
|
...budget.sizedToMs === undefined ? {} : { sizedToMs: budget.sizedToMs },
|
|
54677
|
-
heldAt: this.dapClient?.whereItStopped() ?? null,
|
|
54830
|
+
heldAt: going === null || going.throughEditor ? this.dapClient?.whereItStopped() ?? null : endpoint === null && running && going.keeper !== undefined ? await this.keptHold(going.keeper) : null,
|
|
54678
54831
|
running,
|
|
54679
54832
|
withArgs,
|
|
54680
54833
|
servesAProject: this.ownProject !== null,
|
|
@@ -56762,7 +56915,12 @@ ${error.text}`;
|
|
|
56762
56915
|
return contained.response;
|
|
56763
56916
|
}
|
|
56764
56917
|
const { op: _op, projectPath: _projectPath, ...params } = contained.value;
|
|
56765
|
-
const
|
|
56918
|
+
const stale = await this.refreshStaleClasses(project.value.path);
|
|
56919
|
+
if (!stale.ok) {
|
|
56920
|
+
return stale.response;
|
|
56921
|
+
}
|
|
56922
|
+
const loaded = await this.operation("create_script", params, project.value.path);
|
|
56923
|
+
const created = loaded.ok && stale.value.length > 0 ? { ...loaded, payload: { ...loaded.payload, classesRebuiltFirst: stale.value } } : loaded;
|
|
56766
56924
|
const declared = created.ok ? readString(created.payload, "class_name") ?? "" : "";
|
|
56767
56925
|
if (!created.ok || declared === "") {
|
|
56768
56926
|
return this.answer(created);
|
|
@@ -56799,8 +56957,26 @@ ${error.text}`;
|
|
|
56799
56957
|
});
|
|
56800
56958
|
}
|
|
56801
56959
|
async handleModifyScript(args) {
|
|
56802
|
-
const written = await this.headless("modify_script", args);
|
|
56803
56960
|
const project = this.project(args);
|
|
56961
|
+
const stale = project.ok ? await this.refreshStaleClasses(project.value.path) : null;
|
|
56962
|
+
if (stale !== null && !stale.ok) {
|
|
56963
|
+
return stale.response;
|
|
56964
|
+
}
|
|
56965
|
+
const loaded = await this.headless("modify_script", args);
|
|
56966
|
+
const first = loaded.content[0];
|
|
56967
|
+
const written = stale === null || stale.value.length === 0 || loaded.isError === true || first?.type !== "text" || !first.text.trimStart().startsWith("{") ? loaded : {
|
|
56968
|
+
...loaded,
|
|
56969
|
+
content: [
|
|
56970
|
+
{
|
|
56971
|
+
...first,
|
|
56972
|
+
text: answerJson({
|
|
56973
|
+
...asParams(JSON.parse(first.text)),
|
|
56974
|
+
classesRebuiltFirst: stale.value
|
|
56975
|
+
})
|
|
56976
|
+
},
|
|
56977
|
+
...loaded.content.slice(1)
|
|
56978
|
+
]
|
|
56979
|
+
};
|
|
56804
56980
|
const scriptPath = readNonEmptyString(args, "scriptPath");
|
|
56805
56981
|
if (written.isError === true || !project.ok || scriptPath === undefined || !this.editorHasOpen(project.value.path)) {
|
|
56806
56982
|
return written;
|
|
@@ -57777,7 +57953,7 @@ async function runGodotServer() {
|
|
|
57777
57953
|
const server = new GodotServer;
|
|
57778
57954
|
await server.run();
|
|
57779
57955
|
}
|
|
57780
|
-
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;
|
|
57956
|
+
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_TIMEOUT_MS = 600000, 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;
|
|
57781
57957
|
var init_server = __esm(() => {
|
|
57782
57958
|
init_dist2();
|
|
57783
57959
|
init_stdio();
|
|
@@ -58781,7 +58957,7 @@ import {
|
|
|
58781
58957
|
writeFileSync as writeFileSync12
|
|
58782
58958
|
} from "node:fs";
|
|
58783
58959
|
import { availableParallelism as availableParallelism4 } from "node:os";
|
|
58784
|
-
import { dirname as dirname13, join as join27, relative as
|
|
58960
|
+
import { dirname as dirname13, join as join27, relative as relative5, resolve as resolve10, sep as sep4 } from "node:path";
|
|
58785
58961
|
import process14 from "node:process";
|
|
58786
58962
|
|
|
58787
58963
|
// src/suite-fingerprint.ts
|
|
@@ -58946,6 +59122,7 @@ function getLocalVersion() {
|
|
|
58946
59122
|
}
|
|
58947
59123
|
|
|
58948
59124
|
// src/suite-run.ts
|
|
59125
|
+
var LAST_RUN_FILE = "last-run.json";
|
|
58949
59126
|
var SLOWEST_NAMED = 5;
|
|
58950
59127
|
var SILENCE_TIMES_TAKEN = 4;
|
|
58951
59128
|
function silenceWhile(running, times, floorSeconds) {
|
|
@@ -59116,14 +59293,14 @@ class RunLogs {
|
|
|
59116
59293
|
return this.kept ?? this.directory;
|
|
59117
59294
|
}
|
|
59118
59295
|
named(path) {
|
|
59119
|
-
return this.kept === null ? path : join27(this.kept,
|
|
59296
|
+
return this.kept === null ? path : join27(this.kept, relative5(this.directory, path));
|
|
59120
59297
|
}
|
|
59121
59298
|
keep() {
|
|
59122
59299
|
if (this.kept === null) {
|
|
59123
59300
|
return;
|
|
59124
59301
|
}
|
|
59125
59302
|
for (const file of readdirSync14(this.directory, { recursive: true, encoding: "utf8" })) {
|
|
59126
|
-
if (file.endsWith(".log") && file.split(
|
|
59303
|
+
if (file.endsWith(".log") && file.split(sep4).length === 2) {
|
|
59127
59304
|
mkdirSync10(dirname13(join27(this.kept, file)), { recursive: true });
|
|
59128
59305
|
copyFileSync(join27(this.directory, file), join27(this.kept, file));
|
|
59129
59306
|
}
|
|
@@ -59141,6 +59318,7 @@ async function runTestCommand(options, godotPath) {
|
|
|
59141
59318
|
const state = join27(home, ".godot", "gdharness");
|
|
59142
59319
|
const timesFile = join27(state, "suite-times.json");
|
|
59143
59320
|
const passedFile = join27(state, "suite-passed.json");
|
|
59321
|
+
const lastRunFile = join27(state, LAST_RUN_FILE);
|
|
59144
59322
|
let suites = selectedSuites(scripts, options);
|
|
59145
59323
|
const uncollected = uncollectedSaid(scripts.uncollectedUnder(searchedRoots(options)));
|
|
59146
59324
|
const wholeSuites = suites.map((suite) => scripts.suite(suite.path) ?? suite);
|
|
@@ -59188,7 +59366,7 @@ async function runTestCommand(options, godotPath) {
|
|
|
59188
59366
|
const imported = await importProject(launch);
|
|
59189
59367
|
if (imported !== null) {
|
|
59190
59368
|
logs.keep();
|
|
59191
|
-
writeEarlyResult(options.resultFile, startedAt, [`the import failed: ${imported}`], logs.shown);
|
|
59369
|
+
writeEarlyResult({ resultFile: options.resultFile, lastRunFile, engines: options.engines }, startedAt, [`the import failed: ${imported}`], logs.shown);
|
|
59192
59370
|
console.log(`The import failed, so no suite ran: ${imported}`);
|
|
59193
59371
|
return 1;
|
|
59194
59372
|
}
|
|
@@ -59213,7 +59391,7 @@ ${existsSync23(path) ? readFileSync22(path, "latin1") : "missing"}`)
|
|
|
59213
59391
|
if (suites.length === 0) {
|
|
59214
59392
|
console.log("Nothing has changed since every suite last passed.");
|
|
59215
59393
|
rmSync12(runDirectory, { recursive: true, force: true });
|
|
59216
|
-
writeEarlyResult(options.resultFile, startedAt, uncollected, null);
|
|
59394
|
+
writeEarlyResult({ resultFile: options.resultFile, lastRunFile, engines: options.engines }, startedAt, uncollected, null);
|
|
59217
59395
|
if (uncollected.length === 0) {
|
|
59218
59396
|
return 0;
|
|
59219
59397
|
}
|
|
@@ -59239,26 +59417,31 @@ ${existsSync23(path) ? readFileSync22(path, "latin1") : "missing"}`)
|
|
|
59239
59417
|
const functions = suites.reduce((sum, suite) => sum + suite.cases.length, 0);
|
|
59240
59418
|
const asked = unitsOf(suites, times, splitPastFor(suites, times, options.engines));
|
|
59241
59419
|
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)}`;
|
|
59242
|
-
|
|
59420
|
+
const dealing = [];
|
|
59421
|
+
const say = (line) => {
|
|
59422
|
+
dealing.push(line);
|
|
59423
|
+
console.log(line);
|
|
59424
|
+
};
|
|
59425
|
+
say(`${plural(functions, "test function")} in ${plural(suites.length, "suite")} over ${plural(plans.length, "engine")}${how}.`);
|
|
59243
59426
|
const windowedAnyway = headlessInAWindow(plans, units);
|
|
59244
59427
|
if (windowedAnyway.length > 0) {
|
|
59245
59428
|
const one = windowedAnyway.length === 1;
|
|
59246
|
-
|
|
59429
|
+
say(`${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.`);
|
|
59247
59430
|
}
|
|
59248
59431
|
const split = [...new Set(units.map((unit) => unit.suite))].filter((path) => units.filter((unit) => unit.suite === path && unit.row === undefined).length > 1);
|
|
59249
59432
|
if (plans.length > 1 && split.length > 0) {
|
|
59250
|
-
|
|
59433
|
+
say(`Dealt out case by case, since ${split.length === 1 ? "it" : "each"} took more than ${past} last time: ${named(split)}.`);
|
|
59251
59434
|
}
|
|
59252
59435
|
const byRows = [
|
|
59253
59436
|
...new Set(units.flatMap((unit) => unit.row === undefined ? [] : [`${unit.suite} > ${unit.cases[0] ?? ""}`]))
|
|
59254
59437
|
];
|
|
59255
59438
|
if (plans.length > 1 && byRows.length > 0) {
|
|
59256
|
-
|
|
59439
|
+
say(`Dealt out row by row, since ${byRows.length === 1 ? "it" : "each"} took more than ${past} last time: ${named(byRows)}.`);
|
|
59257
59440
|
}
|
|
59258
59441
|
const keptWhole = units.flatMap((unit) => unit.cost > splitPast && !unit.windowed && suites.some((s) => s.path === unit.suite && s.together) ? [unit.suite] : []);
|
|
59259
59442
|
if (options.order === null && room.engines > 1 && keptWhole.length > 0) {
|
|
59260
59443
|
const one = keptWhole.length === 1;
|
|
59261
|
-
|
|
59444
|
+
say(`Kept whole in one engine, since ${one ? "it declares" : "each declares"} GDHARNESS_TOGETHER: ${named(keptWhole)}.`);
|
|
59262
59445
|
}
|
|
59263
59446
|
const lanes = [...new Set(plans.map((plan) => plan.lane))].map((lane) => plans.flatMap((plan, index) => plan.lane === lane ? [{ plan, index }] : []));
|
|
59264
59447
|
const taken = [];
|
|
@@ -59271,11 +59454,11 @@ ${existsSync23(path) ? readFileSync22(path, "latin1") : "missing"}`)
|
|
|
59271
59454
|
}
|
|
59272
59455
|
const shared = options.order === null && lanes.length > 1 && taken.length < lanes.length;
|
|
59273
59456
|
if (shared) {
|
|
59274
|
-
|
|
59457
|
+
say(sharesSaid(taken.length, lanes.length));
|
|
59275
59458
|
}
|
|
59276
59459
|
let ran = shared ? await runShares(launch, units, declared, lanes.length, taken, times) : (await Promise.all(lanes.map((lane, at) => runLane(launch, lane, times, taken[at] ?? null)))).flat().sort((a, b) => a.index - b.index);
|
|
59277
59460
|
if (ran.some((engine) => engine.verdict.starved) && ran.some((engine) => !engine.verdict.passed)) {
|
|
59278
|
-
|
|
59461
|
+
say("An engine ran out of memory, so every engine that failed runs again, one at a time.");
|
|
59279
59462
|
const again = [];
|
|
59280
59463
|
for (const engine of ran) {
|
|
59281
59464
|
again.push(engine.verdict.passed ? engine : await runEngine2(launch, engine.plan, engine.index, times, "again"));
|
|
@@ -59285,7 +59468,13 @@ ${existsSync23(path) ? readFileSync22(path, "latin1") : "missing"}`)
|
|
|
59285
59468
|
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}`))))));
|
|
59286
59469
|
const across = rowsAcross(ran.map((engine) => ({ handed: engine.plan.suites, printed: engine.printed })));
|
|
59287
59470
|
recordPassed(passedFile, ran, printsOf, new Set(across.map((fault) => fault.suite)));
|
|
59288
|
-
return report(ran, across.map((fault) => fault.fault), uncollected, startedAt, logs, options.printOutput,
|
|
59471
|
+
return report(ran, across.map((fault) => fault.fault), uncollected, startedAt, logs, options.printOutput, {
|
|
59472
|
+
resultFile: options.resultFile,
|
|
59473
|
+
lastRunFile,
|
|
59474
|
+
dealing,
|
|
59475
|
+
engines: options.engines,
|
|
59476
|
+
slotsFree: taken.length
|
|
59477
|
+
});
|
|
59289
59478
|
} finally {
|
|
59290
59479
|
process14.off("SIGINT", interrupted);
|
|
59291
59480
|
process14.off("SIGTERM", interrupted);
|
|
@@ -59450,7 +59639,7 @@ async function runEngine2(launch, plan, index, times, again = "", slot = null) {
|
|
|
59450
59639
|
...plan.windowed ? [] : ["--ignoreHeadlessMode"],
|
|
59451
59640
|
"-c",
|
|
59452
59641
|
"-rd",
|
|
59453
|
-
`res://${
|
|
59642
|
+
`res://${relative5(launch.projectPath, join27(directory, "reports")).split(sep4).join("/")}`,
|
|
59454
59643
|
"-rc",
|
|
59455
59644
|
"1",
|
|
59456
59645
|
"--",
|
|
@@ -59492,6 +59681,7 @@ async function runEngine2(launch, plan, index, times, again = "", slot = null) {
|
|
|
59492
59681
|
printed,
|
|
59493
59682
|
verdict: written === null ? judged : { ...judged, passed: false, faults: [...judged.faults, written] },
|
|
59494
59683
|
log,
|
|
59684
|
+
began,
|
|
59495
59685
|
seconds: (Date.now() - began) / 1000,
|
|
59496
59686
|
again: again !== ""
|
|
59497
59687
|
};
|
|
@@ -59502,11 +59692,11 @@ function reportsFault(reports) {
|
|
|
59502
59692
|
if (files.length === 0) {
|
|
59503
59693
|
return null;
|
|
59504
59694
|
}
|
|
59505
|
-
const shown = files.slice(0, REPORT_FILES_NAMED).map((file) => file.split(
|
|
59695
|
+
const shown = files.slice(0, REPORT_FILES_NAMED).map((file) => file.split(sep4).join("/"));
|
|
59506
59696
|
const more = files.length - shown.length;
|
|
59507
59697
|
return `gdUnit4 wrote ${plural(files.length, "file")} into ${reports} (${shown.join(", ")}${more > 0 ? ` and ${String(more)} more` : ""}), a report the runner had turned off, so the gdUnit4 installed no longer honours a session hook marked off; report it to gdharness with gdUnit4's version`;
|
|
59508
59698
|
}
|
|
59509
|
-
function report(ran, across, uncollected, startedAt, logs, printOutput,
|
|
59699
|
+
function report(ran, across, uncollected, startedAt, logs, printOutput, targets) {
|
|
59510
59700
|
if (printOutput) {
|
|
59511
59701
|
for (const engine of ran) {
|
|
59512
59702
|
console.log("");
|
|
@@ -59583,27 +59773,31 @@ ${readFileSync22(engine.log, "utf8")}`);
|
|
|
59583
59773
|
} else {
|
|
59584
59774
|
logs.keep();
|
|
59585
59775
|
}
|
|
59586
|
-
|
|
59587
|
-
|
|
59588
|
-
|
|
59589
|
-
|
|
59590
|
-
|
|
59591
|
-
|
|
59592
|
-
|
|
59593
|
-
|
|
59594
|
-
|
|
59595
|
-
|
|
59596
|
-
|
|
59597
|
-
|
|
59598
|
-
|
|
59599
|
-
|
|
59600
|
-
|
|
59601
|
-
|
|
59602
|
-
|
|
59603
|
-
|
|
59604
|
-
|
|
59605
|
-
})
|
|
59606
|
-
|
|
59776
|
+
writeResult(targets, {
|
|
59777
|
+
passed,
|
|
59778
|
+
seconds: took,
|
|
59779
|
+
problems: [
|
|
59780
|
+
...failed.flatMap((engine) => engine.verdict.faults.map((fault) => `engine ${String(engine.index)}: ${fault}`)),
|
|
59781
|
+
...across,
|
|
59782
|
+
...uncollected
|
|
59783
|
+
],
|
|
59784
|
+
suites: closed,
|
|
59785
|
+
engines: ran.map((engine) => ({
|
|
59786
|
+
index: engine.index,
|
|
59787
|
+
lane: engine.plan.lane,
|
|
59788
|
+
startedAfter: (engine.began - startedAt) / 1000,
|
|
59789
|
+
seconds: engine.seconds,
|
|
59790
|
+
dealt: engine.plan.cost,
|
|
59791
|
+
passed: engine.verdict.passed,
|
|
59792
|
+
windowed: engine.plan.windowed,
|
|
59793
|
+
suites: engine.plan.suites.map((suite) => suite.path),
|
|
59794
|
+
log: passed ? null : logs.named(engine.log)
|
|
59795
|
+
})),
|
|
59796
|
+
logs: passed ? null : logs.shown,
|
|
59797
|
+
enginesAsked: targets.engines,
|
|
59798
|
+
slotsFree: targets.slotsFree,
|
|
59799
|
+
dealing: targets.dealing
|
|
59800
|
+
});
|
|
59607
59801
|
if (passed) {
|
|
59608
59802
|
console.log(`Passed: ${totals}`);
|
|
59609
59803
|
return 0;
|
|
@@ -59613,16 +59807,23 @@ ${readFileSync22(engine.log, "utf8")}`);
|
|
|
59613
59807
|
console.log(`Every engine's output is kept under ${logs.shown} until ${String(KEPT_RUN_DIRECTORIES)} later runs have failed.`);
|
|
59614
59808
|
return 1;
|
|
59615
59809
|
}
|
|
59616
|
-
function writeEarlyResult(
|
|
59617
|
-
|
|
59618
|
-
|
|
59619
|
-
|
|
59620
|
-
|
|
59621
|
-
|
|
59622
|
-
|
|
59623
|
-
|
|
59624
|
-
|
|
59625
|
-
|
|
59810
|
+
function writeEarlyResult(targets, startedAt, problems, logs) {
|
|
59811
|
+
writeResult(targets, {
|
|
59812
|
+
passed: problems.length === 0,
|
|
59813
|
+
seconds: (Date.now() - startedAt) / 1000,
|
|
59814
|
+
problems,
|
|
59815
|
+
suites: [],
|
|
59816
|
+
engines: [],
|
|
59817
|
+
logs,
|
|
59818
|
+
enginesAsked: targets.engines,
|
|
59819
|
+
slotsFree: 0,
|
|
59820
|
+
dealing: []
|
|
59821
|
+
});
|
|
59822
|
+
}
|
|
59823
|
+
function writeResult(targets, result) {
|
|
59824
|
+
writeJson(targets.lastRunFile, result);
|
|
59825
|
+
if (targets.resultFile !== null) {
|
|
59826
|
+
writeJson(targets.resultFile, result);
|
|
59626
59827
|
}
|
|
59627
59828
|
}
|
|
59628
59829
|
function printUncollected(uncollected) {
|