gdharness 1.3.39 → 1.3.41
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 +315 -112
- package/build/godot/addons/gdharness_editor/animation_reads.gd +7 -6
- package/build/godot/addons/gdharness_editor/file_reads.gd +2 -1
- package/build/godot/addons/gdharness_editor/file_walk.gd +33 -1
- package/build/godot/addons/gdharness_editor/property_values.gd +6 -9
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +6 -3
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +1 -1
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +11 -10
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +77 -22
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +5 -3
- package/build/godot/addons/gdharness_runtime/runtime_typing.gd +15 -8
- package/build/godot/operations/animation_reads.gd +7 -6
- package/build/godot/operations/dependencies.gd +5 -2
- package/build/godot/operations/file_reads.gd +2 -1
- package/build/godot/operations/file_walk.gd +33 -1
- package/build/godot/operations/gdscript_authoring.gd +10 -1
- package/build/godot/operations/import_pipeline.gd +1 -1
- package/build/godot/operations/plugins.gd +39 -1
- package/build/godot/operations/project_config.gd +11 -1
- package/build/godot/operations/property_values.gd +6 -9
- package/build/keeper.js +38 -5
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -852,7 +852,18 @@ public static class GdharnessDesktop {
|
|
|
852
852
|
}
|
|
853
853
|
|
|
854
854
|
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
855
|
-
static extern IntPtr
|
|
855
|
+
static extern IntPtr CreateDesktopExW(string name, IntPtr device, IntPtr mode, uint flags, uint access, IntPtr attributes, uint heapKilobytes, IntPtr reserved);
|
|
856
|
+
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
857
|
+
static extern IntPtr CreateWindowStationW(string name, uint flags, uint access, IntPtr attributes);
|
|
858
|
+
[DllImport("user32.dll", SetLastError = true)] static extern IntPtr GetProcessWindowStation();
|
|
859
|
+
[DllImport("user32.dll", SetLastError = true)] static extern bool SetProcessWindowStation(IntPtr station);
|
|
860
|
+
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
861
|
+
static extern bool GetUserObjectInformationW(IntPtr handle, int index, StringBuilder into, int size, out int needed);
|
|
862
|
+
const uint WindowStationAllAccess = 0x37F;
|
|
863
|
+
const int ObjectName = 2;
|
|
864
|
+
// What Windows gives an interactive desktop. A non-interactive station's desktops get a small
|
|
865
|
+
// fraction of that by default, and one desktop here holds every engine a run starts.
|
|
866
|
+
const uint DesktopHeapKilobytes = 20480;
|
|
856
867
|
|
|
857
868
|
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
858
869
|
static extern bool CreateProcessW(string application, StringBuilder commandLine, IntPtr processAttributes, IntPtr threadAttributes, bool inherit, uint flags, IntPtr environment, string directory, ref StartupInfo startup, out Started started);
|
|
@@ -943,12 +954,34 @@ public static class GdharnessDesktop {
|
|
|
943
954
|
StartupInfo startup = new StartupInfo();
|
|
944
955
|
startup.cb = Marshal.SizeOf(typeof(StartupInfo));
|
|
945
956
|
if (desktop.Length > 0) {
|
|
946
|
-
Step(stepFile, "
|
|
947
|
-
IntPtr
|
|
957
|
+
Step(stepFile, "opening the session's own window station");
|
|
958
|
+
IntPtr station = CreateWindowStationW(null, 0, WindowStationAllAccess, IntPtr.Zero);
|
|
959
|
+
if (station == IntPtr.Zero) {
|
|
960
|
+
throw new Win32Exception(Marshal.GetLastWin32Error(), "CreateWindowStationW");
|
|
961
|
+
}
|
|
962
|
+
StringBuilder stationName = new StringBuilder(256);
|
|
963
|
+
int needed;
|
|
964
|
+
if (!GetUserObjectInformationW(station, ObjectName, stationName, stationName.Capacity * 2, out needed)) {
|
|
965
|
+
throw new Win32Exception(Marshal.GetLastWin32Error(), "GetUserObjectInformationW");
|
|
966
|
+
}
|
|
967
|
+
// A desktop is made in the calling process's station, so this one is the caller's while it is.
|
|
968
|
+
IntPtr own = GetProcessWindowStation();
|
|
969
|
+
if (!SetProcessWindowStation(station)) {
|
|
970
|
+
throw new Win32Exception(Marshal.GetLastWin32Error(), "SetProcessWindowStation");
|
|
971
|
+
}
|
|
972
|
+
IntPtr held;
|
|
973
|
+
int made;
|
|
974
|
+
try {
|
|
975
|
+
Step(stepFile, "creating the " + desktop + " desktop");
|
|
976
|
+
held = CreateDesktopExW(desktop, IntPtr.Zero, IntPtr.Zero, 0, GenericAll, IntPtr.Zero, DesktopHeapKilobytes, IntPtr.Zero);
|
|
977
|
+
made = Marshal.GetLastWin32Error();
|
|
978
|
+
} finally {
|
|
979
|
+
SetProcessWindowStation(own);
|
|
980
|
+
}
|
|
948
981
|
if (held == IntPtr.Zero) {
|
|
949
|
-
throw new Win32Exception(
|
|
982
|
+
throw new Win32Exception(made, "CreateDesktopExW " + stationName + "\\\\" + desktop);
|
|
950
983
|
}
|
|
951
|
-
startup.desktop = desktop;
|
|
984
|
+
startup.desktop = stationName + "\\\\" + desktop;
|
|
952
985
|
}
|
|
953
986
|
startup.flags = UseStdHandles;
|
|
954
987
|
if (noActivate) {
|
|
@@ -12877,7 +12910,7 @@ var init_doc_cache = __esm(() => {
|
|
|
12877
12910
|
|
|
12878
12911
|
// src/project-scan.ts
|
|
12879
12912
|
import { existsSync as existsSync9, readdirSync as readdirSync6, readFileSync as readFileSync8, statSync as statSync9 } from "node:fs";
|
|
12880
|
-
import { basename as basename5, extname, join as join11 } from "node:path";
|
|
12913
|
+
import { basename as basename5, extname, join as join11, relative as relative4, sep as sep3 } from "node:path";
|
|
12881
12914
|
function skipped(name) {
|
|
12882
12915
|
return name.startsWith(".") || SKIPPED.has(name);
|
|
12883
12916
|
}
|
|
@@ -13037,6 +13070,20 @@ function pathsInUidCache(projectPath) {
|
|
|
13037
13070
|
}
|
|
13038
13071
|
return new Set(paths.values());
|
|
13039
13072
|
}
|
|
13073
|
+
function steppedOverAround(projectPath, place) {
|
|
13074
|
+
let at = projectPath;
|
|
13075
|
+
for (const step of relative4(projectPath, place).split(sep3).filter((part) => part !== "")) {
|
|
13076
|
+
at = join11(at, step);
|
|
13077
|
+
if (!statSync9(at).isDirectory()) {
|
|
13078
|
+
break;
|
|
13079
|
+
}
|
|
13080
|
+
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;
|
|
13081
|
+
if (why !== null) {
|
|
13082
|
+
return { folder: `res://${relative4(projectPath, at).split(sep3).join("/")}/`, why };
|
|
13083
|
+
}
|
|
13084
|
+
}
|
|
13085
|
+
return null;
|
|
13086
|
+
}
|
|
13040
13087
|
function isBinary(bytes) {
|
|
13041
13088
|
return bytes.subarray(0, 8000).includes(0);
|
|
13042
13089
|
}
|
|
@@ -13162,8 +13209,8 @@ function searchProject(projectPath, options) {
|
|
|
13162
13209
|
result.summary.files_with_matches += 1;
|
|
13163
13210
|
}
|
|
13164
13211
|
};
|
|
13165
|
-
const visit = (directory) => {
|
|
13166
|
-
if (steppedOver(directory)) {
|
|
13212
|
+
const visit = (directory, named = false) => {
|
|
13213
|
+
if (!named && steppedOver(directory)) {
|
|
13167
13214
|
if (gdignored(directory)) {
|
|
13168
13215
|
notSearched.push(`res://${directory.slice(projectPath.length + 1).replace(/\\/g, "/")}/`);
|
|
13169
13216
|
}
|
|
@@ -13186,20 +13233,17 @@ function searchProject(projectPath, options) {
|
|
|
13186
13233
|
};
|
|
13187
13234
|
const within = options.within ?? projectPath;
|
|
13188
13235
|
if (statSync9(within).isDirectory()) {
|
|
13189
|
-
visit(within);
|
|
13236
|
+
visit(within, true);
|
|
13190
13237
|
} else {
|
|
13191
13238
|
searchFile(within, basename5(within));
|
|
13192
13239
|
}
|
|
13193
13240
|
const answered = notSearched.length === 0 ? result : { ...result, not_searched: notSearched };
|
|
13194
|
-
|
|
13195
|
-
|
|
13196
|
-
|
|
13197
|
-
|
|
13198
|
-
|
|
13199
|
-
return {
|
|
13200
|
-
...answered,
|
|
13201
|
-
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}.`
|
|
13202
|
-
};
|
|
13241
|
+
const around = options.within === undefined ? null : steppedOverAround(projectPath, within);
|
|
13242
|
+
const notes = [
|
|
13243
|
+
around === null ? undefined : `${around.folder} is searched because path names it; a search of the whole project steps over it, since ${around.why}.`,
|
|
13244
|
+
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(", ")}.`
|
|
13245
|
+
].filter((note) => note !== undefined);
|
|
13246
|
+
return notes.length === 0 ? answered : { ...answered, note: notes.join(" ") };
|
|
13203
13247
|
}
|
|
13204
13248
|
var SKIPPED, VERSION_MARKER = ".gdharness-version", SCENE_EXTENSIONS, SIDECAR_EXTENSIONS, ASSET_EXTENSIONS, CARRIES_A_UID, BINARY_EXTENSIONS;
|
|
13205
13249
|
var init_project_scan = __esm(() => {
|
|
@@ -16400,8 +16444,14 @@ var init_tool_definitions = __esm(() => {
|
|
|
16400
16444
|
type: "string",
|
|
16401
16445
|
description: "Variables: the initial value. Constants: the value, which a constant needs. As written in GDScript."
|
|
16402
16446
|
},
|
|
16403
|
-
isExport: {
|
|
16404
|
-
|
|
16447
|
+
isExport: {
|
|
16448
|
+
type: "boolean",
|
|
16449
|
+
description: "Variables: add @export. Default true when exportHint is given."
|
|
16450
|
+
},
|
|
16451
|
+
exportHint: {
|
|
16452
|
+
type: "string",
|
|
16453
|
+
description: 'Variables: the export hint, such as "range(0, 100)", written as @export_range(0, 100). Refused beside isExport false.'
|
|
16454
|
+
},
|
|
16405
16455
|
isOnready: { type: "boolean", description: "Variables: add @onready." },
|
|
16406
16456
|
position: {
|
|
16407
16457
|
type: "string",
|
|
@@ -16504,7 +16554,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16504
16554
|
{
|
|
16505
16555
|
name: "project_settings",
|
|
16506
16556
|
title: "Project settings",
|
|
16507
|
-
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. add_input_action on an action that exists, one of the engine's ui_ actions included, replaces its events, and its deadzone when one is given, and answers was_new false with what it held under 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, the way back from a set of a misspelt name, 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. 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 the engine registers no default for, such as an input action the project added, which only the project can have set.",
|
|
16557
|
+
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. add_input_action on an action that exists, one of the engine's ui_ actions included, replaces its events, and its deadzone when one is given, and answers was_new false with what it held under 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, the way back from a set of a misspelt name, 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 the engine registers no default for, such as an input action the project added, which only the project can have set.",
|
|
16508
16558
|
parameters: {
|
|
16509
16559
|
projectPath: PROJECT_PATH,
|
|
16510
16560
|
setting: {
|
|
@@ -16664,7 +16714,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16664
16714
|
name: "project_search",
|
|
16665
16715
|
title: "Search the project",
|
|
16666
16716
|
effect: "read",
|
|
16667
|
-
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.",
|
|
16717
|
+
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.",
|
|
16668
16718
|
parameters: {
|
|
16669
16719
|
projectPath: PROJECT_PATH,
|
|
16670
16720
|
query: { type: "string", description: "The text or pattern to find." },
|
|
@@ -16679,7 +16729,11 @@ var init_tool_definitions = __esm(() => {
|
|
|
16679
16729
|
},
|
|
16680
16730
|
regex: { type: "boolean", description: "Read the query as a regular expression. Default false." },
|
|
16681
16731
|
caseSensitive: { type: "boolean", description: "Default false." },
|
|
16682
|
-
maxResults: {
|
|
16732
|
+
maxResults: {
|
|
16733
|
+
type: "integer",
|
|
16734
|
+
minimum: 1,
|
|
16735
|
+
description: "How many matching lines to answer. Default 100."
|
|
16736
|
+
}
|
|
16683
16737
|
},
|
|
16684
16738
|
requires: ["projectPath", "query"]
|
|
16685
16739
|
},
|
|
@@ -16696,7 +16750,11 @@ var init_tool_definitions = __esm(() => {
|
|
|
16696
16750
|
enum: ["forward", "reverse"],
|
|
16697
16751
|
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."
|
|
16698
16752
|
},
|
|
16699
|
-
depth: {
|
|
16753
|
+
depth: {
|
|
16754
|
+
type: "integer",
|
|
16755
|
+
minimum: 1,
|
|
16756
|
+
description: "forward: how many levels to follow, 1 being what the file loads itself. Default unlimited."
|
|
16757
|
+
},
|
|
16700
16758
|
includeBuiltin: {
|
|
16701
16759
|
type: "boolean",
|
|
16702
16760
|
description: "forward: include the engine's own res://. resources. Default false."
|
|
@@ -16873,7 +16931,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16873
16931
|
path: {
|
|
16874
16932
|
type: ["string", "array"],
|
|
16875
16933
|
items: { type: "string" },
|
|
16876
|
-
description:
|
|
16934
|
+
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.'
|
|
16877
16935
|
},
|
|
16878
16936
|
ignore: {
|
|
16879
16937
|
type: "array",
|
|
@@ -16916,7 +16974,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16916
16974
|
rootNodeType: {
|
|
16917
16975
|
type: "string",
|
|
16918
16976
|
ops: ["create"],
|
|
16919
|
-
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."
|
|
16977
|
+
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."
|
|
16920
16978
|
},
|
|
16921
16979
|
rootNodeName: {
|
|
16922
16980
|
type: "string",
|
|
@@ -16952,8 +17010,9 @@ var init_tool_definitions = __esm(() => {
|
|
|
16952
17010
|
projectPath: PROJECT_PATH,
|
|
16953
17011
|
scenePath: SCENE_PATH,
|
|
16954
17012
|
depth: {
|
|
16955
|
-
type: "
|
|
16956
|
-
|
|
17013
|
+
type: "integer",
|
|
17014
|
+
minimum: 0,
|
|
17015
|
+
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."
|
|
16957
17016
|
},
|
|
16958
17017
|
includeProperties: {
|
|
16959
17018
|
type: "boolean",
|
|
@@ -16961,7 +17020,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16961
17020
|
},
|
|
16962
17021
|
includeDefaults: {
|
|
16963
17022
|
type: "boolean",
|
|
16964
|
-
description: "
|
|
17023
|
+
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."
|
|
16965
17024
|
}
|
|
16966
17025
|
},
|
|
16967
17026
|
requires: ["projectPath", "scenePath"]
|
|
@@ -16986,7 +17045,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16986
17045
|
nodeType: {
|
|
16987
17046
|
type: "string",
|
|
16988
17047
|
ops: ["add"],
|
|
16989
|
-
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
|
|
17048
|
+
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.`
|
|
16990
17049
|
},
|
|
16991
17050
|
instancePath: {
|
|
16992
17051
|
type: "string",
|
|
@@ -17084,7 +17143,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17084
17143
|
{
|
|
17085
17144
|
name: "scene_signal",
|
|
17086
17145
|
title: "Scene signals",
|
|
17087
|
-
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
|
|
17146
|
+
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}`,
|
|
17088
17147
|
parameters: {
|
|
17089
17148
|
projectPath: PROJECT_PATH,
|
|
17090
17149
|
scenePath: SCENE_PATH,
|
|
@@ -17152,7 +17211,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17152
17211
|
animationName: {
|
|
17153
17212
|
type: "string",
|
|
17154
17213
|
ops: ["read", "create", "add_track", "remove_track", "set", "rename", "remove", "add_state"],
|
|
17155
|
-
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."
|
|
17214
|
+
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."
|
|
17156
17215
|
},
|
|
17157
17216
|
newName: {
|
|
17158
17217
|
type: "string",
|
|
@@ -17582,7 +17641,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17582
17641
|
type: ["string", "array"],
|
|
17583
17642
|
items: { type: "string" },
|
|
17584
17643
|
ops: ["start"],
|
|
17585
|
-
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."
|
|
17644
|
+
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."
|
|
17586
17645
|
},
|
|
17587
17646
|
args: {
|
|
17588
17647
|
type: "array",
|
|
@@ -17604,7 +17663,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17604
17663
|
visible: {
|
|
17605
17664
|
type: "boolean",
|
|
17606
17665
|
ops: ["start"],
|
|
17607
|
-
description: "start: on Windows, show the window on the desktop in use. By default a windowed run this server starts goes on a desktop of its own, where it renders and runtime_capture works but the window never shows and never takes the keyboard. A game a visible editor plays opens on the desktop in use, and one a hidden editor (editor_launch hidden: true) plays goes on its desktop. Elsewhere than Windows the window shows either way. Default false."
|
|
17666
|
+
description: "start: on Windows, show the window on the desktop in use. By default a windowed run this server starts goes on a desktop of its own, where it renders and runtime_capture works but the window never shows and never takes the keyboard or the pointer. A game a visible editor plays opens on the desktop in use, and one a hidden editor (editor_launch hidden: true) plays goes on its desktop. Elsewhere than Windows the window shows either way. Default false."
|
|
17608
17667
|
},
|
|
17609
17668
|
silent: {
|
|
17610
17669
|
type: "boolean",
|
|
@@ -18386,7 +18445,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
18386
18445
|
{
|
|
18387
18446
|
name: "debug_breakpoint",
|
|
18388
18447
|
title: "Breakpoints",
|
|
18389
|
-
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.",
|
|
18448
|
+
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.",
|
|
18390
18449
|
parameters: {
|
|
18391
18450
|
projectPath: PROJECT_PATH,
|
|
18392
18451
|
scriptPath: { ...SCRIPT_PATH, ops: ["set", "remove"] },
|
|
@@ -41398,7 +41457,7 @@ var init_boot_note = __esm(() => {
|
|
|
41398
41457
|
|
|
41399
41458
|
// src/breakpoint-note.ts
|
|
41400
41459
|
import { mkdirSync as mkdirSync12, readFileSync as readFileSync26, rmSync as rmSync13, writeFileSync as writeFileSync14 } from "node:fs";
|
|
41401
|
-
import { dirname as dirname16, isAbsolute as isAbsolute7, join as join31, relative as
|
|
41460
|
+
import { dirname as dirname16, isAbsolute as isAbsolute7, join as join31, relative as relative6 } from "node:path";
|
|
41402
41461
|
function breakpointNotePath(projectPath) {
|
|
41403
41462
|
return join31(projectPath, ".godot", "gdharness-breakpoints.json");
|
|
41404
41463
|
}
|
|
@@ -41413,7 +41472,7 @@ function writeBreakpointNote(projectPath, held) {
|
|
|
41413
41472
|
}
|
|
41414
41473
|
const note = {
|
|
41415
41474
|
breakpoints: inside.map((one) => ({
|
|
41416
|
-
script:
|
|
41475
|
+
script: relative6(root, one.scriptPath).replaceAll("\\", "/"),
|
|
41417
41476
|
lines: [...one.lines]
|
|
41418
41477
|
}))
|
|
41419
41478
|
};
|
|
@@ -41450,7 +41509,7 @@ function readBreakpointNote(projectPath) {
|
|
|
41450
41509
|
}
|
|
41451
41510
|
}
|
|
41452
41511
|
function withinProject(projectPath, scriptPath) {
|
|
41453
|
-
const rel =
|
|
41512
|
+
const rel = relative6(projectPath, scriptPath);
|
|
41454
41513
|
return rel !== "" && !rel.startsWith("..") && !isAbsolute7(rel);
|
|
41455
41514
|
}
|
|
41456
41515
|
var init_breakpoint_note = __esm(() => {
|
|
@@ -43343,9 +43402,9 @@ var init_rename = __esm(() => {
|
|
|
43343
43402
|
|
|
43344
43403
|
// src/rename-symbol.ts
|
|
43345
43404
|
import { existsSync as existsSync30, mkdirSync as mkdirSync17, readdirSync as readdirSync16, realpathSync as realpathSync4, rmSync as rmSync16, writeFileSync as writeFileSync18 } from "node:fs";
|
|
43346
|
-
import { basename as basename9, dirname as dirname21, join as join36, relative as
|
|
43405
|
+
import { basename as basename9, dirname as dirname21, join as join36, relative as relative7, resolve as resolve12 } from "node:path";
|
|
43347
43406
|
function resourcePathOf(projectPath, absolute) {
|
|
43348
|
-
const inside =
|
|
43407
|
+
const inside = relative7(onDiskSpelling(projectPath), onDiskSpelling(absolute));
|
|
43349
43408
|
if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
|
|
43350
43409
|
return null;
|
|
43351
43410
|
}
|
|
@@ -43851,7 +43910,7 @@ import {
|
|
|
43851
43910
|
statSync as statSync17,
|
|
43852
43911
|
writeFileSync as writeFileSync19
|
|
43853
43912
|
} from "node:fs";
|
|
43854
|
-
import { basename as basename10, dirname as dirname22, extname as extname3, join as join37, relative as
|
|
43913
|
+
import { basename as basename10, dirname as dirname22, extname as extname3, join as join37, relative as relative8 } from "node:path";
|
|
43855
43914
|
function filesUnder(directory) {
|
|
43856
43915
|
return readdirSync17(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
43857
43916
|
const path = join37(directory, entry.name);
|
|
@@ -43912,7 +43971,7 @@ function binaryResources(projectPath) {
|
|
|
43912
43971
|
return found;
|
|
43913
43972
|
}
|
|
43914
43973
|
function targetPathOf(projectPath, absolute) {
|
|
43915
|
-
const inside =
|
|
43974
|
+
const inside = relative8(onDiskSpelling(projectPath), join37(onDiskSpelling(dirname22(absolute)), basename10(absolute)));
|
|
43916
43975
|
if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
|
|
43917
43976
|
return null;
|
|
43918
43977
|
}
|
|
@@ -47875,7 +47934,14 @@ function similarity(one, other) {
|
|
|
47875
47934
|
const shared = ones.filter((pair) => others.includes(pair)).length;
|
|
47876
47935
|
return 2 * shared / (ones.length + others.length);
|
|
47877
47936
|
}
|
|
47878
|
-
function withNearestFiles(said, wanted, files) {
|
|
47937
|
+
function withNearestFiles(said, wanted, files, beside) {
|
|
47938
|
+
const stem = (path) => (path.split("/").pop() ?? path).replace(/\.[^.]*$/, "");
|
|
47939
|
+
const named = said.split(/[\s,;]+/).map((word) => word.replace(/[.:]+$/, ""));
|
|
47940
|
+
const namesakes = beside.filter((file) => file !== wanted && stem(file) === stem(wanted) && !named.includes(file)).sort();
|
|
47941
|
+
if (namesakes.length > 0) {
|
|
47942
|
+
const alone = namesakes.length === 1;
|
|
47943
|
+
return `${said}. The ${alone ? "file" : "files"} of that name beside it ${alone ? "is" : "are"} ${namesakes.join(", ")}.`;
|
|
47944
|
+
}
|
|
47879
47945
|
const extension = /\.([^./]+)$/.exec(wanted)?.[1];
|
|
47880
47946
|
if (extension === undefined) {
|
|
47881
47947
|
return said;
|
|
@@ -47883,7 +47949,6 @@ function withNearestFiles(said, wanted, files) {
|
|
|
47883
47949
|
if (files.length === 0) {
|
|
47884
47950
|
return `${said}. The project holds no .${extension} file.`;
|
|
47885
47951
|
}
|
|
47886
|
-
const stem = (path) => (path.split("/").pop() ?? path).replace(/\.[^.]*$/, "");
|
|
47887
47952
|
const close = nearest(stem(wanted), files.map((file) => stem(file)));
|
|
47888
47953
|
const offered = files.filter((file) => close.includes(stem(file)));
|
|
47889
47954
|
if (offered.length > 0) {
|
|
@@ -48861,7 +48926,7 @@ var init_server_instructions = __esm(() => {
|
|
|
48861
48926
|
|
|
48862
48927
|
// src/suites-scene.ts
|
|
48863
48928
|
import { existsSync as existsSync32, mkdirSync as mkdirSync22, writeFileSync as writeFileSync21 } from "node:fs";
|
|
48864
|
-
import { join as join41, relative as
|
|
48929
|
+
import { join as join41, relative as relative9, sep as sep5 } from "node:path";
|
|
48865
48930
|
function hasSuitesScene(projectPath) {
|
|
48866
48931
|
return existsSync32(join41(projectPath, SUITES_SCENE));
|
|
48867
48932
|
}
|
|
@@ -48885,7 +48950,7 @@ function suitesSceneArgs(projectPath, suites) {
|
|
|
48885
48950
|
"-c",
|
|
48886
48951
|
"--ignoreHeadlessMode",
|
|
48887
48952
|
"-rd",
|
|
48888
|
-
`res://${
|
|
48953
|
+
`res://${relative9(projectPath, join41(directory, "reports")).split(sep5).join("/")}`,
|
|
48889
48954
|
"-rc",
|
|
48890
48955
|
"1"
|
|
48891
48956
|
];
|
|
@@ -48900,6 +48965,44 @@ var init_suites_scene = __esm(() => {
|
|
|
48900
48965
|
// src/test-selection.ts
|
|
48901
48966
|
import { existsSync as existsSync33 } from "node:fs";
|
|
48902
48967
|
import { basename as basename12, join as join42 } from "node:path";
|
|
48968
|
+
function casesOutOfPath(projectPath, given, tests) {
|
|
48969
|
+
const entries = Array.isArray(given) ? given : [given];
|
|
48970
|
+
const cases = [];
|
|
48971
|
+
const whole = [];
|
|
48972
|
+
const path = entries.map((entry) => {
|
|
48973
|
+
const caseAt = typeof entry === "string" ? CASE_AFTER_SCRIPT.exec(entry.trim()) : null;
|
|
48974
|
+
const suite = caseAt === null ? null : resolveWithinProject(projectPath, caseAt[1] ?? "");
|
|
48975
|
+
if (caseAt === null || suite === null || !suite.ok) {
|
|
48976
|
+
if (typeof entry === "string") {
|
|
48977
|
+
whole.push(entry);
|
|
48978
|
+
}
|
|
48979
|
+
return entry;
|
|
48980
|
+
}
|
|
48981
|
+
cases.push(`res://${suite.relativePath}:${caseAt[2] ?? ""}`);
|
|
48982
|
+
return `res://${suite.relativePath}`;
|
|
48983
|
+
});
|
|
48984
|
+
if (cases.length === 0) {
|
|
48985
|
+
return { ok: true, path: given, tests };
|
|
48986
|
+
}
|
|
48987
|
+
if (whole.length > 0) {
|
|
48988
|
+
return {
|
|
48989
|
+
ok: false,
|
|
48990
|
+
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.`
|
|
48991
|
+
};
|
|
48992
|
+
}
|
|
48993
|
+
const named = tests === undefined || tests === null ? [] : Array.isArray(tests) ? tests : [tests];
|
|
48994
|
+
return { ok: true, path: [...new Set(path)], tests: [...named, ...cases] };
|
|
48995
|
+
}
|
|
48996
|
+
function caseInSuites(given) {
|
|
48997
|
+
const entries = Array.isArray(given) ? given : [given];
|
|
48998
|
+
const cased = entries.filter((entry) => typeof entry === "string" && CASE_AFTER_SCRIPT.test(entry.trim()));
|
|
48999
|
+
const first = cased[0];
|
|
49000
|
+
if (first === undefined) {
|
|
49001
|
+
return null;
|
|
49002
|
+
}
|
|
49003
|
+
const file = CASE_AFTER_SCRIPT.exec(first.trim())?.[1] ?? first;
|
|
49004
|
+
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}.`;
|
|
49005
|
+
}
|
|
48903
49006
|
function casesIn(scripts, suite) {
|
|
48904
49007
|
return scripts.suite(suite)?.cases ?? scripts.script(suite)?.funcs.filter((name) => name.startsWith("test_")) ?? [];
|
|
48905
49008
|
}
|
|
@@ -48961,9 +49064,12 @@ function selectTests(projectPath, asked, given) {
|
|
|
48961
49064
|
const ignore = declared.filter((one) => suites.includes(one.suite) && !chosen.has(`${one.suite}:${one.test}`)).map((one) => `${one.suite}:${one.test}`);
|
|
48962
49065
|
return { ok: true, value: { suites, ignore, selected } };
|
|
48963
49066
|
}
|
|
49067
|
+
var CASE_AFTER_SCRIPT;
|
|
48964
49068
|
var init_test_selection = __esm(() => {
|
|
49069
|
+
init_paths();
|
|
48965
49070
|
init_project_scan();
|
|
48966
49071
|
init_suite_plan();
|
|
49072
|
+
CASE_AFTER_SCRIPT = /^(.*\.gd):(\w+)$/;
|
|
48967
49073
|
});
|
|
48968
49074
|
|
|
48969
49075
|
// src/server.ts
|
|
@@ -48985,7 +49091,7 @@ import {
|
|
|
48985
49091
|
statSync as statSync20,
|
|
48986
49092
|
writeFileSync as writeFileSync22
|
|
48987
49093
|
} from "node:fs";
|
|
48988
|
-
import { basename as basename13, dirname as dirname25, extname as extname4, isAbsolute as isAbsolute8, join as join43, normalize as normalize2, relative as
|
|
49094
|
+
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";
|
|
48989
49095
|
import { setTimeout as delay7 } from "node:timers/promises";
|
|
48990
49096
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
48991
49097
|
function editorRestartTimeoutMs() {
|
|
@@ -49142,7 +49248,7 @@ function keptGameRequest(tool, op, args) {
|
|
|
49142
49248
|
}
|
|
49143
49249
|
}
|
|
49144
49250
|
function projectSpelling(projectPath, absolutePath) {
|
|
49145
|
-
return `res://${
|
|
49251
|
+
return `res://${relative10(realAsFarAsItExists(projectPath), realAsFarAsItExists(absolutePath)).replaceAll("\\", "/")}`;
|
|
49146
49252
|
}
|
|
49147
49253
|
function whichGame(args) {
|
|
49148
49254
|
return { projectPath: args["projectPath"], pid: args["pid"] };
|
|
@@ -50049,7 +50155,7 @@ function failedVerdict(report) {
|
|
|
50049
50155
|
const last = parts.pop();
|
|
50050
50156
|
return last === undefined ? "failures" : parts.length === 0 ? last : `${parts.join(", ")} and ${last}`;
|
|
50051
50157
|
}
|
|
50052
|
-
function timedOutVerdict(timeoutMs, silentForMs, repeating = null) {
|
|
50158
|
+
function timedOutVerdict(timeoutMs, silentForMs, repeating = null, printedAnything = true) {
|
|
50053
50159
|
const quiet = Math.round(silentForMs);
|
|
50054
50160
|
if (repeating !== null) {
|
|
50055
50161
|
return {
|
|
@@ -50057,6 +50163,12 @@ function timedOutVerdict(timeoutMs, silentForMs, repeating = null) {
|
|
|
50057
50163
|
hung: false
|
|
50058
50164
|
};
|
|
50059
50165
|
}
|
|
50166
|
+
if (!printedAnything && timeoutMs < STALLED_AFTER_MS) {
|
|
50167
|
+
return {
|
|
50168
|
+
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}`,
|
|
50169
|
+
hung: false
|
|
50170
|
+
};
|
|
50171
|
+
}
|
|
50060
50172
|
return quiet >= Math.min(STALLED_AFTER_MS, timeoutMs / 2) ? {
|
|
50061
50173
|
verdict: `hung: killed after ${timeoutMs} ms, having printed nothing for the last ${quiet} ms`,
|
|
50062
50174
|
hung: true
|
|
@@ -51285,13 +51397,20 @@ class GodotServer {
|
|
|
51285
51397
|
return await this.handleRunTests(args);
|
|
51286
51398
|
case "scene_create":
|
|
51287
51399
|
return op === "create" ? await editorOrEngine("create_scene", "writes") : await editorOrEngine("save_scene", "writes");
|
|
51288
|
-
case "scene_tree":
|
|
51400
|
+
case "scene_tree": {
|
|
51401
|
+
const defaults = readBoolean(args, "includeDefaults") ?? false;
|
|
51402
|
+
const asked = readBoolean(args, "includeProperties");
|
|
51403
|
+
if (defaults && asked === false) {
|
|
51404
|
+
return this.createErrorResponse("includeDefaults lists the defaults among the properties, and includeProperties false lists none: leave includeProperties out, or set it true.");
|
|
51405
|
+
}
|
|
51406
|
+
const properties = asked ?? defaults;
|
|
51289
51407
|
return await this.readFromFile(args, "scenePath", "read_scene_tree", (file) => ({
|
|
51290
51408
|
scenePath: file,
|
|
51291
51409
|
depth: readNumber(args, "depth") ?? -1,
|
|
51292
|
-
includeProperties:
|
|
51293
|
-
includeDefaults:
|
|
51294
|
-
}), async () => await bridge("list_scene_nodes"));
|
|
51410
|
+
includeProperties: properties,
|
|
51411
|
+
includeDefaults: defaults
|
|
51412
|
+
}), async () => await bridge("list_scene_nodes", { includeProperties: properties }));
|
|
51413
|
+
}
|
|
51295
51414
|
case "scene_node":
|
|
51296
51415
|
switch (op) {
|
|
51297
51416
|
case "add":
|
|
@@ -52448,6 +52567,9 @@ class GodotServer {
|
|
|
52448
52567
|
if (!project.ok) {
|
|
52449
52568
|
return project.response;
|
|
52450
52569
|
}
|
|
52570
|
+
if (/[\r\n]/.test(readString(args, "query") ?? "")) {
|
|
52571
|
+
return this.createErrorResponse("query holds a line break, and project_search matches one line at a time: search for one of its lines.");
|
|
52572
|
+
}
|
|
52451
52573
|
const given = readNonEmptyString(args, "path");
|
|
52452
52574
|
const place = given === undefined ? null : resolveWithinProject(project.value.path, given, { root: true });
|
|
52453
52575
|
if (place !== null && !place.ok) {
|
|
@@ -52697,10 +52819,18 @@ ${platform}`;
|
|
|
52697
52819
|
ticker.unref();
|
|
52698
52820
|
return ticker;
|
|
52699
52821
|
}
|
|
52700
|
-
async handleRunTests(
|
|
52822
|
+
async handleRunTests(given) {
|
|
52823
|
+
const project = this.project(given);
|
|
52824
|
+
if (!project.ok) {
|
|
52825
|
+
return project.response;
|
|
52826
|
+
}
|
|
52827
|
+
const split = casesOutOfPath(project.value.path, given["path"], given["tests"]);
|
|
52828
|
+
if (!split.ok) {
|
|
52829
|
+
return this.createErrorResponse(split.reason);
|
|
52830
|
+
}
|
|
52831
|
+
const args = split.path === given["path"] ? given : { ...given, path: split.path, tests: split.tests };
|
|
52701
52832
|
const ran = await this.runTests(args);
|
|
52702
|
-
|
|
52703
|
-
if (!project.ok || callSignal()?.aborted === true) {
|
|
52833
|
+
if (callSignal()?.aborted === true) {
|
|
52704
52834
|
return ran;
|
|
52705
52835
|
}
|
|
52706
52836
|
const paths = testPathsIn(project.value.path, args["path"]);
|
|
@@ -52859,7 +52989,7 @@ ${platform}`;
|
|
|
52859
52989
|
"--expected-out",
|
|
52860
52990
|
expectedFile
|
|
52861
52991
|
];
|
|
52862
|
-
const timeoutMs = readPositiveNumber(args, "timeoutMs") ??
|
|
52992
|
+
const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? TEST_TIMEOUT_MS;
|
|
52863
52993
|
this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
52864
52994
|
const helperFiles = {
|
|
52865
52995
|
spec: join43(userData, "spec"),
|
|
@@ -52872,12 +53002,15 @@ ${platform}`;
|
|
|
52872
53002
|
const run = await this.spawnGame(engine.value, cmdArgs, userDataIn(userData), onDesktop ? { directory: project.value.path, files: helperFiles } : undefined);
|
|
52873
53003
|
const console2 = [];
|
|
52874
53004
|
let lastSaid = Date.now();
|
|
53005
|
+
let saidAnything = false;
|
|
52875
53006
|
run.process.stdout?.on("data", (data) => {
|
|
52876
53007
|
console2.push(data);
|
|
52877
53008
|
lastSaid = Date.now();
|
|
53009
|
+
saidAnything = true;
|
|
52878
53010
|
});
|
|
52879
53011
|
run.process.stderr?.on("data", () => {
|
|
52880
53012
|
lastSaid = Date.now();
|
|
53013
|
+
saidAnything = true;
|
|
52881
53014
|
});
|
|
52882
53015
|
const reporting = this.reportTestProgress(run.log);
|
|
52883
53016
|
const autoloaded = scriptsAutoloaded(project.value.path);
|
|
@@ -52970,7 +53103,7 @@ ${platform}`;
|
|
|
52970
53103
|
}).entries;
|
|
52971
53104
|
const said = printed.map((entry) => entry.text);
|
|
52972
53105
|
const nothingRan = timedOut || notCompiled !== null ? null : whyNoReport(said, asked.join(" and "));
|
|
52973
|
-
const cutShort = timedOut && notCompiled === null ? timedOutVerdict(timeoutMs, silentForMs, repeatingScriptError(run.log.all, (entry) => run.log.raisedByScript(entry))) : null;
|
|
53106
|
+
const cutShort = timedOut && notCompiled === null ? timedOutVerdict(timeoutMs, silentForMs, repeatingScriptError(run.log.all, (entry) => run.log.raisedByScript(entry)), saidAnything) : null;
|
|
52974
53107
|
const hung = cutShort?.hung ?? false;
|
|
52975
53108
|
const printedErrors = notCompiled !== null ? compileErrorsIn(run.log.all) : timedOut || exitCode !== 105 ? [] : scriptErrorsPrinted(said);
|
|
52976
53109
|
const declaring = printedErrors.some((error) => unresolvedClassIn(error.message) !== null) ? await originsOf(printedErrors, declaredClasses(project.value.path), checkWithEngine(engine.value, project.value.path)) : { origins: [], unconfirmed: [] };
|
|
@@ -53323,7 +53456,10 @@ ${error.text}`;
|
|
|
53323
53456
|
missingScript(projectPath, relativePath) {
|
|
53324
53457
|
const scripts = projectFiles(projectPath, [".gd"]).map((script) => `res://${script}`);
|
|
53325
53458
|
const wanted = `res://${relativePath}`;
|
|
53326
|
-
|
|
53459
|
+
const folder = dirname25(relativePath.replaceAll("\\", "/"));
|
|
53460
|
+
const inFolder = join43(projectPath, folder);
|
|
53461
|
+
const beside = existsSync34(inFolder) ? readdirSync19(inFolder, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => `res://${folder === "." ? "" : `${folder}/`}${entry.name}`) : [];
|
|
53462
|
+
return this.createErrorResponse(withNearestFiles(`Script file does not exist: ${wanted}`, wanted, scripts, beside));
|
|
53327
53463
|
}
|
|
53328
53464
|
async compileScripts(projectPath, paths) {
|
|
53329
53465
|
const payload = { checked: [], failed: [], scripts: [] };
|
|
@@ -54404,6 +54540,10 @@ ${error.text}`;
|
|
|
54404
54540
|
if (args["scene"] !== undefined || args["executable"] !== undefined) {
|
|
54405
54541
|
return refused("suites plays the suites in the tests addon's own scene, so it takes no scene and no executable: leave those out.");
|
|
54406
54542
|
}
|
|
54543
|
+
const cased = caseInSuites(args["suites"]);
|
|
54544
|
+
if (cased !== null) {
|
|
54545
|
+
return refused(cased);
|
|
54546
|
+
}
|
|
54407
54547
|
const paths = testPathsIn(projectPath, args["suites"], "suites");
|
|
54408
54548
|
if (!paths.ok) {
|
|
54409
54549
|
return refused(paths.reason, PATH_SOLUTIONS);
|
|
@@ -54593,6 +54733,9 @@ ${error.text}`;
|
|
|
54593
54733
|
let editorAskedAt = 0;
|
|
54594
54734
|
let editorSaysGoing = true;
|
|
54595
54735
|
let editorAsks = 0;
|
|
54736
|
+
let holding = null;
|
|
54737
|
+
let keeperAskedAt = 0;
|
|
54738
|
+
let heldHere = null;
|
|
54596
54739
|
const waitBegan = Date.now();
|
|
54597
54740
|
const runBegan = Math.min(this.activeProcess?.startedAt ?? waitBegan, waitBegan);
|
|
54598
54741
|
const lastSaid = () => editorSaysGoing ? "playing" : "not playing";
|
|
@@ -54605,7 +54748,19 @@ ${error.text}`;
|
|
|
54605
54748
|
}
|
|
54606
54749
|
const run = this.activeProcess;
|
|
54607
54750
|
if (run === null || !run.throughEditor) {
|
|
54608
|
-
|
|
54751
|
+
if (!stillRunning(run)) {
|
|
54752
|
+
return true;
|
|
54753
|
+
}
|
|
54754
|
+
const keeper = run?.keeper;
|
|
54755
|
+
if (keeper !== undefined && holding === null && Date.now() - keeperAskedAt >= RUN_POLL_MS) {
|
|
54756
|
+
keeperAskedAt = Date.now();
|
|
54757
|
+
holding = this.keptHold(keeper).then((held) => {
|
|
54758
|
+
heldHere = held;
|
|
54759
|
+
}).finally(() => {
|
|
54760
|
+
holding = null;
|
|
54761
|
+
});
|
|
54762
|
+
}
|
|
54763
|
+
return heldHere !== null;
|
|
54609
54764
|
}
|
|
54610
54765
|
if (asking === null && Date.now() - editorAskedAt >= RUN_POLL_MS) {
|
|
54611
54766
|
editorAskedAt = Date.now();
|
|
@@ -54641,7 +54796,7 @@ ${error.text}`;
|
|
|
54641
54796
|
addon: true,
|
|
54642
54797
|
budgetMs,
|
|
54643
54798
|
...budget.sizedToMs === undefined ? {} : { sizedToMs: budget.sizedToMs },
|
|
54644
|
-
heldAt: this.dapClient?.whereItStopped() ?? null,
|
|
54799
|
+
heldAt: going === null || going.throughEditor ? this.dapClient?.whereItStopped() ?? null : endpoint === null && running && going.keeper !== undefined ? await this.keptHold(going.keeper) : null,
|
|
54645
54800
|
running,
|
|
54646
54801
|
withArgs,
|
|
54647
54802
|
servesAProject: this.ownProject !== null,
|
|
@@ -56729,7 +56884,12 @@ ${error.text}`;
|
|
|
56729
56884
|
return contained.response;
|
|
56730
56885
|
}
|
|
56731
56886
|
const { op: _op, projectPath: _projectPath, ...params } = contained.value;
|
|
56732
|
-
const
|
|
56887
|
+
const stale = await this.refreshStaleClasses(project.value.path);
|
|
56888
|
+
if (!stale.ok) {
|
|
56889
|
+
return stale.response;
|
|
56890
|
+
}
|
|
56891
|
+
const loaded = await this.operation("create_script", params, project.value.path);
|
|
56892
|
+
const created = loaded.ok && stale.value.length > 0 ? { ...loaded, payload: { ...loaded.payload, classesRebuiltFirst: stale.value } } : loaded;
|
|
56733
56893
|
const declared = created.ok ? readString(created.payload, "class_name") ?? "" : "";
|
|
56734
56894
|
if (!created.ok || declared === "") {
|
|
56735
56895
|
return this.answer(created);
|
|
@@ -56766,8 +56926,26 @@ ${error.text}`;
|
|
|
56766
56926
|
});
|
|
56767
56927
|
}
|
|
56768
56928
|
async handleModifyScript(args) {
|
|
56769
|
-
const written = await this.headless("modify_script", args);
|
|
56770
56929
|
const project = this.project(args);
|
|
56930
|
+
const stale = project.ok ? await this.refreshStaleClasses(project.value.path) : null;
|
|
56931
|
+
if (stale !== null && !stale.ok) {
|
|
56932
|
+
return stale.response;
|
|
56933
|
+
}
|
|
56934
|
+
const loaded = await this.headless("modify_script", args);
|
|
56935
|
+
const first = loaded.content[0];
|
|
56936
|
+
const written = stale === null || stale.value.length === 0 || loaded.isError === true || first?.type !== "text" || !first.text.trimStart().startsWith("{") ? loaded : {
|
|
56937
|
+
...loaded,
|
|
56938
|
+
content: [
|
|
56939
|
+
{
|
|
56940
|
+
...first,
|
|
56941
|
+
text: answerJson({
|
|
56942
|
+
...asParams(JSON.parse(first.text)),
|
|
56943
|
+
classesRebuiltFirst: stale.value
|
|
56944
|
+
})
|
|
56945
|
+
},
|
|
56946
|
+
...loaded.content.slice(1)
|
|
56947
|
+
]
|
|
56948
|
+
};
|
|
56771
56949
|
const scriptPath = readNonEmptyString(args, "scriptPath");
|
|
56772
56950
|
if (written.isError === true || !project.ok || scriptPath === undefined || !this.editorHasOpen(project.value.path)) {
|
|
56773
56951
|
return written;
|
|
@@ -57744,7 +57922,7 @@ async function runGodotServer() {
|
|
|
57744
57922
|
const server = new GodotServer;
|
|
57745
57923
|
await server.run();
|
|
57746
57924
|
}
|
|
57747
|
-
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, FAILED_BUDGET = 1e6, ANSWER_BUDGET = 4000000, __dirname2, PARENT_ASKED_AFTER_MS = 5000, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, ALONGSIDE_NOTE = "Another call on this game was running while this one was: calls sent together run together and are answered as each finishes, so this answer is not from after the other. Send a call meant to come after another once that one has answered.", OWN_DEBUGGER = " Its debugger is the keeper holding it rather than the editor, so the debug_* tools answer for it all the same: breakpoints stop it, and debug_control pause breaks into it wherever it is, which the editor offers no way to do.", PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, KEEPER_DEBUG_MS, EXIT_REPORTED_WITHIN_MS = 2000, SUCCESSOR_SOUGHT_WITHIN_MS = 20000, CLOSE_ASK_MS = 3000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, PROFILE_SEND_MS = 5000, MOST_GROUPS = 20, RUNTIME_LISTENING_LINE = "[gdharness] runtime listening on ", VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, OPS_ELSEWHERE, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", LOOPING_AFTER = 100, WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, DESTINATION_ARGUMENTS, OUTPUT_PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, PROPERTY_PLACES = "It is a property, so each place says whether it writes it (access write), which runs its set block, or reads it (access read), which runs its get block: withinCalls on a read says nothing of the set block, nor on a write of the get block unless the write is a compound one such as +=. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not listed.", CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, MOST_PRINTED_LINES = 50, MOST_UNDECLARED_ERRORS = 20, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", RAISED_WARNING = " (Warning treated as error.)", PARSE_ERROR2 = "Parse Error: ", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
|
|
57925
|
+
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;
|
|
57748
57926
|
var init_server = __esm(() => {
|
|
57749
57927
|
init_dist2();
|
|
57750
57928
|
init_stdio();
|
|
@@ -58235,7 +58413,7 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
|
|
|
58235
58413
|
These need the runtime autoload, which \`gdharness setup\` registers. \`runtime_capture\` needs a
|
|
58236
58414
|
window and refuses headless rather than handing back the last frame anything drew. On Windows a
|
|
58237
58415
|
windowed run this server starts is on a desktop of its own, where it draws and can be captured but
|
|
58238
|
-
never shows or takes the keyboard.
|
|
58416
|
+
never shows or takes the keyboard or the pointer.
|
|
58239
58417
|
|
|
58240
58418
|
The autoload reads these settings and no others. \`${BIND_ADDRESS_SETTING}\`
|
|
58241
58419
|
is \`127.0.0.1\` and should stay there: the command set includes \`call_method\`, \`set_property\`
|
|
@@ -58748,7 +58926,7 @@ import {
|
|
|
58748
58926
|
writeFileSync as writeFileSync12
|
|
58749
58927
|
} from "node:fs";
|
|
58750
58928
|
import { availableParallelism as availableParallelism4 } from "node:os";
|
|
58751
|
-
import { dirname as dirname13, join as join27, relative as
|
|
58929
|
+
import { dirname as dirname13, join as join27, relative as relative5, resolve as resolve10, sep as sep4 } from "node:path";
|
|
58752
58930
|
import process14 from "node:process";
|
|
58753
58931
|
|
|
58754
58932
|
// src/suite-fingerprint.ts
|
|
@@ -58913,6 +59091,7 @@ function getLocalVersion() {
|
|
|
58913
59091
|
}
|
|
58914
59092
|
|
|
58915
59093
|
// src/suite-run.ts
|
|
59094
|
+
var LAST_RUN_FILE = "last-run.json";
|
|
58916
59095
|
var SLOWEST_NAMED = 5;
|
|
58917
59096
|
var SILENCE_TIMES_TAKEN = 4;
|
|
58918
59097
|
function silenceWhile(running, times, floorSeconds) {
|
|
@@ -59083,14 +59262,14 @@ class RunLogs {
|
|
|
59083
59262
|
return this.kept ?? this.directory;
|
|
59084
59263
|
}
|
|
59085
59264
|
named(path) {
|
|
59086
|
-
return this.kept === null ? path : join27(this.kept,
|
|
59265
|
+
return this.kept === null ? path : join27(this.kept, relative5(this.directory, path));
|
|
59087
59266
|
}
|
|
59088
59267
|
keep() {
|
|
59089
59268
|
if (this.kept === null) {
|
|
59090
59269
|
return;
|
|
59091
59270
|
}
|
|
59092
59271
|
for (const file of readdirSync14(this.directory, { recursive: true, encoding: "utf8" })) {
|
|
59093
|
-
if (file.endsWith(".log") && file.split(
|
|
59272
|
+
if (file.endsWith(".log") && file.split(sep4).length === 2) {
|
|
59094
59273
|
mkdirSync10(dirname13(join27(this.kept, file)), { recursive: true });
|
|
59095
59274
|
copyFileSync(join27(this.directory, file), join27(this.kept, file));
|
|
59096
59275
|
}
|
|
@@ -59108,6 +59287,7 @@ async function runTestCommand(options, godotPath) {
|
|
|
59108
59287
|
const state = join27(home, ".godot", "gdharness");
|
|
59109
59288
|
const timesFile = join27(state, "suite-times.json");
|
|
59110
59289
|
const passedFile = join27(state, "suite-passed.json");
|
|
59290
|
+
const lastRunFile = join27(state, LAST_RUN_FILE);
|
|
59111
59291
|
let suites = selectedSuites(scripts, options);
|
|
59112
59292
|
const uncollected = uncollectedSaid(scripts.uncollectedUnder(searchedRoots(options)));
|
|
59113
59293
|
const wholeSuites = suites.map((suite) => scripts.suite(suite.path) ?? suite);
|
|
@@ -59155,7 +59335,7 @@ async function runTestCommand(options, godotPath) {
|
|
|
59155
59335
|
const imported = await importProject(launch);
|
|
59156
59336
|
if (imported !== null) {
|
|
59157
59337
|
logs.keep();
|
|
59158
|
-
writeEarlyResult(options.resultFile, startedAt, [`the import failed: ${imported}`], logs.shown);
|
|
59338
|
+
writeEarlyResult({ resultFile: options.resultFile, lastRunFile, engines: options.engines }, startedAt, [`the import failed: ${imported}`], logs.shown);
|
|
59159
59339
|
console.log(`The import failed, so no suite ran: ${imported}`);
|
|
59160
59340
|
return 1;
|
|
59161
59341
|
}
|
|
@@ -59180,7 +59360,7 @@ ${existsSync23(path) ? readFileSync22(path, "latin1") : "missing"}`)
|
|
|
59180
59360
|
if (suites.length === 0) {
|
|
59181
59361
|
console.log("Nothing has changed since every suite last passed.");
|
|
59182
59362
|
rmSync12(runDirectory, { recursive: true, force: true });
|
|
59183
|
-
writeEarlyResult(options.resultFile, startedAt, uncollected, null);
|
|
59363
|
+
writeEarlyResult({ resultFile: options.resultFile, lastRunFile, engines: options.engines }, startedAt, uncollected, null);
|
|
59184
59364
|
if (uncollected.length === 0) {
|
|
59185
59365
|
return 0;
|
|
59186
59366
|
}
|
|
@@ -59206,26 +59386,31 @@ ${existsSync23(path) ? readFileSync22(path, "latin1") : "missing"}`)
|
|
|
59206
59386
|
const functions = suites.reduce((sum, suite) => sum + suite.cases.length, 0);
|
|
59207
59387
|
const asked = unitsOf(suites, times, splitPastFor(suites, times, options.engines));
|
|
59208
59388
|
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)}`;
|
|
59209
|
-
|
|
59389
|
+
const dealing = [];
|
|
59390
|
+
const say = (line) => {
|
|
59391
|
+
dealing.push(line);
|
|
59392
|
+
console.log(line);
|
|
59393
|
+
};
|
|
59394
|
+
say(`${plural(functions, "test function")} in ${plural(suites.length, "suite")} over ${plural(plans.length, "engine")}${how}.`);
|
|
59210
59395
|
const windowedAnyway = headlessInAWindow(plans, units);
|
|
59211
59396
|
if (windowedAnyway.length > 0) {
|
|
59212
59397
|
const one = windowedAnyway.length === 1;
|
|
59213
|
-
|
|
59398
|
+
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.`);
|
|
59214
59399
|
}
|
|
59215
59400
|
const split = [...new Set(units.map((unit) => unit.suite))].filter((path) => units.filter((unit) => unit.suite === path && unit.row === undefined).length > 1);
|
|
59216
59401
|
if (plans.length > 1 && split.length > 0) {
|
|
59217
|
-
|
|
59402
|
+
say(`Dealt out case by case, since ${split.length === 1 ? "it" : "each"} took more than ${past} last time: ${named(split)}.`);
|
|
59218
59403
|
}
|
|
59219
59404
|
const byRows = [
|
|
59220
59405
|
...new Set(units.flatMap((unit) => unit.row === undefined ? [] : [`${unit.suite} > ${unit.cases[0] ?? ""}`]))
|
|
59221
59406
|
];
|
|
59222
59407
|
if (plans.length > 1 && byRows.length > 0) {
|
|
59223
|
-
|
|
59408
|
+
say(`Dealt out row by row, since ${byRows.length === 1 ? "it" : "each"} took more than ${past} last time: ${named(byRows)}.`);
|
|
59224
59409
|
}
|
|
59225
59410
|
const keptWhole = units.flatMap((unit) => unit.cost > splitPast && !unit.windowed && suites.some((s) => s.path === unit.suite && s.together) ? [unit.suite] : []);
|
|
59226
59411
|
if (options.order === null && room.engines > 1 && keptWhole.length > 0) {
|
|
59227
59412
|
const one = keptWhole.length === 1;
|
|
59228
|
-
|
|
59413
|
+
say(`Kept whole in one engine, since ${one ? "it declares" : "each declares"} GDHARNESS_TOGETHER: ${named(keptWhole)}.`);
|
|
59229
59414
|
}
|
|
59230
59415
|
const lanes = [...new Set(plans.map((plan) => plan.lane))].map((lane) => plans.flatMap((plan, index) => plan.lane === lane ? [{ plan, index }] : []));
|
|
59231
59416
|
const taken = [];
|
|
@@ -59238,11 +59423,11 @@ ${existsSync23(path) ? readFileSync22(path, "latin1") : "missing"}`)
|
|
|
59238
59423
|
}
|
|
59239
59424
|
const shared = options.order === null && lanes.length > 1 && taken.length < lanes.length;
|
|
59240
59425
|
if (shared) {
|
|
59241
|
-
|
|
59426
|
+
say(sharesSaid(taken.length, lanes.length));
|
|
59242
59427
|
}
|
|
59243
59428
|
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);
|
|
59244
59429
|
if (ran.some((engine) => engine.verdict.starved) && ran.some((engine) => !engine.verdict.passed)) {
|
|
59245
|
-
|
|
59430
|
+
say("An engine ran out of memory, so every engine that failed runs again, one at a time.");
|
|
59246
59431
|
const again = [];
|
|
59247
59432
|
for (const engine of ran) {
|
|
59248
59433
|
again.push(engine.verdict.passed ? engine : await runEngine2(launch, engine.plan, engine.index, times, "again"));
|
|
@@ -59252,7 +59437,13 @@ ${existsSync23(path) ? readFileSync22(path, "latin1") : "missing"}`)
|
|
|
59252
59437
|
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}`))))));
|
|
59253
59438
|
const across = rowsAcross(ran.map((engine) => ({ handed: engine.plan.suites, printed: engine.printed })));
|
|
59254
59439
|
recordPassed(passedFile, ran, printsOf, new Set(across.map((fault) => fault.suite)));
|
|
59255
|
-
return report(ran, across.map((fault) => fault.fault), uncollected, startedAt, logs, options.printOutput,
|
|
59440
|
+
return report(ran, across.map((fault) => fault.fault), uncollected, startedAt, logs, options.printOutput, {
|
|
59441
|
+
resultFile: options.resultFile,
|
|
59442
|
+
lastRunFile,
|
|
59443
|
+
dealing,
|
|
59444
|
+
engines: options.engines,
|
|
59445
|
+
slotsFree: taken.length
|
|
59446
|
+
});
|
|
59256
59447
|
} finally {
|
|
59257
59448
|
process14.off("SIGINT", interrupted);
|
|
59258
59449
|
process14.off("SIGTERM", interrupted);
|
|
@@ -59417,7 +59608,7 @@ async function runEngine2(launch, plan, index, times, again = "", slot = null) {
|
|
|
59417
59608
|
...plan.windowed ? [] : ["--ignoreHeadlessMode"],
|
|
59418
59609
|
"-c",
|
|
59419
59610
|
"-rd",
|
|
59420
|
-
`res://${
|
|
59611
|
+
`res://${relative5(launch.projectPath, join27(directory, "reports")).split(sep4).join("/")}`,
|
|
59421
59612
|
"-rc",
|
|
59422
59613
|
"1",
|
|
59423
59614
|
"--",
|
|
@@ -59459,6 +59650,7 @@ async function runEngine2(launch, plan, index, times, again = "", slot = null) {
|
|
|
59459
59650
|
printed,
|
|
59460
59651
|
verdict: written === null ? judged : { ...judged, passed: false, faults: [...judged.faults, written] },
|
|
59461
59652
|
log,
|
|
59653
|
+
began,
|
|
59462
59654
|
seconds: (Date.now() - began) / 1000,
|
|
59463
59655
|
again: again !== ""
|
|
59464
59656
|
};
|
|
@@ -59469,11 +59661,11 @@ function reportsFault(reports) {
|
|
|
59469
59661
|
if (files.length === 0) {
|
|
59470
59662
|
return null;
|
|
59471
59663
|
}
|
|
59472
|
-
const shown = files.slice(0, REPORT_FILES_NAMED).map((file) => file.split(
|
|
59664
|
+
const shown = files.slice(0, REPORT_FILES_NAMED).map((file) => file.split(sep4).join("/"));
|
|
59473
59665
|
const more = files.length - shown.length;
|
|
59474
59666
|
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`;
|
|
59475
59667
|
}
|
|
59476
|
-
function report(ran, across, uncollected, startedAt, logs, printOutput,
|
|
59668
|
+
function report(ran, across, uncollected, startedAt, logs, printOutput, targets) {
|
|
59477
59669
|
if (printOutput) {
|
|
59478
59670
|
for (const engine of ran) {
|
|
59479
59671
|
console.log("");
|
|
@@ -59550,27 +59742,31 @@ ${readFileSync22(engine.log, "utf8")}`);
|
|
|
59550
59742
|
} else {
|
|
59551
59743
|
logs.keep();
|
|
59552
59744
|
}
|
|
59553
|
-
|
|
59554
|
-
|
|
59555
|
-
|
|
59556
|
-
|
|
59557
|
-
|
|
59558
|
-
|
|
59559
|
-
|
|
59560
|
-
|
|
59561
|
-
|
|
59562
|
-
|
|
59563
|
-
|
|
59564
|
-
|
|
59565
|
-
|
|
59566
|
-
|
|
59567
|
-
|
|
59568
|
-
|
|
59569
|
-
|
|
59570
|
-
|
|
59571
|
-
|
|
59572
|
-
})
|
|
59573
|
-
|
|
59745
|
+
writeResult(targets, {
|
|
59746
|
+
passed,
|
|
59747
|
+
seconds: took,
|
|
59748
|
+
problems: [
|
|
59749
|
+
...failed.flatMap((engine) => engine.verdict.faults.map((fault) => `engine ${String(engine.index)}: ${fault}`)),
|
|
59750
|
+
...across,
|
|
59751
|
+
...uncollected
|
|
59752
|
+
],
|
|
59753
|
+
suites: closed,
|
|
59754
|
+
engines: ran.map((engine) => ({
|
|
59755
|
+
index: engine.index,
|
|
59756
|
+
lane: engine.plan.lane,
|
|
59757
|
+
startedAfter: (engine.began - startedAt) / 1000,
|
|
59758
|
+
seconds: engine.seconds,
|
|
59759
|
+
dealt: engine.plan.cost,
|
|
59760
|
+
passed: engine.verdict.passed,
|
|
59761
|
+
windowed: engine.plan.windowed,
|
|
59762
|
+
suites: engine.plan.suites.map((suite) => suite.path),
|
|
59763
|
+
log: passed ? null : logs.named(engine.log)
|
|
59764
|
+
})),
|
|
59765
|
+
logs: passed ? null : logs.shown,
|
|
59766
|
+
enginesAsked: targets.engines,
|
|
59767
|
+
slotsFree: targets.slotsFree,
|
|
59768
|
+
dealing: targets.dealing
|
|
59769
|
+
});
|
|
59574
59770
|
if (passed) {
|
|
59575
59771
|
console.log(`Passed: ${totals}`);
|
|
59576
59772
|
return 0;
|
|
@@ -59580,16 +59776,23 @@ ${readFileSync22(engine.log, "utf8")}`);
|
|
|
59580
59776
|
console.log(`Every engine's output is kept under ${logs.shown} until ${String(KEPT_RUN_DIRECTORIES)} later runs have failed.`);
|
|
59581
59777
|
return 1;
|
|
59582
59778
|
}
|
|
59583
|
-
function writeEarlyResult(
|
|
59584
|
-
|
|
59585
|
-
|
|
59586
|
-
|
|
59587
|
-
|
|
59588
|
-
|
|
59589
|
-
|
|
59590
|
-
|
|
59591
|
-
|
|
59592
|
-
|
|
59779
|
+
function writeEarlyResult(targets, startedAt, problems, logs) {
|
|
59780
|
+
writeResult(targets, {
|
|
59781
|
+
passed: problems.length === 0,
|
|
59782
|
+
seconds: (Date.now() - startedAt) / 1000,
|
|
59783
|
+
problems,
|
|
59784
|
+
suites: [],
|
|
59785
|
+
engines: [],
|
|
59786
|
+
logs,
|
|
59787
|
+
enginesAsked: targets.engines,
|
|
59788
|
+
slotsFree: 0,
|
|
59789
|
+
dealing: []
|
|
59790
|
+
});
|
|
59791
|
+
}
|
|
59792
|
+
function writeResult(targets, result) {
|
|
59793
|
+
writeJson(targets.lastRunFile, result);
|
|
59794
|
+
if (targets.resultFile !== null) {
|
|
59795
|
+
writeJson(targets.resultFile, result);
|
|
59593
59796
|
}
|
|
59594
59797
|
}
|
|
59595
59798
|
function printUncollected(uncollected) {
|