gdharness 1.3.40 → 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 +275 -105
- 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/scene_tools.gd +11 -10
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +77 -22
- package/build/godot/addons/gdharness_runtime/runtime_typing.gd +15 -8
- 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/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -12910,7 +12910,7 @@ var init_doc_cache = __esm(() => {
|
|
|
12910
12910
|
|
|
12911
12911
|
// src/project-scan.ts
|
|
12912
12912
|
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";
|
|
12913
|
+
import { basename as basename5, extname, join as join11, relative as relative4, sep as sep3 } from "node:path";
|
|
12914
12914
|
function skipped(name) {
|
|
12915
12915
|
return name.startsWith(".") || SKIPPED.has(name);
|
|
12916
12916
|
}
|
|
@@ -13070,6 +13070,20 @@ function pathsInUidCache(projectPath) {
|
|
|
13070
13070
|
}
|
|
13071
13071
|
return new Set(paths.values());
|
|
13072
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
|
+
}
|
|
13073
13087
|
function isBinary(bytes) {
|
|
13074
13088
|
return bytes.subarray(0, 8000).includes(0);
|
|
13075
13089
|
}
|
|
@@ -13195,8 +13209,8 @@ function searchProject(projectPath, options) {
|
|
|
13195
13209
|
result.summary.files_with_matches += 1;
|
|
13196
13210
|
}
|
|
13197
13211
|
};
|
|
13198
|
-
const visit = (directory) => {
|
|
13199
|
-
if (steppedOver(directory)) {
|
|
13212
|
+
const visit = (directory, named = false) => {
|
|
13213
|
+
if (!named && steppedOver(directory)) {
|
|
13200
13214
|
if (gdignored(directory)) {
|
|
13201
13215
|
notSearched.push(`res://${directory.slice(projectPath.length + 1).replace(/\\/g, "/")}/`);
|
|
13202
13216
|
}
|
|
@@ -13219,20 +13233,17 @@ function searchProject(projectPath, options) {
|
|
|
13219
13233
|
};
|
|
13220
13234
|
const within = options.within ?? projectPath;
|
|
13221
13235
|
if (statSync9(within).isDirectory()) {
|
|
13222
|
-
visit(within);
|
|
13236
|
+
visit(within, true);
|
|
13223
13237
|
} else {
|
|
13224
13238
|
searchFile(within, basename5(within));
|
|
13225
13239
|
}
|
|
13226
13240
|
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
|
-
};
|
|
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(" ") };
|
|
13236
13247
|
}
|
|
13237
13248
|
var SKIPPED, VERSION_MARKER = ".gdharness-version", SCENE_EXTENSIONS, SIDECAR_EXTENSIONS, ASSET_EXTENSIONS, CARRIES_A_UID, BINARY_EXTENSIONS;
|
|
13238
13249
|
var init_project_scan = __esm(() => {
|
|
@@ -16433,8 +16444,14 @@ var init_tool_definitions = __esm(() => {
|
|
|
16433
16444
|
type: "string",
|
|
16434
16445
|
description: "Variables: the initial value. Constants: the value, which a constant needs. As written in GDScript."
|
|
16435
16446
|
},
|
|
16436
|
-
isExport: {
|
|
16437
|
-
|
|
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
|
+
},
|
|
16438
16455
|
isOnready: { type: "boolean", description: "Variables: add @onready." },
|
|
16439
16456
|
position: {
|
|
16440
16457
|
type: "string",
|
|
@@ -16537,7 +16554,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16537
16554
|
{
|
|
16538
16555
|
name: "project_settings",
|
|
16539
16556
|
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. 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.",
|
|
16541
16558
|
parameters: {
|
|
16542
16559
|
projectPath: PROJECT_PATH,
|
|
16543
16560
|
setting: {
|
|
@@ -16697,7 +16714,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16697
16714
|
name: "project_search",
|
|
16698
16715
|
title: "Search the project",
|
|
16699
16716
|
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.",
|
|
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.",
|
|
16701
16718
|
parameters: {
|
|
16702
16719
|
projectPath: PROJECT_PATH,
|
|
16703
16720
|
query: { type: "string", description: "The text or pattern to find." },
|
|
@@ -16712,7 +16729,11 @@ var init_tool_definitions = __esm(() => {
|
|
|
16712
16729
|
},
|
|
16713
16730
|
regex: { type: "boolean", description: "Read the query as a regular expression. Default false." },
|
|
16714
16731
|
caseSensitive: { type: "boolean", description: "Default false." },
|
|
16715
|
-
maxResults: {
|
|
16732
|
+
maxResults: {
|
|
16733
|
+
type: "integer",
|
|
16734
|
+
minimum: 1,
|
|
16735
|
+
description: "How many matching lines to answer. Default 100."
|
|
16736
|
+
}
|
|
16716
16737
|
},
|
|
16717
16738
|
requires: ["projectPath", "query"]
|
|
16718
16739
|
},
|
|
@@ -16729,7 +16750,11 @@ var init_tool_definitions = __esm(() => {
|
|
|
16729
16750
|
enum: ["forward", "reverse"],
|
|
16730
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."
|
|
16731
16752
|
},
|
|
16732
|
-
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
|
+
},
|
|
16733
16758
|
includeBuiltin: {
|
|
16734
16759
|
type: "boolean",
|
|
16735
16760
|
description: "forward: include the engine's own res://. resources. Default false."
|
|
@@ -16906,7 +16931,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16906
16931
|
path: {
|
|
16907
16932
|
type: ["string", "array"],
|
|
16908
16933
|
items: { type: "string" },
|
|
16909
|
-
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.'
|
|
16910
16935
|
},
|
|
16911
16936
|
ignore: {
|
|
16912
16937
|
type: "array",
|
|
@@ -16949,7 +16974,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16949
16974
|
rootNodeType: {
|
|
16950
16975
|
type: "string",
|
|
16951
16976
|
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."
|
|
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."
|
|
16953
16978
|
},
|
|
16954
16979
|
rootNodeName: {
|
|
16955
16980
|
type: "string",
|
|
@@ -16985,8 +17010,9 @@ var init_tool_definitions = __esm(() => {
|
|
|
16985
17010
|
projectPath: PROJECT_PATH,
|
|
16986
17011
|
scenePath: SCENE_PATH,
|
|
16987
17012
|
depth: {
|
|
16988
|
-
type: "
|
|
16989
|
-
|
|
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."
|
|
16990
17016
|
},
|
|
16991
17017
|
includeProperties: {
|
|
16992
17018
|
type: "boolean",
|
|
@@ -16994,7 +17020,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16994
17020
|
},
|
|
16995
17021
|
includeDefaults: {
|
|
16996
17022
|
type: "boolean",
|
|
16997
|
-
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."
|
|
16998
17024
|
}
|
|
16999
17025
|
},
|
|
17000
17026
|
requires: ["projectPath", "scenePath"]
|
|
@@ -17019,7 +17045,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17019
17045
|
nodeType: {
|
|
17020
17046
|
type: "string",
|
|
17021
17047
|
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
|
|
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.`
|
|
17023
17049
|
},
|
|
17024
17050
|
instancePath: {
|
|
17025
17051
|
type: "string",
|
|
@@ -17117,7 +17143,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17117
17143
|
{
|
|
17118
17144
|
name: "scene_signal",
|
|
17119
17145
|
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
|
|
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}`,
|
|
17121
17147
|
parameters: {
|
|
17122
17148
|
projectPath: PROJECT_PATH,
|
|
17123
17149
|
scenePath: SCENE_PATH,
|
|
@@ -17185,7 +17211,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17185
17211
|
animationName: {
|
|
17186
17212
|
type: "string",
|
|
17187
17213
|
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."
|
|
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."
|
|
17189
17215
|
},
|
|
17190
17216
|
newName: {
|
|
17191
17217
|
type: "string",
|
|
@@ -17615,7 +17641,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17615
17641
|
type: ["string", "array"],
|
|
17616
17642
|
items: { type: "string" },
|
|
17617
17643
|
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."
|
|
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."
|
|
17619
17645
|
},
|
|
17620
17646
|
args: {
|
|
17621
17647
|
type: "array",
|
|
@@ -18419,7 +18445,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
18419
18445
|
{
|
|
18420
18446
|
name: "debug_breakpoint",
|
|
18421
18447
|
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.",
|
|
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.",
|
|
18423
18449
|
parameters: {
|
|
18424
18450
|
projectPath: PROJECT_PATH,
|
|
18425
18451
|
scriptPath: { ...SCRIPT_PATH, ops: ["set", "remove"] },
|
|
@@ -41431,7 +41457,7 @@ var init_boot_note = __esm(() => {
|
|
|
41431
41457
|
|
|
41432
41458
|
// src/breakpoint-note.ts
|
|
41433
41459
|
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
|
|
41460
|
+
import { dirname as dirname16, isAbsolute as isAbsolute7, join as join31, relative as relative6 } from "node:path";
|
|
41435
41461
|
function breakpointNotePath(projectPath) {
|
|
41436
41462
|
return join31(projectPath, ".godot", "gdharness-breakpoints.json");
|
|
41437
41463
|
}
|
|
@@ -41446,7 +41472,7 @@ function writeBreakpointNote(projectPath, held) {
|
|
|
41446
41472
|
}
|
|
41447
41473
|
const note = {
|
|
41448
41474
|
breakpoints: inside.map((one) => ({
|
|
41449
|
-
script:
|
|
41475
|
+
script: relative6(root, one.scriptPath).replaceAll("\\", "/"),
|
|
41450
41476
|
lines: [...one.lines]
|
|
41451
41477
|
}))
|
|
41452
41478
|
};
|
|
@@ -41483,7 +41509,7 @@ function readBreakpointNote(projectPath) {
|
|
|
41483
41509
|
}
|
|
41484
41510
|
}
|
|
41485
41511
|
function withinProject(projectPath, scriptPath) {
|
|
41486
|
-
const rel =
|
|
41512
|
+
const rel = relative6(projectPath, scriptPath);
|
|
41487
41513
|
return rel !== "" && !rel.startsWith("..") && !isAbsolute7(rel);
|
|
41488
41514
|
}
|
|
41489
41515
|
var init_breakpoint_note = __esm(() => {
|
|
@@ -43376,9 +43402,9 @@ var init_rename = __esm(() => {
|
|
|
43376
43402
|
|
|
43377
43403
|
// src/rename-symbol.ts
|
|
43378
43404
|
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
|
|
43405
|
+
import { basename as basename9, dirname as dirname21, join as join36, relative as relative7, resolve as resolve12 } from "node:path";
|
|
43380
43406
|
function resourcePathOf(projectPath, absolute) {
|
|
43381
|
-
const inside =
|
|
43407
|
+
const inside = relative7(onDiskSpelling(projectPath), onDiskSpelling(absolute));
|
|
43382
43408
|
if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
|
|
43383
43409
|
return null;
|
|
43384
43410
|
}
|
|
@@ -43884,7 +43910,7 @@ import {
|
|
|
43884
43910
|
statSync as statSync17,
|
|
43885
43911
|
writeFileSync as writeFileSync19
|
|
43886
43912
|
} from "node:fs";
|
|
43887
|
-
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";
|
|
43888
43914
|
function filesUnder(directory) {
|
|
43889
43915
|
return readdirSync17(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
43890
43916
|
const path = join37(directory, entry.name);
|
|
@@ -43945,7 +43971,7 @@ function binaryResources(projectPath) {
|
|
|
43945
43971
|
return found;
|
|
43946
43972
|
}
|
|
43947
43973
|
function targetPathOf(projectPath, absolute) {
|
|
43948
|
-
const inside =
|
|
43974
|
+
const inside = relative8(onDiskSpelling(projectPath), join37(onDiskSpelling(dirname22(absolute)), basename10(absolute)));
|
|
43949
43975
|
if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
|
|
43950
43976
|
return null;
|
|
43951
43977
|
}
|
|
@@ -47908,7 +47934,14 @@ function similarity(one, other) {
|
|
|
47908
47934
|
const shared = ones.filter((pair) => others.includes(pair)).length;
|
|
47909
47935
|
return 2 * shared / (ones.length + others.length);
|
|
47910
47936
|
}
|
|
47911
|
-
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
|
+
}
|
|
47912
47945
|
const extension = /\.([^./]+)$/.exec(wanted)?.[1];
|
|
47913
47946
|
if (extension === undefined) {
|
|
47914
47947
|
return said;
|
|
@@ -47916,7 +47949,6 @@ function withNearestFiles(said, wanted, files) {
|
|
|
47916
47949
|
if (files.length === 0) {
|
|
47917
47950
|
return `${said}. The project holds no .${extension} file.`;
|
|
47918
47951
|
}
|
|
47919
|
-
const stem = (path) => (path.split("/").pop() ?? path).replace(/\.[^.]*$/, "");
|
|
47920
47952
|
const close = nearest(stem(wanted), files.map((file) => stem(file)));
|
|
47921
47953
|
const offered = files.filter((file) => close.includes(stem(file)));
|
|
47922
47954
|
if (offered.length > 0) {
|
|
@@ -48894,7 +48926,7 @@ var init_server_instructions = __esm(() => {
|
|
|
48894
48926
|
|
|
48895
48927
|
// src/suites-scene.ts
|
|
48896
48928
|
import { existsSync as existsSync32, mkdirSync as mkdirSync22, writeFileSync as writeFileSync21 } from "node:fs";
|
|
48897
|
-
import { join as join41, relative as
|
|
48929
|
+
import { join as join41, relative as relative9, sep as sep5 } from "node:path";
|
|
48898
48930
|
function hasSuitesScene(projectPath) {
|
|
48899
48931
|
return existsSync32(join41(projectPath, SUITES_SCENE));
|
|
48900
48932
|
}
|
|
@@ -48918,7 +48950,7 @@ function suitesSceneArgs(projectPath, suites) {
|
|
|
48918
48950
|
"-c",
|
|
48919
48951
|
"--ignoreHeadlessMode",
|
|
48920
48952
|
"-rd",
|
|
48921
|
-
`res://${
|
|
48953
|
+
`res://${relative9(projectPath, join41(directory, "reports")).split(sep5).join("/")}`,
|
|
48922
48954
|
"-rc",
|
|
48923
48955
|
"1"
|
|
48924
48956
|
];
|
|
@@ -48933,6 +48965,44 @@ var init_suites_scene = __esm(() => {
|
|
|
48933
48965
|
// src/test-selection.ts
|
|
48934
48966
|
import { existsSync as existsSync33 } from "node:fs";
|
|
48935
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
|
+
}
|
|
48936
49006
|
function casesIn(scripts, suite) {
|
|
48937
49007
|
return scripts.suite(suite)?.cases ?? scripts.script(suite)?.funcs.filter((name) => name.startsWith("test_")) ?? [];
|
|
48938
49008
|
}
|
|
@@ -48994,9 +49064,12 @@ function selectTests(projectPath, asked, given) {
|
|
|
48994
49064
|
const ignore = declared.filter((one) => suites.includes(one.suite) && !chosen.has(`${one.suite}:${one.test}`)).map((one) => `${one.suite}:${one.test}`);
|
|
48995
49065
|
return { ok: true, value: { suites, ignore, selected } };
|
|
48996
49066
|
}
|
|
49067
|
+
var CASE_AFTER_SCRIPT;
|
|
48997
49068
|
var init_test_selection = __esm(() => {
|
|
49069
|
+
init_paths();
|
|
48998
49070
|
init_project_scan();
|
|
48999
49071
|
init_suite_plan();
|
|
49072
|
+
CASE_AFTER_SCRIPT = /^(.*\.gd):(\w+)$/;
|
|
49000
49073
|
});
|
|
49001
49074
|
|
|
49002
49075
|
// src/server.ts
|
|
@@ -49018,7 +49091,7 @@ import {
|
|
|
49018
49091
|
statSync as statSync20,
|
|
49019
49092
|
writeFileSync as writeFileSync22
|
|
49020
49093
|
} 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
|
|
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";
|
|
49022
49095
|
import { setTimeout as delay7 } from "node:timers/promises";
|
|
49023
49096
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
49024
49097
|
function editorRestartTimeoutMs() {
|
|
@@ -49175,7 +49248,7 @@ function keptGameRequest(tool, op, args) {
|
|
|
49175
49248
|
}
|
|
49176
49249
|
}
|
|
49177
49250
|
function projectSpelling(projectPath, absolutePath) {
|
|
49178
|
-
return `res://${
|
|
49251
|
+
return `res://${relative10(realAsFarAsItExists(projectPath), realAsFarAsItExists(absolutePath)).replaceAll("\\", "/")}`;
|
|
49179
49252
|
}
|
|
49180
49253
|
function whichGame(args) {
|
|
49181
49254
|
return { projectPath: args["projectPath"], pid: args["pid"] };
|
|
@@ -50082,7 +50155,7 @@ function failedVerdict(report) {
|
|
|
50082
50155
|
const last = parts.pop();
|
|
50083
50156
|
return last === undefined ? "failures" : parts.length === 0 ? last : `${parts.join(", ")} and ${last}`;
|
|
50084
50157
|
}
|
|
50085
|
-
function timedOutVerdict(timeoutMs, silentForMs, repeating = null) {
|
|
50158
|
+
function timedOutVerdict(timeoutMs, silentForMs, repeating = null, printedAnything = true) {
|
|
50086
50159
|
const quiet = Math.round(silentForMs);
|
|
50087
50160
|
if (repeating !== null) {
|
|
50088
50161
|
return {
|
|
@@ -50090,6 +50163,12 @@ function timedOutVerdict(timeoutMs, silentForMs, repeating = null) {
|
|
|
50090
50163
|
hung: false
|
|
50091
50164
|
};
|
|
50092
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
|
+
}
|
|
50093
50172
|
return quiet >= Math.min(STALLED_AFTER_MS, timeoutMs / 2) ? {
|
|
50094
50173
|
verdict: `hung: killed after ${timeoutMs} ms, having printed nothing for the last ${quiet} ms`,
|
|
50095
50174
|
hung: true
|
|
@@ -51318,13 +51397,20 @@ class GodotServer {
|
|
|
51318
51397
|
return await this.handleRunTests(args);
|
|
51319
51398
|
case "scene_create":
|
|
51320
51399
|
return op === "create" ? await editorOrEngine("create_scene", "writes") : await editorOrEngine("save_scene", "writes");
|
|
51321
|
-
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;
|
|
51322
51407
|
return await this.readFromFile(args, "scenePath", "read_scene_tree", (file) => ({
|
|
51323
51408
|
scenePath: file,
|
|
51324
51409
|
depth: readNumber(args, "depth") ?? -1,
|
|
51325
|
-
includeProperties:
|
|
51326
|
-
includeDefaults:
|
|
51327
|
-
}), async () => await bridge("list_scene_nodes"));
|
|
51410
|
+
includeProperties: properties,
|
|
51411
|
+
includeDefaults: defaults
|
|
51412
|
+
}), async () => await bridge("list_scene_nodes", { includeProperties: properties }));
|
|
51413
|
+
}
|
|
51328
51414
|
case "scene_node":
|
|
51329
51415
|
switch (op) {
|
|
51330
51416
|
case "add":
|
|
@@ -52481,6 +52567,9 @@ class GodotServer {
|
|
|
52481
52567
|
if (!project.ok) {
|
|
52482
52568
|
return project.response;
|
|
52483
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
|
+
}
|
|
52484
52573
|
const given = readNonEmptyString(args, "path");
|
|
52485
52574
|
const place = given === undefined ? null : resolveWithinProject(project.value.path, given, { root: true });
|
|
52486
52575
|
if (place !== null && !place.ok) {
|
|
@@ -52730,10 +52819,18 @@ ${platform}`;
|
|
|
52730
52819
|
ticker.unref();
|
|
52731
52820
|
return ticker;
|
|
52732
52821
|
}
|
|
52733
|
-
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 };
|
|
52734
52832
|
const ran = await this.runTests(args);
|
|
52735
|
-
|
|
52736
|
-
if (!project.ok || callSignal()?.aborted === true) {
|
|
52833
|
+
if (callSignal()?.aborted === true) {
|
|
52737
52834
|
return ran;
|
|
52738
52835
|
}
|
|
52739
52836
|
const paths = testPathsIn(project.value.path, args["path"]);
|
|
@@ -52892,7 +52989,7 @@ ${platform}`;
|
|
|
52892
52989
|
"--expected-out",
|
|
52893
52990
|
expectedFile
|
|
52894
52991
|
];
|
|
52895
|
-
const timeoutMs = readPositiveNumber(args, "timeoutMs") ??
|
|
52992
|
+
const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? TEST_TIMEOUT_MS;
|
|
52896
52993
|
this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
52897
52994
|
const helperFiles = {
|
|
52898
52995
|
spec: join43(userData, "spec"),
|
|
@@ -52905,12 +53002,15 @@ ${platform}`;
|
|
|
52905
53002
|
const run = await this.spawnGame(engine.value, cmdArgs, userDataIn(userData), onDesktop ? { directory: project.value.path, files: helperFiles } : undefined);
|
|
52906
53003
|
const console2 = [];
|
|
52907
53004
|
let lastSaid = Date.now();
|
|
53005
|
+
let saidAnything = false;
|
|
52908
53006
|
run.process.stdout?.on("data", (data) => {
|
|
52909
53007
|
console2.push(data);
|
|
52910
53008
|
lastSaid = Date.now();
|
|
53009
|
+
saidAnything = true;
|
|
52911
53010
|
});
|
|
52912
53011
|
run.process.stderr?.on("data", () => {
|
|
52913
53012
|
lastSaid = Date.now();
|
|
53013
|
+
saidAnything = true;
|
|
52914
53014
|
});
|
|
52915
53015
|
const reporting = this.reportTestProgress(run.log);
|
|
52916
53016
|
const autoloaded = scriptsAutoloaded(project.value.path);
|
|
@@ -53003,7 +53103,7 @@ ${platform}`;
|
|
|
53003
53103
|
}).entries;
|
|
53004
53104
|
const said = printed.map((entry) => entry.text);
|
|
53005
53105
|
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;
|
|
53106
|
+
const cutShort = timedOut && notCompiled === null ? timedOutVerdict(timeoutMs, silentForMs, repeatingScriptError(run.log.all, (entry) => run.log.raisedByScript(entry)), saidAnything) : null;
|
|
53007
53107
|
const hung = cutShort?.hung ?? false;
|
|
53008
53108
|
const printedErrors = notCompiled !== null ? compileErrorsIn(run.log.all) : timedOut || exitCode !== 105 ? [] : scriptErrorsPrinted(said);
|
|
53009
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: [] };
|
|
@@ -53356,7 +53456,10 @@ ${error.text}`;
|
|
|
53356
53456
|
missingScript(projectPath, relativePath) {
|
|
53357
53457
|
const scripts = projectFiles(projectPath, [".gd"]).map((script) => `res://${script}`);
|
|
53358
53458
|
const wanted = `res://${relativePath}`;
|
|
53359
|
-
|
|
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));
|
|
53360
53463
|
}
|
|
53361
53464
|
async compileScripts(projectPath, paths) {
|
|
53362
53465
|
const payload = { checked: [], failed: [], scripts: [] };
|
|
@@ -54437,6 +54540,10 @@ ${error.text}`;
|
|
|
54437
54540
|
if (args["scene"] !== undefined || args["executable"] !== undefined) {
|
|
54438
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.");
|
|
54439
54542
|
}
|
|
54543
|
+
const cased = caseInSuites(args["suites"]);
|
|
54544
|
+
if (cased !== null) {
|
|
54545
|
+
return refused(cased);
|
|
54546
|
+
}
|
|
54440
54547
|
const paths = testPathsIn(projectPath, args["suites"], "suites");
|
|
54441
54548
|
if (!paths.ok) {
|
|
54442
54549
|
return refused(paths.reason, PATH_SOLUTIONS);
|
|
@@ -54626,6 +54733,9 @@ ${error.text}`;
|
|
|
54626
54733
|
let editorAskedAt = 0;
|
|
54627
54734
|
let editorSaysGoing = true;
|
|
54628
54735
|
let editorAsks = 0;
|
|
54736
|
+
let holding = null;
|
|
54737
|
+
let keeperAskedAt = 0;
|
|
54738
|
+
let heldHere = null;
|
|
54629
54739
|
const waitBegan = Date.now();
|
|
54630
54740
|
const runBegan = Math.min(this.activeProcess?.startedAt ?? waitBegan, waitBegan);
|
|
54631
54741
|
const lastSaid = () => editorSaysGoing ? "playing" : "not playing";
|
|
@@ -54638,7 +54748,19 @@ ${error.text}`;
|
|
|
54638
54748
|
}
|
|
54639
54749
|
const run = this.activeProcess;
|
|
54640
54750
|
if (run === null || !run.throughEditor) {
|
|
54641
|
-
|
|
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;
|
|
54642
54764
|
}
|
|
54643
54765
|
if (asking === null && Date.now() - editorAskedAt >= RUN_POLL_MS) {
|
|
54644
54766
|
editorAskedAt = Date.now();
|
|
@@ -54674,7 +54796,7 @@ ${error.text}`;
|
|
|
54674
54796
|
addon: true,
|
|
54675
54797
|
budgetMs,
|
|
54676
54798
|
...budget.sizedToMs === undefined ? {} : { sizedToMs: budget.sizedToMs },
|
|
54677
|
-
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,
|
|
54678
54800
|
running,
|
|
54679
54801
|
withArgs,
|
|
54680
54802
|
servesAProject: this.ownProject !== null,
|
|
@@ -56762,7 +56884,12 @@ ${error.text}`;
|
|
|
56762
56884
|
return contained.response;
|
|
56763
56885
|
}
|
|
56764
56886
|
const { op: _op, projectPath: _projectPath, ...params } = contained.value;
|
|
56765
|
-
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;
|
|
56766
56893
|
const declared = created.ok ? readString(created.payload, "class_name") ?? "" : "";
|
|
56767
56894
|
if (!created.ok || declared === "") {
|
|
56768
56895
|
return this.answer(created);
|
|
@@ -56799,8 +56926,26 @@ ${error.text}`;
|
|
|
56799
56926
|
});
|
|
56800
56927
|
}
|
|
56801
56928
|
async handleModifyScript(args) {
|
|
56802
|
-
const written = await this.headless("modify_script", args);
|
|
56803
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
|
+
};
|
|
56804
56949
|
const scriptPath = readNonEmptyString(args, "scriptPath");
|
|
56805
56950
|
if (written.isError === true || !project.ok || scriptPath === undefined || !this.editorHasOpen(project.value.path)) {
|
|
56806
56951
|
return written;
|
|
@@ -57777,7 +57922,7 @@ async function runGodotServer() {
|
|
|
57777
57922
|
const server = new GodotServer;
|
|
57778
57923
|
await server.run();
|
|
57779
57924
|
}
|
|
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;
|
|
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;
|
|
57781
57926
|
var init_server = __esm(() => {
|
|
57782
57927
|
init_dist2();
|
|
57783
57928
|
init_stdio();
|
|
@@ -58781,7 +58926,7 @@ import {
|
|
|
58781
58926
|
writeFileSync as writeFileSync12
|
|
58782
58927
|
} from "node:fs";
|
|
58783
58928
|
import { availableParallelism as availableParallelism4 } from "node:os";
|
|
58784
|
-
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";
|
|
58785
58930
|
import process14 from "node:process";
|
|
58786
58931
|
|
|
58787
58932
|
// src/suite-fingerprint.ts
|
|
@@ -58946,6 +59091,7 @@ function getLocalVersion() {
|
|
|
58946
59091
|
}
|
|
58947
59092
|
|
|
58948
59093
|
// src/suite-run.ts
|
|
59094
|
+
var LAST_RUN_FILE = "last-run.json";
|
|
58949
59095
|
var SLOWEST_NAMED = 5;
|
|
58950
59096
|
var SILENCE_TIMES_TAKEN = 4;
|
|
58951
59097
|
function silenceWhile(running, times, floorSeconds) {
|
|
@@ -59116,14 +59262,14 @@ class RunLogs {
|
|
|
59116
59262
|
return this.kept ?? this.directory;
|
|
59117
59263
|
}
|
|
59118
59264
|
named(path) {
|
|
59119
|
-
return this.kept === null ? path : join27(this.kept,
|
|
59265
|
+
return this.kept === null ? path : join27(this.kept, relative5(this.directory, path));
|
|
59120
59266
|
}
|
|
59121
59267
|
keep() {
|
|
59122
59268
|
if (this.kept === null) {
|
|
59123
59269
|
return;
|
|
59124
59270
|
}
|
|
59125
59271
|
for (const file of readdirSync14(this.directory, { recursive: true, encoding: "utf8" })) {
|
|
59126
|
-
if (file.endsWith(".log") && file.split(
|
|
59272
|
+
if (file.endsWith(".log") && file.split(sep4).length === 2) {
|
|
59127
59273
|
mkdirSync10(dirname13(join27(this.kept, file)), { recursive: true });
|
|
59128
59274
|
copyFileSync(join27(this.directory, file), join27(this.kept, file));
|
|
59129
59275
|
}
|
|
@@ -59141,6 +59287,7 @@ async function runTestCommand(options, godotPath) {
|
|
|
59141
59287
|
const state = join27(home, ".godot", "gdharness");
|
|
59142
59288
|
const timesFile = join27(state, "suite-times.json");
|
|
59143
59289
|
const passedFile = join27(state, "suite-passed.json");
|
|
59290
|
+
const lastRunFile = join27(state, LAST_RUN_FILE);
|
|
59144
59291
|
let suites = selectedSuites(scripts, options);
|
|
59145
59292
|
const uncollected = uncollectedSaid(scripts.uncollectedUnder(searchedRoots(options)));
|
|
59146
59293
|
const wholeSuites = suites.map((suite) => scripts.suite(suite.path) ?? suite);
|
|
@@ -59188,7 +59335,7 @@ async function runTestCommand(options, godotPath) {
|
|
|
59188
59335
|
const imported = await importProject(launch);
|
|
59189
59336
|
if (imported !== null) {
|
|
59190
59337
|
logs.keep();
|
|
59191
|
-
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);
|
|
59192
59339
|
console.log(`The import failed, so no suite ran: ${imported}`);
|
|
59193
59340
|
return 1;
|
|
59194
59341
|
}
|
|
@@ -59213,7 +59360,7 @@ ${existsSync23(path) ? readFileSync22(path, "latin1") : "missing"}`)
|
|
|
59213
59360
|
if (suites.length === 0) {
|
|
59214
59361
|
console.log("Nothing has changed since every suite last passed.");
|
|
59215
59362
|
rmSync12(runDirectory, { recursive: true, force: true });
|
|
59216
|
-
writeEarlyResult(options.resultFile, startedAt, uncollected, null);
|
|
59363
|
+
writeEarlyResult({ resultFile: options.resultFile, lastRunFile, engines: options.engines }, startedAt, uncollected, null);
|
|
59217
59364
|
if (uncollected.length === 0) {
|
|
59218
59365
|
return 0;
|
|
59219
59366
|
}
|
|
@@ -59239,26 +59386,31 @@ ${existsSync23(path) ? readFileSync22(path, "latin1") : "missing"}`)
|
|
|
59239
59386
|
const functions = suites.reduce((sum, suite) => sum + suite.cases.length, 0);
|
|
59240
59387
|
const asked = unitsOf(suites, times, splitPastFor(suites, times, options.engines));
|
|
59241
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)}`;
|
|
59242
|
-
|
|
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}.`);
|
|
59243
59395
|
const windowedAnyway = headlessInAWindow(plans, units);
|
|
59244
59396
|
if (windowedAnyway.length > 0) {
|
|
59245
59397
|
const one = windowedAnyway.length === 1;
|
|
59246
|
-
|
|
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.`);
|
|
59247
59399
|
}
|
|
59248
59400
|
const split = [...new Set(units.map((unit) => unit.suite))].filter((path) => units.filter((unit) => unit.suite === path && unit.row === undefined).length > 1);
|
|
59249
59401
|
if (plans.length > 1 && split.length > 0) {
|
|
59250
|
-
|
|
59402
|
+
say(`Dealt out case by case, since ${split.length === 1 ? "it" : "each"} took more than ${past} last time: ${named(split)}.`);
|
|
59251
59403
|
}
|
|
59252
59404
|
const byRows = [
|
|
59253
59405
|
...new Set(units.flatMap((unit) => unit.row === undefined ? [] : [`${unit.suite} > ${unit.cases[0] ?? ""}`]))
|
|
59254
59406
|
];
|
|
59255
59407
|
if (plans.length > 1 && byRows.length > 0) {
|
|
59256
|
-
|
|
59408
|
+
say(`Dealt out row by row, since ${byRows.length === 1 ? "it" : "each"} took more than ${past} last time: ${named(byRows)}.`);
|
|
59257
59409
|
}
|
|
59258
59410
|
const keptWhole = units.flatMap((unit) => unit.cost > splitPast && !unit.windowed && suites.some((s) => s.path === unit.suite && s.together) ? [unit.suite] : []);
|
|
59259
59411
|
if (options.order === null && room.engines > 1 && keptWhole.length > 0) {
|
|
59260
59412
|
const one = keptWhole.length === 1;
|
|
59261
|
-
|
|
59413
|
+
say(`Kept whole in one engine, since ${one ? "it declares" : "each declares"} GDHARNESS_TOGETHER: ${named(keptWhole)}.`);
|
|
59262
59414
|
}
|
|
59263
59415
|
const lanes = [...new Set(plans.map((plan) => plan.lane))].map((lane) => plans.flatMap((plan, index) => plan.lane === lane ? [{ plan, index }] : []));
|
|
59264
59416
|
const taken = [];
|
|
@@ -59271,11 +59423,11 @@ ${existsSync23(path) ? readFileSync22(path, "latin1") : "missing"}`)
|
|
|
59271
59423
|
}
|
|
59272
59424
|
const shared = options.order === null && lanes.length > 1 && taken.length < lanes.length;
|
|
59273
59425
|
if (shared) {
|
|
59274
|
-
|
|
59426
|
+
say(sharesSaid(taken.length, lanes.length));
|
|
59275
59427
|
}
|
|
59276
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);
|
|
59277
59429
|
if (ran.some((engine) => engine.verdict.starved) && ran.some((engine) => !engine.verdict.passed)) {
|
|
59278
|
-
|
|
59430
|
+
say("An engine ran out of memory, so every engine that failed runs again, one at a time.");
|
|
59279
59431
|
const again = [];
|
|
59280
59432
|
for (const engine of ran) {
|
|
59281
59433
|
again.push(engine.verdict.passed ? engine : await runEngine2(launch, engine.plan, engine.index, times, "again"));
|
|
@@ -59285,7 +59437,13 @@ ${existsSync23(path) ? readFileSync22(path, "latin1") : "missing"}`)
|
|
|
59285
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}`))))));
|
|
59286
59438
|
const across = rowsAcross(ran.map((engine) => ({ handed: engine.plan.suites, printed: engine.printed })));
|
|
59287
59439
|
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,
|
|
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
|
+
});
|
|
59289
59447
|
} finally {
|
|
59290
59448
|
process14.off("SIGINT", interrupted);
|
|
59291
59449
|
process14.off("SIGTERM", interrupted);
|
|
@@ -59450,7 +59608,7 @@ async function runEngine2(launch, plan, index, times, again = "", slot = null) {
|
|
|
59450
59608
|
...plan.windowed ? [] : ["--ignoreHeadlessMode"],
|
|
59451
59609
|
"-c",
|
|
59452
59610
|
"-rd",
|
|
59453
|
-
`res://${
|
|
59611
|
+
`res://${relative5(launch.projectPath, join27(directory, "reports")).split(sep4).join("/")}`,
|
|
59454
59612
|
"-rc",
|
|
59455
59613
|
"1",
|
|
59456
59614
|
"--",
|
|
@@ -59492,6 +59650,7 @@ async function runEngine2(launch, plan, index, times, again = "", slot = null) {
|
|
|
59492
59650
|
printed,
|
|
59493
59651
|
verdict: written === null ? judged : { ...judged, passed: false, faults: [...judged.faults, written] },
|
|
59494
59652
|
log,
|
|
59653
|
+
began,
|
|
59495
59654
|
seconds: (Date.now() - began) / 1000,
|
|
59496
59655
|
again: again !== ""
|
|
59497
59656
|
};
|
|
@@ -59502,11 +59661,11 @@ function reportsFault(reports) {
|
|
|
59502
59661
|
if (files.length === 0) {
|
|
59503
59662
|
return null;
|
|
59504
59663
|
}
|
|
59505
|
-
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("/"));
|
|
59506
59665
|
const more = files.length - shown.length;
|
|
59507
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`;
|
|
59508
59667
|
}
|
|
59509
|
-
function report(ran, across, uncollected, startedAt, logs, printOutput,
|
|
59668
|
+
function report(ran, across, uncollected, startedAt, logs, printOutput, targets) {
|
|
59510
59669
|
if (printOutput) {
|
|
59511
59670
|
for (const engine of ran) {
|
|
59512
59671
|
console.log("");
|
|
@@ -59583,27 +59742,31 @@ ${readFileSync22(engine.log, "utf8")}`);
|
|
|
59583
59742
|
} else {
|
|
59584
59743
|
logs.keep();
|
|
59585
59744
|
}
|
|
59586
|
-
|
|
59587
|
-
|
|
59588
|
-
|
|
59589
|
-
|
|
59590
|
-
|
|
59591
|
-
|
|
59592
|
-
|
|
59593
|
-
|
|
59594
|
-
|
|
59595
|
-
|
|
59596
|
-
|
|
59597
|
-
|
|
59598
|
-
|
|
59599
|
-
|
|
59600
|
-
|
|
59601
|
-
|
|
59602
|
-
|
|
59603
|
-
|
|
59604
|
-
|
|
59605
|
-
})
|
|
59606
|
-
|
|
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
|
+
});
|
|
59607
59770
|
if (passed) {
|
|
59608
59771
|
console.log(`Passed: ${totals}`);
|
|
59609
59772
|
return 0;
|
|
@@ -59613,16 +59776,23 @@ ${readFileSync22(engine.log, "utf8")}`);
|
|
|
59613
59776
|
console.log(`Every engine's output is kept under ${logs.shown} until ${String(KEPT_RUN_DIRECTORIES)} later runs have failed.`);
|
|
59614
59777
|
return 1;
|
|
59615
59778
|
}
|
|
59616
|
-
function writeEarlyResult(
|
|
59617
|
-
|
|
59618
|
-
|
|
59619
|
-
|
|
59620
|
-
|
|
59621
|
-
|
|
59622
|
-
|
|
59623
|
-
|
|
59624
|
-
|
|
59625
|
-
|
|
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);
|
|
59626
59796
|
}
|
|
59627
59797
|
}
|
|
59628
59798
|
function printUncollected(uncollected) {
|