gdharness 1.0.22 → 1.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/cli.js
CHANGED
|
@@ -16369,7 +16369,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16369
16369
|
},
|
|
16370
16370
|
{
|
|
16371
16371
|
name: "editor_output",
|
|
16372
|
-
description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. For a run the editor plays the reports come from the game itself, through the runtime addon, since the editor's debugger relays what a game prints and not what it reports; a played game without the runtime addon answers here with its prints alone. Answers with the counts and the verdict as well as the entries. heldAt is three answers: null for a run this server knows is not held, running or over, an object for one it knows is held at a breakpoint, whether the adapter said so or the runtime did by accepting a connection and never answering, and absent with heldUnknown beside it for a server that connected to the debugger after the stop and has no runtime to ask, since the adapter reports a stop only to the sessions connected when it happened. A run that has quit still answers here, with running false and its exit code, until the next one starts. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one measured elsewhere reported a game as playing for fifteen seconds after its process had been ended from outside. editor_status names that game under runtimes, which does not depend on the editor answering. op: \"editor\" answers about the editor itself instead, which is a different log and the only way anything here can read what an editor printed: its console reaches no plugin, so the addon cannot scrape it, and an editor gdharness opened is asked for a log file as it starts. That covers the whole session from the editor's first line, which is where the output worth reading usually is, since the class cache, the plugins and the language server all say what they have to say before any harness has connected. The answer carries the entries in the order they were printed and, beside them under repeated, each message shape several lines share: how many, what varied in each slot of it and how many different values stood there, the first occurrence, and the ordinary lines above the group, found by walking back past the group rather than by counting lines. An editor somebody opened by hand has no console here and is refused saying so rather than answered empty, because an editor that printed nothing and an editor nobody captured are not the same thing.",
|
|
16372
|
+
description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. For a run the editor plays the reports come from the game itself, through the runtime addon, since the editor's debugger relays what a game prints and not what it reports; a played game without the runtime addon answers here with its prints alone. Answers with the counts and the verdict as well as the entries. heldAt is three answers: null for a run this server knows is not held, running or over, an object for one it knows is held at a breakpoint, whether the adapter said so or the runtime did by accepting a connection and never answering, and absent with heldUnknown beside it for a server that connected to the debugger after the stop and has no runtime to ask, since the adapter reports a stop only to the sessions connected when it happened. A run that has quit still answers here, with running false and its exit code, until the next one starts; a run ended by a signal has no exit code, and exitSignal names the signal instead. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one measured elsewhere reported a game as playing for fifteen seconds after its process had been ended from outside. editor_status names that game under runtimes, which does not depend on the editor answering. op: \"editor\" answers about the editor itself instead, which is a different log and the only way anything here can read what an editor printed: its console reaches no plugin, so the addon cannot scrape it, and an editor gdharness opened is asked for a log file as it starts. That covers the whole session from the editor's first line, which is where the output worth reading usually is, since the class cache, the plugins and the language server all say what they have to say before any harness has connected. The answer carries the entries in the order they were printed and, beside them under repeated, each message shape several lines share: how many, what varied in each slot of it and how many different values stood there, the first occurrence, and the ordinary lines above the group, found by walking back past the group rather than by counting lines. An editor somebody opened by hand has no console here and is refused saying so rather than answered empty, because an editor that printed nothing and an editor nobody captured are not the same thing.",
|
|
16373
16373
|
parameters: {
|
|
16374
16374
|
severity: {
|
|
16375
16375
|
type: "string",
|
|
@@ -16415,7 +16415,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16415
16415
|
},
|
|
16416
16416
|
{
|
|
16417
16417
|
name: "editor_status",
|
|
16418
|
-
description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and whether a game with the runtime addon is reachable: runtimeConnected counts the games of the project this server serves, and runtimes lists every game on the machine with ofThisProject saying which is which, since a game of another project is reached only by naming its projectPath. playingInEditor is null when the editor was not asked or did not answer, which is not the same as a no: with no editor connected there is nobody to ask, and an editor that does not serve the question is given a moment and then left. A game can be playing while this is null, so read it as unanswered rather than as nothing playing, and use processActive and runtimes, which come from elsewhere and do not depend on the editor answering. Each game under runtimes is pinged with a short wait, so one held at a breakpoint is listed as unreachable rather than making this call wait out the runtime timeout; for the run this server is reporting on, heldAt says whether that is what it is, with the same three answers editor_output gives. A game the editor played announces the editor under editorPid, and so does whatever that game started for itself; a game some other server started announces none, which is how the two are told apart when both are running from one project. When connected is false, mayYetConnect says whether that is final: the editor dials this server rather than the other way round and backs off between tries, so for the first half-minute after a server starts, false means \"not reached yet\" as often as it means \"no editor\". True is worth waiting out; false is an editor that is not there. False beside restartInterrupted is one that an editor_launch restart on a previous server asked to quit, where the launch half never ran because that server was ended in between, usually by the reconnect the restart was taken for; editor_launch open finishes it. listeningSince is when this server took the bridge port, which is what that window is measured from and what a caller wanting its own window should read. An editor this server opened counts for as long as its process lives, however far past that window the import runs, and awaitingLaunchedEditor names its pid so the two reasons for true are told apart. settingsDropped appears here once when the editor this server opened saved project.godot on its way in, which is the same reading editor_launch restart makes in its own answer. A game that has announced itself in a protocol this server does not speak is listed under unreadable rather than left out, with the protocol it speaks and whether the server or the addon is the older half. This happens during an upgrade that moves the runtime protocol, which most do not: installing replaces the addon on disk as soon as the pin moves, while a server that is already running stays on the version it started with, so the two speak different protocols until the server is reconnected. Those games are real and running, but no tool here can reach them until the halves match, so runtimes stays empty and runtimeConnected stays false. An upgrade that leaves the protocol alone opens no such window and this list stays empty through it. addonIsStale is decided by the code of the editor addons rather than their version, so an editor started before an upgrade that left that code alone reports the older addonVersion, is not stale, and addonNote says so: it needs no restart. port is the one the editor bridge is on; when that is not the one this server was configured with, portWanted names the configured one and portNote says why it moved, which
|
|
16418
|
+
description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and whether a game with the runtime addon is reachable: runtimeConnected counts the games of the project this server serves, and runtimes lists every game on the machine with ofThisProject saying which is which, since a game of another project is reached only by naming its projectPath. playingInEditor is null when the editor was not asked or did not answer, which is not the same as a no: with no editor connected there is nobody to ask, and an editor that does not serve the question is given a moment and then left. A game can be playing while this is null, so read it as unanswered rather than as nothing playing, and use processActive and runtimes, which come from elsewhere and do not depend on the editor answering. Each game under runtimes is pinged with a short wait, so one held at a breakpoint is listed as unreachable rather than making this call wait out the runtime timeout; for the run this server is reporting on, heldAt says whether that is what it is, with the same three answers editor_output gives. A game the editor played announces the editor under editorPid, and so does whatever that game started for itself; a game some other server started announces none, which is how the two are told apart when both are running from one project. When connected is false, mayYetConnect says whether that is final: the editor dials this server rather than the other way round and backs off between tries, so for the first half-minute after a server starts, false means \"not reached yet\" as often as it means \"no editor\". True is worth waiting out; false is an editor that is not there. False beside restartInterrupted is one that an editor_launch restart on a previous server asked to quit, where the launch half never ran because that server was ended in between, usually by the reconnect the restart was taken for; editor_launch open finishes it. listeningSince is when this server took the bridge port, which is what that window is measured from and what a caller wanting its own window should read. An editor this server opened counts for as long as its process lives, however far past that window the import runs, and awaitingLaunchedEditor names its pid so the two reasons for true are told apart. settingsDropped appears here once when the editor this server opened saved project.godot on its way in, which is the same reading editor_launch restart makes in its own answer. A game that has announced itself in a protocol this server does not speak is listed under unreadable rather than left out, with the protocol it speaks and whether the server or the addon is the older half. This happens during an upgrade that moves the runtime protocol, which most do not: installing replaces the addon on disk as soon as the pin moves, while a server that is already running stays on the version it started with, so the two speak different protocols until the server is reconnected. Those games are real and running, but no tool here can reach them until the halves match, so runtimes stays empty and runtimeConnected stays false. An upgrade that leaves the protocol alone opens no such window and this list stays empty through it. addonIsStale is decided by the code of the editor addons rather than their version, so an editor started before an upgrade that left that code alone reports the older addonVersion, is not stale, and addonNote says so: it needs no restart. port is the one the editor bridge is on; when that is not the one this server was configured with, portWanted names the configured one and portNote says why it moved and, when it was the server this one replaced, which pid kept it. A server replacing another of its project that holds the configured port waits for it to stand down and takes the port over, which takes a few seconds; bridgeHandover says so while it waits, and it moves only if that server has not let go within the wait.",
|
|
16419
16419
|
parameters: {},
|
|
16420
16420
|
requires: []
|
|
16421
16421
|
},
|
|
@@ -16489,7 +16489,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16489
16489
|
property: {
|
|
16490
16490
|
type: "string",
|
|
16491
16491
|
ops: ["property", "find"],
|
|
16492
|
-
description: 'property: which one to read. find: read this one off every node matched, so a panel of labels is one call rather than one per label. Colons read through what a node holds, "_game:clock:speed", which is where a game keeps what is worth asking about. A number or a key steps into a list or a map, "_game:run:roster:0:traits", which is how the lists a game keeps its state in are walked: a roster, a board, an in-tray. A negative number counts from the end. A step written as a call, "get_viewport():gui_get_focus_owner()", calls a method that takes no arguments and walks into what it returned, which is how a question only a method answers is read in one call: which control holds the focus in the viewport this one is in. A step that is not there is named, and says what was there instead.'
|
|
16492
|
+
description: 'property: which one to read. find: read this one off every node matched, so a panel of labels is one call rather than one per label. Colons read through what a node holds, "_game:clock:speed", which is where a game keeps what is worth asking about. A number or a key steps into a list or a map, "_game:run:roster:0:traits", which is how the lists a game keeps its state in are walked: a roster, a board, an in-tray. A negative number counts from the end. A step written as a call, "get_viewport():gui_get_focus_owner()", calls a method that takes no arguments and walks into what it returned, which is how a question only a method answers is read in one call: which control holds the focus in the viewport this one is in. A list or a map answers the calls that read it and take no arguments, "_game:run:board:size()" or "_game:inbox:keys()", and not those that change it, such as clear() or sort(). A step that is not there is named, and says what was there instead.'
|
|
16493
16493
|
},
|
|
16494
16494
|
depth: { type: "number", ops: ["tree"], description: "tree: levels to descend. Default 3." },
|
|
16495
16495
|
includeProperties: {
|
|
@@ -16535,6 +16535,10 @@ var init_tool_definitions = __esm(() => {
|
|
|
16535
16535
|
items: { type: "string" },
|
|
16536
16536
|
ops: ["metrics"],
|
|
16537
16537
|
description: "metrics: which to read. Default all."
|
|
16538
|
+
},
|
|
16539
|
+
timeoutMs: {
|
|
16540
|
+
type: "number",
|
|
16541
|
+
description: "How long to wait for the answer before answering pending with a requestId, which runtime_invoke op result collects the reply by. Default 10000, or GDHARNESS_RUNTIME_TIMEOUT_MS."
|
|
16538
16542
|
}
|
|
16539
16543
|
},
|
|
16540
16544
|
requires: [],
|
|
@@ -16562,20 +16566,24 @@ var init_tool_definitions = __esm(() => {
|
|
|
16562
16566
|
},
|
|
16563
16567
|
{
|
|
16564
16568
|
name: "runtime_invoke",
|
|
16565
|
-
description: "Sets a property or calls a method on a node in the running game. Needs the game running with the runtime addon.",
|
|
16569
|
+
description: "Sets a property or calls a method on a node in the running game. Needs the game running with the runtime addon. A call that takes longer than timeoutMs is not cancelled: the answer is pending: true with a requestId, the call goes on and does everything it was asked, and op result with that requestId collects its reply once it comes.",
|
|
16566
16570
|
parameters: {
|
|
16567
16571
|
projectPath: RUNNING_PROJECT_PATH,
|
|
16568
16572
|
pid: RUNNING_PID,
|
|
16569
|
-
nodePath: {
|
|
16573
|
+
nodePath: {
|
|
16574
|
+
type: "string",
|
|
16575
|
+
ops: ["set", "call"],
|
|
16576
|
+
description: 'Absolute node path, such as "/root/Main/Player".'
|
|
16577
|
+
},
|
|
16570
16578
|
property: {
|
|
16571
16579
|
type: "string",
|
|
16572
16580
|
ops: ["set"],
|
|
16573
|
-
description: 'set: which one to write. Colons write through what a node holds, "_game:run:day", and a number or a key steps into a list or a map on the way, "_game:run:roster:0:name", a negative number counting from the end, and a step written as a call, "get_viewport():gui_embed_subwindows", walks through what a method taking no arguments returned. The answer reads back off the same holder,
|
|
16581
|
+
description: 'set: which one to write. Colons write through what a node holds, "_game:run:day", and a number or a key steps into a list or a map on the way, "_game:run:roster:0:name", a negative number counting from the end, and a step written as a call, "get_viewport():gui_embed_subwindows", walks through what a method taking no arguments returned. The answer reads back off the same holder, and a write that leaves the property as it was, because the engine would not take it, is refused and says what the property still holds.'
|
|
16574
16582
|
},
|
|
16575
16583
|
value: {
|
|
16576
16584
|
ops: ["set"],
|
|
16577
16585
|
blank: true,
|
|
16578
|
-
description: `set: the value, fitted to the property's type. "" writes an empty string. A property typed as or holding an object takes a path naming one the game holds, the way call's args do, and holds that instance.`
|
|
16586
|
+
description: `set: the value, fitted to the property's type. "" writes an empty string. A property typed as or holding an object takes a path naming one the game holds, the way call's args do, and holds that instance. A typed list or map, such as Array[int] or Dictionary[String, int], takes a JSON list or object with each element fitted to its element type, and an element that cannot become one is refused by its index or key.`
|
|
16579
16587
|
},
|
|
16580
16588
|
method: {
|
|
16581
16589
|
type: "string",
|
|
@@ -16585,18 +16593,32 @@ var init_tool_definitions = __esm(() => {
|
|
|
16585
16593
|
args: {
|
|
16586
16594
|
type: "array",
|
|
16587
16595
|
ops: ["call"],
|
|
16588
|
-
description: `call: the arguments, fitted to the method's parameter types. A parameter typed as an object takes a path naming one the game holds and is handed that instance: a colon path read from nodePath, "_game:run:wares:3", or one starting at a node, "/root/Main/Hud" or "/root/Main:_game:run". A path that reaches no object, or an object of another class than the parameter declares, is refused.`
|
|
16596
|
+
description: `call: the arguments, fitted to the method's parameter types. A parameter typed as an object takes a path naming one the game holds and is handed that instance: a colon path read from nodePath, "_game:run:wares:3", or one starting at a node, "/root/Main/Hud" or "/root/Main:_game:run". A path that reaches no object, or an object of another class than the parameter declares, is refused. A parameter typed as a list or map, such as Array[int] or Array[Gear], is built from a JSON list or object element by element, objects named by their paths.`
|
|
16597
|
+
},
|
|
16598
|
+
timeoutMs: {
|
|
16599
|
+
type: "number",
|
|
16600
|
+
ops: ["set", "call"],
|
|
16601
|
+
description: "set, call: how long to wait for the answer before answering pending with a requestId, which does not stop the call. Default 10000, or GDHARNESS_RUNTIME_TIMEOUT_MS. Give a longer one for a call known to take a while, such as one that plays many turns in one go."
|
|
16602
|
+
},
|
|
16603
|
+
requestId: {
|
|
16604
|
+
type: "number",
|
|
16605
|
+
ops: ["result"],
|
|
16606
|
+
description: "result: the requestId a pending answer gave."
|
|
16589
16607
|
}
|
|
16590
16608
|
},
|
|
16591
|
-
requires: [
|
|
16609
|
+
requires: [],
|
|
16592
16610
|
operations: {
|
|
16593
16611
|
set: {
|
|
16594
16612
|
summary: "set a property, on a node or on an object it holds",
|
|
16595
|
-
requires: ["property", "value"]
|
|
16613
|
+
requires: ["nodePath", "property", "value"]
|
|
16596
16614
|
},
|
|
16597
16615
|
call: {
|
|
16598
16616
|
summary: "call a method, on a node or on an object it holds, and return its result",
|
|
16599
|
-
requires: ["method"]
|
|
16617
|
+
requires: ["nodePath", "method"]
|
|
16618
|
+
},
|
|
16619
|
+
result: {
|
|
16620
|
+
summary: "collect the reply to a set or call whose wait ran out, by the requestId it answered with",
|
|
16621
|
+
requires: ["requestId"]
|
|
16600
16622
|
}
|
|
16601
16623
|
}
|
|
16602
16624
|
},
|
|
@@ -31916,14 +31938,40 @@ function chooseRuntime(endpoints, projectPath, unspoken = [], pid) {
|
|
|
31916
31938
|
problem: `Several games are running: ${endpoints.map(describe2).join("; ")}. Pass pid to choose one, or projectPath when the project you mean is running only one.`
|
|
31917
31939
|
};
|
|
31918
31940
|
}
|
|
31919
|
-
function
|
|
31941
|
+
function keepLate(id, answer) {
|
|
31942
|
+
lateAnswers.delete(id);
|
|
31943
|
+
lateAnswers.set(id, answer);
|
|
31944
|
+
while (lateAnswers.size > LATE_KEPT) {
|
|
31945
|
+
const oldest = lateAnswers.keys().next().value;
|
|
31946
|
+
if (oldest === undefined) {
|
|
31947
|
+
break;
|
|
31948
|
+
}
|
|
31949
|
+
lateAnswers.delete(oldest);
|
|
31950
|
+
}
|
|
31951
|
+
}
|
|
31952
|
+
function lateAnswer(id) {
|
|
31953
|
+
return lateAnswers.get(id);
|
|
31954
|
+
}
|
|
31955
|
+
function runtimeRequest(endpoint, command, params, timeoutMs, keepListening = false) {
|
|
31920
31956
|
const id = nextRequestId++;
|
|
31957
|
+
const sentAt = Date.now();
|
|
31921
31958
|
return new Promise((settle) => {
|
|
31922
31959
|
let done = false;
|
|
31960
|
+
let late = false;
|
|
31923
31961
|
let buffered = "";
|
|
31924
31962
|
let welcomed = false;
|
|
31925
31963
|
const socket = createConnection({ port: endpoint.port, host: endpoint.address });
|
|
31964
|
+
let lateTimer = null;
|
|
31926
31965
|
const finish = (reply) => {
|
|
31966
|
+
if (late) {
|
|
31967
|
+
if (lateTimer !== null) {
|
|
31968
|
+
clearTimeout(lateTimer);
|
|
31969
|
+
}
|
|
31970
|
+
socket.destroy();
|
|
31971
|
+
keepLate(id, reply.ok || reply.reason === "error" ? { state: "answered", command, sentAt, answeredAt: Date.now(), reply } : { state: "never", command, sentAt, why: reply.message });
|
|
31972
|
+
late = false;
|
|
31973
|
+
return;
|
|
31974
|
+
}
|
|
31927
31975
|
if (done) {
|
|
31928
31976
|
return;
|
|
31929
31977
|
}
|
|
@@ -31933,10 +31981,32 @@ function runtimeRequest(endpoint, command, params, timeoutMs) {
|
|
|
31933
31981
|
settle(reply);
|
|
31934
31982
|
};
|
|
31935
31983
|
const timer = setTimeout(() => {
|
|
31936
|
-
|
|
31984
|
+
const still = welcomed ? `The game (${describe2(endpoint)}) has the request and did not answer '${command}' within ${timeoutMs}ms.` : `The game (${describe2(endpoint)}) did not answer '${command}' within ${timeoutMs}ms and had not taken the request: its frame has not ended since it was sent, which a long frame or a breakpoint does, and the request runs when it does.`;
|
|
31985
|
+
if (!keepListening) {
|
|
31986
|
+
finish({
|
|
31987
|
+
ok: false,
|
|
31988
|
+
reason: "busy",
|
|
31989
|
+
message: `${still} It is either working through something long or held at a breakpoint.`
|
|
31990
|
+
});
|
|
31991
|
+
return;
|
|
31992
|
+
}
|
|
31993
|
+
done = true;
|
|
31994
|
+
late = true;
|
|
31995
|
+
keepLate(id, { state: "waiting", command, sentAt });
|
|
31996
|
+
lateTimer = setTimeout(() => {
|
|
31997
|
+
finish({
|
|
31998
|
+
ok: false,
|
|
31999
|
+
reason: "busy",
|
|
32000
|
+
message: `No answer came within ${LATE_ANSWER_MS / 60000} minutes.`
|
|
32001
|
+
});
|
|
32002
|
+
}, LATE_ANSWER_MS);
|
|
32003
|
+
lateTimer.unref();
|
|
32004
|
+
socket.unref();
|
|
32005
|
+
settle({
|
|
31937
32006
|
ok: false,
|
|
31938
32007
|
reason: "busy",
|
|
31939
|
-
|
|
32008
|
+
requestId: id,
|
|
32009
|
+
message: `${still} Nothing was cancelled: a call that takes longer than the wait goes on and does everything it was asked, so read the state back rather than repeating it. runtime_invoke op result with requestId ${id} answers with its reply once it comes, and timeoutMs waits longer next time. A game held at a breakpoint instead is one debug_state stack answers about.`
|
|
31940
32010
|
});
|
|
31941
32011
|
}, timeoutMs);
|
|
31942
32012
|
socket.setEncoding("utf8");
|
|
@@ -31948,7 +32018,7 @@ function runtimeRequest(endpoint, command, params, timeoutMs) {
|
|
|
31948
32018
|
buffered += chunk;
|
|
31949
32019
|
let newline = buffered.indexOf(`
|
|
31950
32020
|
`);
|
|
31951
|
-
while (newline !== -1 && !done) {
|
|
32021
|
+
while (newline !== -1 && (!done || late)) {
|
|
31952
32022
|
const line = buffered.slice(0, newline).trim();
|
|
31953
32023
|
buffered = buffered.slice(newline + 1);
|
|
31954
32024
|
newline = buffered.indexOf(`
|
|
@@ -32003,7 +32073,7 @@ function runtimeRequest(endpoint, command, params, timeoutMs) {
|
|
|
32003
32073
|
});
|
|
32004
32074
|
});
|
|
32005
32075
|
}
|
|
32006
|
-
var RUNTIME_PROTOCOL = 2, ANNOUNCEMENT_PATTERN, ERROR_REPORT_PATTERN, ERROR_REPORT_KEEP_MS, GONE_WINDOW_MS = 120000, GONE_KEPT = 32, gone, STARTED_AFTER_ANNOUNCING_MS = 2000, CONFIRMED_FOR_MS = 60000, JUDGED_AFTER_MS = 60000, verdicts, ENDED_WITHIN_MS = 3000, LOOK_EVERY_MS = 50, ANNOUNCE_BUDGET_MS = 5000, nextRequestId = 1;
|
|
32076
|
+
var RUNTIME_PROTOCOL = 2, ANNOUNCEMENT_PATTERN, ERROR_REPORT_PATTERN, ERROR_REPORT_KEEP_MS, GONE_WINDOW_MS = 120000, GONE_KEPT = 32, gone, STARTED_AFTER_ANNOUNCING_MS = 2000, CONFIRMED_FOR_MS = 60000, JUDGED_AFTER_MS = 60000, verdicts, ENDED_WITHIN_MS = 3000, LOOK_EVERY_MS = 50, ANNOUNCE_BUDGET_MS = 5000, nextRequestId = 1, LATE_ANSWER_MS, LATE_KEPT = 50, lateAnswers;
|
|
32007
32077
|
var init_runtime_client = __esm(() => {
|
|
32008
32078
|
init_paths();
|
|
32009
32079
|
init_process_children();
|
|
@@ -32012,6 +32082,8 @@ var init_runtime_client = __esm(() => {
|
|
|
32012
32082
|
ERROR_REPORT_KEEP_MS = 60 * 60 * 1000;
|
|
32013
32083
|
gone = [];
|
|
32014
32084
|
verdicts = new Map;
|
|
32085
|
+
LATE_ANSWER_MS = 30 * 60000;
|
|
32086
|
+
lateAnswers = new Map;
|
|
32015
32087
|
});
|
|
32016
32088
|
|
|
32017
32089
|
// src/boot-note.ts
|
|
@@ -36145,7 +36217,10 @@ var init_godot_bridge = __esm(() => {
|
|
|
36145
36217
|
get port() {
|
|
36146
36218
|
return this.boundPort ?? this.wantedPort;
|
|
36147
36219
|
}
|
|
36148
|
-
|
|
36220
|
+
get configuredPort() {
|
|
36221
|
+
return this.wantedPort;
|
|
36222
|
+
}
|
|
36223
|
+
async start(moveIfHeld = true) {
|
|
36149
36224
|
if (this.httpServer) {
|
|
36150
36225
|
return;
|
|
36151
36226
|
}
|
|
@@ -36153,7 +36228,7 @@ var init_godot_bridge = __esm(() => {
|
|
|
36153
36228
|
await this.listenOn(this.wantedPort);
|
|
36154
36229
|
} catch (error) {
|
|
36155
36230
|
const held = error instanceof Error && "code" in error && error.code === "EADDRINUSE";
|
|
36156
|
-
if (!held || !this.mayMove) {
|
|
36231
|
+
if (!held || !this.mayMove || !moveIfHeld) {
|
|
36157
36232
|
throw error;
|
|
36158
36233
|
}
|
|
36159
36234
|
this.log("warn", `Editor bridge port ${this.wantedPort} is held; taking another one.`);
|
|
@@ -36956,6 +37031,65 @@ function whyNoReport(printed, asked) {
|
|
|
36956
37031
|
}
|
|
36957
37032
|
return printed.some((line) => NOTHING_RAN.test(line)) ? `no test cases found at ${asked}` : null;
|
|
36958
37033
|
}
|
|
37034
|
+
function unspelled(text) {
|
|
37035
|
+
return text.replace(/<(?:LF|CR|TAB|BS|FF|VT|BEL|ESC|DEL|0x[0-9A-F]{2})>/g, (spelling) => spelling.startsWith("<0x") ? String.fromCharCode(Number.parseInt(spelling.slice(3, 5), 16)) : SPELLED[spelling] ?? spelling);
|
|
37036
|
+
}
|
|
37037
|
+
function readDiff(printed) {
|
|
37038
|
+
let merged = "";
|
|
37039
|
+
let actual = "";
|
|
37040
|
+
let background = null;
|
|
37041
|
+
let last = 0;
|
|
37042
|
+
const take = (text) => {
|
|
37043
|
+
merged += text;
|
|
37044
|
+
if (background === null) {
|
|
37045
|
+
actual += text;
|
|
37046
|
+
} else if (background === "added") {
|
|
37047
|
+
actual += unspelled(text);
|
|
37048
|
+
}
|
|
37049
|
+
};
|
|
37050
|
+
for (const match of printed.matchAll(COLOUR2)) {
|
|
37051
|
+
take(printed.slice(last, match.index));
|
|
37052
|
+
last = match.index + match[0].length;
|
|
37053
|
+
const codes = (match[1] ?? "").split(";");
|
|
37054
|
+
if (codes[0] === "48" && codes[1] === "2") {
|
|
37055
|
+
background = Number(codes[2]) > Number(codes[3]) ? "missing" : "added";
|
|
37056
|
+
} else if (codes[0] === "0" || codes[0] === "") {
|
|
37057
|
+
background = null;
|
|
37058
|
+
}
|
|
37059
|
+
}
|
|
37060
|
+
take(printed.slice(last));
|
|
37061
|
+
return { merged, actual };
|
|
37062
|
+
}
|
|
37063
|
+
function withActualsPrinted(failed, printed) {
|
|
37064
|
+
const diffs = [...printed.replaceAll(`\r
|
|
37065
|
+
`, `
|
|
37066
|
+
`).matchAll(PRINTED_ACTUAL)].map((match) => ({
|
|
37067
|
+
at: match[3] ?? "",
|
|
37068
|
+
...readDiff(match[1] ?? "")
|
|
37069
|
+
}));
|
|
37070
|
+
return failed.map((entry) => {
|
|
37071
|
+
if (entry.detail === null) {
|
|
37072
|
+
return entry;
|
|
37073
|
+
}
|
|
37074
|
+
let detail = entry.detail;
|
|
37075
|
+
for (const diff of diffs) {
|
|
37076
|
+
const wrong = ` but was
|
|
37077
|
+
'${diff.merged.replaceAll("\r", "")}'
|
|
37078
|
+
at ${diff.at}`;
|
|
37079
|
+
if (detail.includes(wrong)) {
|
|
37080
|
+
detail = detail.replace(wrong, () => ` but was
|
|
37081
|
+
'${diff.actual}'
|
|
37082
|
+
at ${diff.at}`);
|
|
37083
|
+
}
|
|
37084
|
+
}
|
|
37085
|
+
const alike = /Expecting:\n '([\s\S]*)'\n but was\n '\1'\n\tat /.test(detail);
|
|
37086
|
+
if (alike) {
|
|
37087
|
+
detail = `${detail}
|
|
37088
|
+
${BOTH_SIDES_ALIKE_NOTE}`;
|
|
37089
|
+
}
|
|
37090
|
+
return detail === entry.detail ? entry : { ...entry, detail };
|
|
37091
|
+
});
|
|
37092
|
+
}
|
|
36959
37093
|
function orphansPrinted(printed) {
|
|
36960
37094
|
const suites = [];
|
|
36961
37095
|
let total = null;
|
|
@@ -36986,7 +37120,7 @@ function orphansPrinted(printed) {
|
|
|
36986
37120
|
suites
|
|
36987
37121
|
};
|
|
36988
37122
|
}
|
|
36989
|
-
var MalformedReportError, ENTITIES, ATTRIBUTE, NOTHING_RAN, NO_SUCH_PATH, RUNNING_SUITE, ORPHANS, OVERALL = "Overall Summary";
|
|
37123
|
+
var MalformedReportError, ENTITIES, ATTRIBUTE, NOTHING_RAN, NO_SUCH_PATH, ESCAPE, PRINTED_ACTUAL, COLOUR2, SPELLED, BOTH_SIDES_ALIKE_NOTE = "[gdharness: the expected and the actual printed alike because gdUnit4 drops the marks that tell them apart when it writes its report, and the runner printed no copy of this one, so neither side above is the value the assertion found.]", RUNNING_SUITE, ORPHANS, OVERALL = "Overall Summary";
|
|
36990
37124
|
var init_junit = __esm(() => {
|
|
36991
37125
|
MalformedReportError = class MalformedReportError extends Error {
|
|
36992
37126
|
constructor(message) {
|
|
@@ -36998,6 +37132,21 @@ var init_junit = __esm(() => {
|
|
|
36998
37132
|
ATTRIBUTE = /\s*([^\s=/>]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/y;
|
|
36999
37133
|
NOTHING_RAN = /no test cases found/i;
|
|
37000
37134
|
NO_SUCH_PATH = /given directory or file does not exists:\s*(\S+)/i;
|
|
37135
|
+
ESCAPE = String.fromCharCode(27);
|
|
37136
|
+
PRINTED_ACTUAL = new RegExp(` but was\\n '([\\s\\S]*?)'((?:${ESCAPE}\\[[0-9;]*m)*)\\tat ('[^'\\n]*' in \\S+:\\d+)`, "g");
|
|
37137
|
+
COLOUR2 = new RegExp(`${ESCAPE}\\[([0-9;]*)m`, "g");
|
|
37138
|
+
SPELLED = {
|
|
37139
|
+
"<LF>": `
|
|
37140
|
+
`,
|
|
37141
|
+
"<CR>": "\r",
|
|
37142
|
+
"<TAB>": "\t",
|
|
37143
|
+
"<BS>": "\b",
|
|
37144
|
+
"<FF>": "\f",
|
|
37145
|
+
"<VT>": "\v",
|
|
37146
|
+
"<BEL>": "\x07",
|
|
37147
|
+
"<ESC>": ESCAPE,
|
|
37148
|
+
"<DEL>": ""
|
|
37149
|
+
};
|
|
37001
37150
|
RUNNING_SUITE = /Run Test Suite:\s*(\S+)/;
|
|
37002
37151
|
ORPHANS = /(\d+)\s+orphans/;
|
|
37003
37152
|
});
|
|
@@ -37808,14 +37957,19 @@ function editorNoteAt(path) {
|
|
|
37808
37957
|
}
|
|
37809
37958
|
return { projectPath, transcript, startedAt };
|
|
37810
37959
|
}
|
|
37811
|
-
function recordRunEnded(pid,
|
|
37960
|
+
function recordRunEnded(pid, ending) {
|
|
37812
37961
|
const path = recordPath();
|
|
37813
37962
|
const record = recordAt(path);
|
|
37814
37963
|
if (record === null || record.pid !== pid) {
|
|
37815
37964
|
return;
|
|
37816
37965
|
}
|
|
37966
|
+
const written = {
|
|
37967
|
+
...record,
|
|
37968
|
+
...ending.exitCode === null ? {} : { exitCode: ending.exitCode },
|
|
37969
|
+
...ending.exitSignal === null ? {} : { exitSignal: ending.exitSignal }
|
|
37970
|
+
};
|
|
37817
37971
|
try {
|
|
37818
|
-
writeFileSync15(path, JSON.stringify(
|
|
37972
|
+
writeFileSync15(path, JSON.stringify(written, null, 2), "utf8");
|
|
37819
37973
|
} catch {}
|
|
37820
37974
|
}
|
|
37821
37975
|
function readRunRecord() {
|
|
@@ -37852,7 +38006,8 @@ function recordAt(path) {
|
|
|
37852
38006
|
projectPath: typeof fields["projectPath"] === "string" ? fields["projectPath"] : "",
|
|
37853
38007
|
arguments: Array.isArray(args) ? args.filter((value) => typeof value === "string") : [],
|
|
37854
38008
|
...typeof fields["command"] === "string" ? { command: fields["command"] } : {},
|
|
37855
|
-
...typeof fields["exitCode"] === "number" ? { exitCode: fields["exitCode"] } : {}
|
|
38009
|
+
...typeof fields["exitCode"] === "number" ? { exitCode: fields["exitCode"] } : {},
|
|
38010
|
+
...typeof fields["exitSignal"] === "string" ? { exitSignal: fields["exitSignal"] } : {}
|
|
37856
38011
|
};
|
|
37857
38012
|
}
|
|
37858
38013
|
function runningAs(pid) {
|
|
@@ -38113,8 +38268,21 @@ function describeHalt(halt) {
|
|
|
38113
38268
|
return `stopped with reason "${halt.reason}"`;
|
|
38114
38269
|
}
|
|
38115
38270
|
}
|
|
38271
|
+
function exited(run) {
|
|
38272
|
+
return run.exitCode !== null || run.exitSignal !== null;
|
|
38273
|
+
}
|
|
38274
|
+
function endingOf(failed) {
|
|
38275
|
+
return {
|
|
38276
|
+
exitCode: typeof failed.code === "number" ? failed.code : null,
|
|
38277
|
+
exitSignal: typeof failed.signal === "string" ? failed.signal : null,
|
|
38278
|
+
failure: typeof failed.code === "string" ? failed.code : null
|
|
38279
|
+
};
|
|
38280
|
+
}
|
|
38281
|
+
function howItExited(run) {
|
|
38282
|
+
return run.exitSignal === null ? `exit code ${run.exitCode ?? "unknown"}` : `ended by ${run.exitSignal}, with no exit code`;
|
|
38283
|
+
}
|
|
38116
38284
|
function stillRunning(run) {
|
|
38117
|
-
if (run
|
|
38285
|
+
if (run === null || exited(run) || run.endedUnwatched === true) {
|
|
38118
38286
|
return false;
|
|
38119
38287
|
}
|
|
38120
38288
|
if (typeof run.endedHere === "string") {
|
|
@@ -38144,7 +38312,15 @@ function runIsUp(run, editorSays, now = Date.now()) {
|
|
|
38144
38312
|
return editorSays ?? stillRunning(run);
|
|
38145
38313
|
}
|
|
38146
38314
|
function noCodeWillCome(run) {
|
|
38147
|
-
return run
|
|
38315
|
+
return !exited(run) && run.process === null;
|
|
38316
|
+
}
|
|
38317
|
+
async function untilGone(handle, pids, withinMs = STOP_WAIT_MS) {
|
|
38318
|
+
const gone = () => (handle?.exitCode !== null || handle.signalCode !== null) && pids.every((pid) => !alive(pid));
|
|
38319
|
+
const deadline = Date.now() + withinMs;
|
|
38320
|
+
while (!gone() && Date.now() < deadline) {
|
|
38321
|
+
await delay3(50);
|
|
38322
|
+
}
|
|
38323
|
+
return gone();
|
|
38148
38324
|
}
|
|
38149
38325
|
function endedWithoutACode(run) {
|
|
38150
38326
|
if (!run.throughEditor) {
|
|
@@ -38419,6 +38595,8 @@ class GodotServer {
|
|
|
38419
38595
|
launchedFrom = null;
|
|
38420
38596
|
ownProject;
|
|
38421
38597
|
announcedAt = null;
|
|
38598
|
+
handingOverFrom = null;
|
|
38599
|
+
notHandedOverBy = null;
|
|
38422
38600
|
constructor() {
|
|
38423
38601
|
this.ownProject = envValue("GDHARNESS_PROJECT") ?? null;
|
|
38424
38602
|
this.godotBridge = getDefaultBridge(this.ownProject);
|
|
@@ -38465,6 +38643,7 @@ class GodotServer {
|
|
|
38465
38643
|
});
|
|
38466
38644
|
console.error("gdharness running on stdio");
|
|
38467
38645
|
try {
|
|
38646
|
+
await this.takeOverFromAPredecessor();
|
|
38468
38647
|
await this.godotBridge.start();
|
|
38469
38648
|
this.bridgeStartupError = null;
|
|
38470
38649
|
const bridgeStatus = this.godotBridge.getStatus();
|
|
@@ -38479,6 +38658,35 @@ class GodotServer {
|
|
|
38479
38658
|
this.keepTryingTheBridge();
|
|
38480
38659
|
}
|
|
38481
38660
|
}
|
|
38661
|
+
async takeOverFromAPredecessor() {
|
|
38662
|
+
if (this.ownProject === null) {
|
|
38663
|
+
return;
|
|
38664
|
+
}
|
|
38665
|
+
const wanted = this.godotBridge.configuredPort;
|
|
38666
|
+
const announced = readAnnouncement2(announcementPath2(this.ownProject));
|
|
38667
|
+
if (announced === null || announced.pid === process.pid || announced.port !== wanted || !alive(announced.pid)) {
|
|
38668
|
+
return;
|
|
38669
|
+
}
|
|
38670
|
+
this.handingOverFrom = announced.pid;
|
|
38671
|
+
this.announcedAt = announceBridge(this.ownProject, {
|
|
38672
|
+
host: this.godotBridge.getStatus().host,
|
|
38673
|
+
port: wanted,
|
|
38674
|
+
version: SERVER_VERSION
|
|
38675
|
+
});
|
|
38676
|
+
const until = Date.now() + HANDOVER_MS;
|
|
38677
|
+
let taken = false;
|
|
38678
|
+
while (!taken && Date.now() < until && !this.shutdownInitiated) {
|
|
38679
|
+
try {
|
|
38680
|
+
await this.godotBridge.start(false);
|
|
38681
|
+
taken = true;
|
|
38682
|
+
this.logDebug(`Took port ${wanted} over from pid ${announced.pid}`);
|
|
38683
|
+
} catch {
|
|
38684
|
+
await delay3(HANDOVER_POLL_MS);
|
|
38685
|
+
}
|
|
38686
|
+
}
|
|
38687
|
+
this.handingOverFrom = null;
|
|
38688
|
+
this.notHandedOverBy = taken ? null : announced.pid;
|
|
38689
|
+
}
|
|
38482
38690
|
keepTryingTheBridge() {
|
|
38483
38691
|
if (this.bridgeRetry !== null || this.shutdownInitiated) {
|
|
38484
38692
|
return;
|
|
@@ -38937,7 +39145,8 @@ class GodotServer {
|
|
|
38937
39145
|
return await this.handleEditorStatus();
|
|
38938
39146
|
case "editor_rescan":
|
|
38939
39147
|
return await this.handleRescanFilesystem(args);
|
|
38940
|
-
case "runtime_inspect":
|
|
39148
|
+
case "runtime_inspect": {
|
|
39149
|
+
const patience = readPositiveNumber(args, "timeoutMs") ?? this.runtimeTimeoutMs();
|
|
38941
39150
|
switch (op) {
|
|
38942
39151
|
case "tree":
|
|
38943
39152
|
return await this.handleRuntimeCommand("get_tree", {
|
|
@@ -38946,45 +39155,45 @@ class GodotServer {
|
|
|
38946
39155
|
depth: readPositiveNumber(args, "depth") ?? 3,
|
|
38947
39156
|
include_properties: readBoolean(args, "includeProperties") ?? false,
|
|
38948
39157
|
properties: readArray(args, "properties") ?? []
|
|
38949
|
-
});
|
|
39158
|
+
}, patience);
|
|
38950
39159
|
case "find":
|
|
38951
|
-
return await this.handleFindRuntimeNodes(args);
|
|
39160
|
+
return await this.handleFindRuntimeNodes(args, patience);
|
|
38952
39161
|
case "text":
|
|
38953
39162
|
return await this.handleRuntimeCommand("read_text", {
|
|
38954
39163
|
...whichGame(args),
|
|
38955
39164
|
root: readNonEmptyString(args, "nodePath") ?? "/root",
|
|
38956
39165
|
include_hidden: readBoolean(args, "includeHidden") ?? false,
|
|
38957
39166
|
limit: readPositiveNumber(args, "limit") ?? 500
|
|
38958
|
-
});
|
|
39167
|
+
}, patience);
|
|
38959
39168
|
case "rect":
|
|
38960
|
-
return await this.handleRuntimeCommand("get_rect", {
|
|
38961
|
-
...whichGame(args),
|
|
38962
|
-
path: readNonEmptyString(args, "nodePath") ?? ""
|
|
38963
|
-
});
|
|
39169
|
+
return await this.handleRuntimeCommand("get_rect", { ...whichGame(args), path: readNonEmptyString(args, "nodePath") ?? "" }, patience);
|
|
38964
39170
|
case "property":
|
|
38965
39171
|
return await this.handleRuntimeCommand("get_property", {
|
|
38966
39172
|
...whichGame(args),
|
|
38967
39173
|
path: readNonEmptyString(args, "nodePath") ?? "",
|
|
38968
39174
|
property: readNonEmptyString(args, "property") ?? ""
|
|
38969
|
-
});
|
|
39175
|
+
}, patience);
|
|
38970
39176
|
default:
|
|
38971
|
-
return await this.handleRuntimeCommand("get_metrics", {
|
|
38972
|
-
|
|
38973
|
-
|
|
38974
|
-
|
|
39177
|
+
return await this.handleRuntimeCommand("get_metrics", { ...whichGame(args), metrics: readArray(args, "metrics") ?? [] }, patience);
|
|
39178
|
+
}
|
|
39179
|
+
}
|
|
39180
|
+
case "runtime_invoke": {
|
|
39181
|
+
if (op === "result") {
|
|
39182
|
+
return this.handleLateAnswer(args);
|
|
38975
39183
|
}
|
|
38976
|
-
|
|
39184
|
+
const patience = readPositiveNumber(args, "timeoutMs") ?? this.runtimeTimeoutMs();
|
|
38977
39185
|
return op === "set" ? await this.handleRuntimeCommand("set_property", {
|
|
38978
39186
|
...whichGame(args),
|
|
38979
39187
|
path: readNonEmptyString(args, "nodePath") ?? "",
|
|
38980
39188
|
property: readString(args, "property") ?? "",
|
|
38981
39189
|
value: args["value"]
|
|
38982
|
-
}) : await this.handleRuntimeCommand("call_method", {
|
|
39190
|
+
}, patience) : await this.handleRuntimeCommand("call_method", {
|
|
38983
39191
|
...whichGame(args),
|
|
38984
39192
|
path: readNonEmptyString(args, "nodePath") ?? "",
|
|
38985
39193
|
method: readString(args, "method") ?? "",
|
|
38986
39194
|
args: readArray(args, "args") ?? []
|
|
38987
|
-
});
|
|
39195
|
+
}, patience);
|
|
39196
|
+
}
|
|
38988
39197
|
case "runtime_capture":
|
|
38989
39198
|
return await this.handleRuntimeCommand(op === "screenshot" ? "capture_screenshot" : "capture_viewport", args);
|
|
38990
39199
|
case "runtime_input":
|
|
@@ -39135,10 +39344,10 @@ class GodotServer {
|
|
|
39135
39344
|
}
|
|
39136
39345
|
async debuggedGame(intent) {
|
|
39137
39346
|
const game = this.activeProcess;
|
|
39138
|
-
if (game !== null && game
|
|
39347
|
+
if (game !== null && exited(game)) {
|
|
39139
39348
|
return {
|
|
39140
39349
|
ok: false,
|
|
39141
|
-
response: this.createErrorResponse(`The last run has already finished (
|
|
39350
|
+
response: this.createErrorResponse(`The last run has already finished (${howItExited(game)}), so there is no debug session to answer for.`, [
|
|
39142
39351
|
"editor_output still reads what it printed, until the next run starts",
|
|
39143
39352
|
"editor_run start plays another, which the debugger can hold"
|
|
39144
39353
|
])
|
|
@@ -39412,10 +39621,11 @@ class GodotServer {
|
|
|
39412
39621
|
];
|
|
39413
39622
|
this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
|
|
39414
39623
|
const log = new GameLog;
|
|
39415
|
-
let
|
|
39624
|
+
let ending = { exitCode: 0, exitSignal: null, failure: null };
|
|
39416
39625
|
try {
|
|
39417
39626
|
const { stdout, stderr } = await run7(engine.value, exportArgs, {
|
|
39418
39627
|
timeout: 300000,
|
|
39628
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
39419
39629
|
signal: callSignal()
|
|
39420
39630
|
});
|
|
39421
39631
|
log.append("stdout", stdout);
|
|
@@ -39427,26 +39637,32 @@ class GodotServer {
|
|
|
39427
39637
|
const failed = error;
|
|
39428
39638
|
log.append("stdout", failed.stdout);
|
|
39429
39639
|
log.append("stderr", failed.stderr);
|
|
39430
|
-
|
|
39640
|
+
ending = endingOf(failed);
|
|
39431
39641
|
} finally {
|
|
39432
39642
|
discard(exportLogs);
|
|
39433
39643
|
}
|
|
39434
39644
|
log.finish();
|
|
39435
39645
|
const problems = log.select({ severity: "warning", sinceLastCall: false, limit: 200 });
|
|
39436
39646
|
const verdict = {
|
|
39437
|
-
exported: exitCode === 0 && log.count("error") === 0 && existsSync15(output.absolutePath),
|
|
39647
|
+
exported: ending.exitCode === 0 && log.count("error") === 0 && existsSync15(output.absolutePath),
|
|
39438
39648
|
preset,
|
|
39439
39649
|
outputPath: output.relativePath,
|
|
39440
39650
|
debug,
|
|
39441
|
-
exitCode,
|
|
39651
|
+
exitCode: ending.exitCode,
|
|
39652
|
+
exitSignal: ending.exitSignal ?? undefined,
|
|
39653
|
+
failure: ending.failure ?? undefined,
|
|
39442
39654
|
errors: log.count("error"),
|
|
39443
39655
|
warnings: log.count("warning"),
|
|
39444
39656
|
entries: forAnswer(problems.entries)
|
|
39445
39657
|
};
|
|
39446
39658
|
if (!verdict.exported) {
|
|
39659
|
+
const why = ending.exitSignal === null ? ending.failure === null ? "" : ` The engine was ended by Node (${ending.failure}), so it has no exit code.` : ` The engine was ended by ${ending.exitSignal}, so it has no exit code: the export ran past its five minutes or the call was cancelled.`;
|
|
39447
39660
|
return {
|
|
39448
39661
|
content: [
|
|
39449
|
-
{
|
|
39662
|
+
{
|
|
39663
|
+
type: "text",
|
|
39664
|
+
text: `Export with preset '${preset}' did not produce ${output.relativePath}.${why}`
|
|
39665
|
+
},
|
|
39450
39666
|
{ type: "text", text: JSON.stringify(verdict, null, 2) }
|
|
39451
39667
|
],
|
|
39452
39668
|
isError: true
|
|
@@ -39501,6 +39717,10 @@ class GodotServer {
|
|
|
39501
39717
|
this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
|
|
39502
39718
|
const userData = mkdtempSync3(join23(tmpdir5(), "gdharness-tests-"));
|
|
39503
39719
|
const run = this.spawnGame(engine.value, cmdArgs, userDataIn(userData));
|
|
39720
|
+
const console2 = [];
|
|
39721
|
+
run.process.stdout?.on("data", (data) => {
|
|
39722
|
+
console2.push(data);
|
|
39723
|
+
});
|
|
39504
39724
|
const hung = await new Promise((resolve) => {
|
|
39505
39725
|
const timer = setTimeout(() => {
|
|
39506
39726
|
run.process.kill();
|
|
@@ -39545,7 +39765,7 @@ class GodotServer {
|
|
|
39545
39765
|
const printed = run.log.select({ severity: "info", sinceLastCall: false, limit: 200 }).entries;
|
|
39546
39766
|
const said = printed.map((entry) => entry.text);
|
|
39547
39767
|
const nothingRan = hung ? null : whyNoReport(said, asked);
|
|
39548
|
-
const verdict = hung ? `hung: killed after ${timeoutMs} ms` : nothingRan ?? verdicts[exitCode ??
|
|
39768
|
+
const verdict = hung ? `hung: killed after ${timeoutMs} ms` : nothingRan ?? (exitCode === null ? undefined : verdicts[exitCode]) ?? (run.exitSignal === null ? `exit ${exitCode ?? "unknown"}` : howItExited(run));
|
|
39549
39769
|
const savesNote = savesStayPut() && nothingRan === null ? SAVES_NOT_MOVED_NOTE : undefined;
|
|
39550
39770
|
if (report === null) {
|
|
39551
39771
|
const note = nothingRan === null ? `The test run wrote no report (${verdict}${reportProblem ? `; ${reportProblem}` : ""}).` : `No tests ran: ${verdict}.${elsewhereIn(project.value.path, asked)}`;
|
|
@@ -39559,6 +39779,7 @@ class GodotServer {
|
|
|
39559
39779
|
verdict,
|
|
39560
39780
|
tests: 0,
|
|
39561
39781
|
exitCode,
|
|
39782
|
+
exitSignal: run.exitSignal ?? undefined,
|
|
39562
39783
|
hung,
|
|
39563
39784
|
arguments: cmdArgs,
|
|
39564
39785
|
entries: forAnswer(printed.slice(0, 60)),
|
|
@@ -39569,7 +39790,7 @@ class GodotServer {
|
|
|
39569
39790
|
isError: true
|
|
39570
39791
|
};
|
|
39571
39792
|
}
|
|
39572
|
-
const failed = report.suites.flatMap((suite) => suite.cases.filter((entry) => entry.status === "failed" || entry.status === "error").map((entry) => ({ ...entry, path: suite.path })));
|
|
39793
|
+
const failed = withActualsPrinted(report.suites.flatMap((suite) => suite.cases.filter((entry) => entry.status === "failed" || entry.status === "error").map((entry) => ({ ...entry, path: suite.path }))), Buffer.concat(console2).toString("utf8"));
|
|
39573
39794
|
const orphans = orphansPrinted(said);
|
|
39574
39795
|
const leftBehind = new Map(orphans.suites.map((suite) => [suite.path, suite.orphans]));
|
|
39575
39796
|
const orphansIn = (path) => path === null ? 0 : leftBehind.get(path) ?? 0;
|
|
@@ -39586,6 +39807,7 @@ class GodotServer {
|
|
|
39586
39807
|
passed: !hung && exitCode === 0 && report.failures === 0 && report.errors === 0,
|
|
39587
39808
|
verdict,
|
|
39588
39809
|
exitCode,
|
|
39810
|
+
exitSignal: run.exitSignal ?? undefined,
|
|
39589
39811
|
tests: report.tests,
|
|
39590
39812
|
failures: report.failures,
|
|
39591
39813
|
errors: report.errors,
|
|
@@ -39852,7 +40074,12 @@ class GodotServer {
|
|
|
39852
40074
|
awaitingLaunchedEditor: status.connected || !this.launchedEditorIsUp() ? undefined : this.launchedEditor?.pid ?? undefined,
|
|
39853
40075
|
...status.connected ? this.whatTheLaunchedEditorDropped() : {},
|
|
39854
40076
|
startupError: this.bridgeStartupError,
|
|
39855
|
-
portNote: status.portWanted === undefined ? undefined : `Port ${status.portWanted}, the one this server was configured with, was held by another process when the bridge started, so it took ${status.port} and announced that where the editor looks for it${this.announcedAt === null ? "" : `, ${this.announcedAt}`}. The editor finds it there; the next server started takes ${status.portWanted} again if it is free.`,
|
|
40077
|
+
portNote: status.portWanted === undefined ? undefined : `Port ${status.portWanted}, the one this server was configured with, was held ${this.notHandedOverBy === null ? "by another process" : `by pid ${this.notHandedOverBy}, the gdharness server this one replaced, which had not let go of it ${HANDOVER_MS / 1000} seconds after being told it was replaced`} when the bridge started, so it took ${status.port} and announced that where the editor looks for it${this.announcedAt === null ? "" : `, ${this.announcedAt}`}. The editor finds it there; the next server started takes ${status.portWanted} again if it is free.`,
|
|
40078
|
+
bridgeHandover: this.handingOverFrom === null ? undefined : {
|
|
40079
|
+
fromPid: this.handingOverFrom,
|
|
40080
|
+
port: this.godotBridge.configuredPort,
|
|
40081
|
+
note: `The gdharness server this one replaced, pid ${this.handingOverFrom}, still holds port ${this.godotBridge.configuredPort} and is standing down; this server takes the port when it lets go, and takes another after ${HANDOVER_MS / 1000} seconds if it does not.`
|
|
40082
|
+
},
|
|
39856
40083
|
staleNote: stale ? addonMismatch(status.addonVersion, SERVER_VERSION, status.addonDigest, shippedEditorDigest()) : undefined,
|
|
39857
40084
|
addonNote: status.connected ? sameCodeNote(status.addonVersion, SERVER_VERSION, status.addonDigest, shippedEditorDigest()) : undefined,
|
|
39858
40085
|
...this.breakpointsAtRisk(),
|
|
@@ -40312,6 +40539,7 @@ class GodotServer {
|
|
|
40312
40539
|
projectPath,
|
|
40313
40540
|
startedAt,
|
|
40314
40541
|
exitCode: null,
|
|
40542
|
+
exitSignal: null,
|
|
40315
40543
|
throughEditor: true,
|
|
40316
40544
|
brokeOn: null,
|
|
40317
40545
|
announcedBefore: alreadyPlaying,
|
|
@@ -40404,24 +40632,25 @@ class GodotServer {
|
|
|
40404
40632
|
const recorded = running?.process === null ? readRunRecord() : null;
|
|
40405
40633
|
clearRunRecord((record) => this.couldBeOurs(record.projectPath));
|
|
40406
40634
|
if (!running || !going) {
|
|
40407
|
-
return;
|
|
40635
|
+
return true;
|
|
40408
40636
|
}
|
|
40409
40637
|
running.endedHere = reason;
|
|
40638
|
+
const announced = this.announcedPidOf(running);
|
|
40410
40639
|
if (running.throughEditor) {
|
|
40411
40640
|
await this.handleViaBridge("stop_playing", {});
|
|
40412
|
-
return;
|
|
40641
|
+
return await untilGone(null, announced === undefined ? [] : [announced]);
|
|
40413
40642
|
}
|
|
40414
40643
|
if (running.process !== null) {
|
|
40415
40644
|
running.process.kill();
|
|
40416
|
-
return;
|
|
40645
|
+
return await untilGone(running.process, announced === undefined ? [] : [announced]);
|
|
40417
40646
|
}
|
|
40418
40647
|
if (running.pid === null) {
|
|
40419
|
-
return;
|
|
40648
|
+
return true;
|
|
40420
40649
|
}
|
|
40421
40650
|
if (recorded === null || recorded.pid !== running.pid || !stillTheRecordedRun(recorded)) {
|
|
40422
40651
|
running.endedHere = null;
|
|
40423
40652
|
running.log.record("warning", `This run was not ended here: pid ${running.pid} no longer answers as the run that was recorded, so nothing was signalled. If that process is still the game, end it yourself; if it is not, it belongs to something else.`);
|
|
40424
|
-
return;
|
|
40653
|
+
return true;
|
|
40425
40654
|
}
|
|
40426
40655
|
const signalled = runningAs(running.pid);
|
|
40427
40656
|
const described = signalled === null ? "nothing it would name" : `${signalled.kind}: ${signalled.text}`;
|
|
@@ -40431,7 +40660,9 @@ class GodotServer {
|
|
|
40431
40660
|
} catch {
|
|
40432
40661
|
landed = false;
|
|
40433
40662
|
}
|
|
40663
|
+
const gone = await untilGone(null, [running.pid]);
|
|
40434
40664
|
running.log.record("info", landed ? `gdharness ended pid ${running.pid}, which the operating system described as ${described}.` : `gdharness signalled pid ${running.pid} and it was already gone; the operating system had described it as ${described}.`);
|
|
40665
|
+
return gone;
|
|
40435
40666
|
}
|
|
40436
40667
|
spawnGame(godotPath, cmdArgs, env) {
|
|
40437
40668
|
const child = spawn(godotPath, cmdArgs, { stdio: ["ignore", "pipe", "pipe"], ...env ? { env } : {} });
|
|
@@ -40462,6 +40693,7 @@ class GodotServer {
|
|
|
40462
40693
|
projectPath: null,
|
|
40463
40694
|
startedAt: Date.now(),
|
|
40464
40695
|
exitCode: null,
|
|
40696
|
+
exitSignal: null,
|
|
40465
40697
|
throughEditor: false,
|
|
40466
40698
|
brokeOn: null
|
|
40467
40699
|
};
|
|
@@ -40471,13 +40703,11 @@ class GodotServer {
|
|
|
40471
40703
|
child.stderr.on("data", (data) => {
|
|
40472
40704
|
log.append("stderr", data);
|
|
40473
40705
|
});
|
|
40474
|
-
child.on("exit", (code) => {
|
|
40475
|
-
this.logDebug(`Godot process exited with code ${code ?? "none"}`);
|
|
40706
|
+
child.on("exit", (code, signal) => {
|
|
40707
|
+
this.logDebug(`Godot process exited with code ${code ?? "none"}, signal ${signal ?? "none"}`);
|
|
40476
40708
|
log.finish();
|
|
40477
|
-
started.exitCode = code
|
|
40478
|
-
|
|
40479
|
-
recordRunEnded(started.pid, started.exitCode);
|
|
40480
|
-
}
|
|
40709
|
+
started.exitCode = code;
|
|
40710
|
+
started.exitSignal = code === null ? signal : null;
|
|
40481
40711
|
});
|
|
40482
40712
|
child.on("error", (err) => {
|
|
40483
40713
|
console.error("Failed to start Godot process:", err);
|
|
@@ -40511,14 +40741,19 @@ class GodotServer {
|
|
|
40511
40741
|
projectPath,
|
|
40512
40742
|
startedAt,
|
|
40513
40743
|
exitCode: null,
|
|
40744
|
+
exitSignal: null,
|
|
40514
40745
|
throughEditor: false,
|
|
40515
40746
|
brokeOn: null
|
|
40516
40747
|
};
|
|
40517
|
-
child.on("exit", (code) => {
|
|
40518
|
-
this.logDebug(`Godot process exited with code ${code ?? "none"}`);
|
|
40748
|
+
child.on("exit", (code, signal) => {
|
|
40749
|
+
this.logDebug(`Godot process exited with code ${code ?? "none"}, signal ${signal ?? "none"}`);
|
|
40519
40750
|
this.drainTranscript(started);
|
|
40520
40751
|
log.finish();
|
|
40521
|
-
started.exitCode = code
|
|
40752
|
+
started.exitCode = code;
|
|
40753
|
+
started.exitSignal = code === null ? signal : null;
|
|
40754
|
+
if (started.pid !== null) {
|
|
40755
|
+
recordRunEnded(started.pid, { exitCode: started.exitCode, exitSignal: started.exitSignal });
|
|
40756
|
+
}
|
|
40522
40757
|
});
|
|
40523
40758
|
child.on("error", (err) => {
|
|
40524
40759
|
console.error("Failed to start Godot process:", err);
|
|
@@ -40601,6 +40836,7 @@ class GodotServer {
|
|
|
40601
40836
|
projectPath: project,
|
|
40602
40837
|
startedAt: note?.startedAt ?? Date.now(),
|
|
40603
40838
|
exitCode: null,
|
|
40839
|
+
exitSignal: null,
|
|
40604
40840
|
throughEditor: true,
|
|
40605
40841
|
brokeOn: null,
|
|
40606
40842
|
pickedUpPlaying: true
|
|
@@ -40737,6 +40973,7 @@ class GodotServer {
|
|
|
40737
40973
|
projectPath: record.projectPath === "" ? null : record.projectPath,
|
|
40738
40974
|
startedAt: record.startedAt,
|
|
40739
40975
|
exitCode: null,
|
|
40976
|
+
exitSignal: null,
|
|
40740
40977
|
throughEditor: false,
|
|
40741
40978
|
brokeOn: null
|
|
40742
40979
|
};
|
|
@@ -40744,7 +40981,8 @@ class GodotServer {
|
|
|
40744
40981
|
if (!alive(record.pid) || !couldStillBeTheRecordedRun(record)) {
|
|
40745
40982
|
adopted.log.finish();
|
|
40746
40983
|
adopted.exitCode = record.exitCode ?? null;
|
|
40747
|
-
adopted.
|
|
40984
|
+
adopted.exitSignal = record.exitSignal ?? null;
|
|
40985
|
+
adopted.endedUnwatched = !exited(adopted);
|
|
40748
40986
|
}
|
|
40749
40987
|
this.activeProcess = adopted;
|
|
40750
40988
|
return adopted;
|
|
@@ -40843,6 +41081,7 @@ class GodotServer {
|
|
|
40843
41081
|
booted: !hung && boot.exitCode === 0 && errors === 0,
|
|
40844
41082
|
hung,
|
|
40845
41083
|
exitCode: boot.exitCode,
|
|
41084
|
+
exitSignal: boot.exitSignal ?? undefined,
|
|
40846
41085
|
durationMs: Date.now() - boot.startedAt,
|
|
40847
41086
|
frames,
|
|
40848
41087
|
errors,
|
|
@@ -40869,7 +41108,11 @@ class GodotServer {
|
|
|
40869
41108
|
}
|
|
40870
41109
|
async runStillGoing(run, editorSays) {
|
|
40871
41110
|
if (!run.throughEditor) {
|
|
40872
|
-
|
|
41111
|
+
const going = stillRunning(run);
|
|
41112
|
+
if (!going && run.process !== null && !exited(run)) {
|
|
41113
|
+
await untilGone(run.process, [], EXIT_REPORTED_WITHIN_MS);
|
|
41114
|
+
}
|
|
41115
|
+
return going;
|
|
40873
41116
|
}
|
|
40874
41117
|
const announced = this.announcedPidOf(run);
|
|
40875
41118
|
if (announced !== undefined && !alive(announced)) {
|
|
@@ -40977,8 +41220,8 @@ class GodotServer {
|
|
|
40977
41220
|
}
|
|
40978
41221
|
if (typeof run.endedHere === "string") {
|
|
40979
41222
|
notes.push(`This run was ended here, by ${run.endedHere}.`);
|
|
40980
|
-
} else if (!stillRunning(run) && !run.throughEditor && run
|
|
40981
|
-
notes.push(run.exitCode === 0 ? "This run quit on its own, cleanly: exit code 0." : "Nothing here ended this run: it stopped on its own or something outside this server stopped it.");
|
|
41223
|
+
} else if (!stillRunning(run) && !run.throughEditor && exited(run)) {
|
|
41224
|
+
notes.push(run.exitCode === 0 ? "This run quit on its own, cleanly: exit code 0." : run.exitSignal === null ? "Nothing here ended this run: it stopped on its own or something outside this server stopped it." : `Nothing here ended this run: something outside this server sent it ${run.exitSignal}, so it has no exit code.`);
|
|
40982
41225
|
}
|
|
40983
41226
|
if (run.endedUnwatched === true) {
|
|
40984
41227
|
notes.push(endedWithoutACode({
|
|
@@ -41001,6 +41244,7 @@ class GodotServer {
|
|
|
41001
41244
|
return this.jsonTextResponse({
|
|
41002
41245
|
running: going,
|
|
41003
41246
|
exitCode: run.exitCode,
|
|
41247
|
+
exitSignal: run.exitSignal ?? undefined,
|
|
41004
41248
|
through: run.throughEditor ? "editor" : "gdharness",
|
|
41005
41249
|
pid: run.pid ?? announced ?? run.announcedPid ?? null,
|
|
41006
41250
|
errors: run.log.count("error"),
|
|
@@ -41033,7 +41277,7 @@ class GodotServer {
|
|
|
41033
41277
|
const endedPid = stopped.pid ?? announcedPid;
|
|
41034
41278
|
const children = readBoolean(args, "andChildren") === true && wasRunning ? await this.whatTheRunStarted(stopped) : null;
|
|
41035
41279
|
this.logDebug("Stopping the running game");
|
|
41036
|
-
await this.endActiveGame("editor_run stop", wasRunning);
|
|
41280
|
+
const gone = await this.endActiveGame("editor_run stop", wasRunning);
|
|
41037
41281
|
const ended = children === null ? null : endChildrenAmong(children);
|
|
41038
41282
|
if (wasRunning) {
|
|
41039
41283
|
for (const pid of new Set([endedPid, announcedPid])) {
|
|
@@ -41054,10 +41298,11 @@ class GodotServer {
|
|
|
41054
41298
|
},
|
|
41055
41299
|
exitedBeforeStop: !wasRunning,
|
|
41056
41300
|
exitCode: stopped.exitCode,
|
|
41301
|
+
exitSignal: stopped.exitSignal ?? undefined,
|
|
41057
41302
|
errors: stopped.log.count("error"),
|
|
41058
41303
|
warnings: stopped.log.count("warning"),
|
|
41059
41304
|
clean: stopped.log.count("error") === 0,
|
|
41060
|
-
note: !wasRunning ? "This run was over before the stop, so nothing was ended here: editor_output has what it printed and how it ended. Anything that game started for itself, with OS.create_process or otherwise, is a separate process and may still be running." : `${stopped.throughEditor ? endedPid === null ? "The editor was asked to stop the scene it is playing. Its game had not announced a runtime, so no process was named under endedPid, and its exit code stays with the editor." : "The editor was asked to stop the scene it is playing." : "The process named under endedPid was ended."} ${aboutTheChildren(ended, stopped.throughEditor)}`,
|
|
41305
|
+
note: !wasRunning ? "This run was over before the stop, so nothing was ended here: editor_output has what it printed and how it ended. Anything that game started for itself, with OS.create_process or otherwise, is a separate process and may still be running." : `${stopped.throughEditor ? endedPid === null ? "The editor was asked to stop the scene it is playing. Its game had not announced a runtime, so no process was named under endedPid, and its exit code stays with the editor." : "The editor was asked to stop the scene it is playing." : "The process named under endedPid was ended."}${stopped.exitSignal === null ? "" : ` It was ended by ${stopped.exitSignal} and so has no exit code.`}${gone ? "" : ` It had not exited ${STOP_WAIT_MS / 1000} seconds after being told to, so it may still be going: editor_status says whether it is.`} ${aboutTheChildren(ended, stopped.throughEditor)}`,
|
|
41061
41306
|
entries: forAnswer(stopped.log.select({ severity: "warning", sinceLastCall: false, limit: 200 }).entries)
|
|
41062
41307
|
});
|
|
41063
41308
|
}
|
|
@@ -41473,7 +41718,17 @@ class GodotServer {
|
|
|
41473
41718
|
const screenshotDir = expectsScreenshot ? mkdtempSync3(join23(tmpdir5(), "gdharness-runtime-screenshot-")) : null;
|
|
41474
41719
|
const screenshotPath = screenshotDir ? join23(screenshotDir, "capture.png") : null;
|
|
41475
41720
|
try {
|
|
41476
|
-
const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs);
|
|
41721
|
+
const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs, !expectsScreenshot);
|
|
41722
|
+
if (!reply.ok && reply.requestId !== undefined) {
|
|
41723
|
+
return this.jsonTextResponse({
|
|
41724
|
+
pending: true,
|
|
41725
|
+
requestId: reply.requestId,
|
|
41726
|
+
command,
|
|
41727
|
+
waitedMs: timeoutMs,
|
|
41728
|
+
note: reply.message,
|
|
41729
|
+
...answeredBy
|
|
41730
|
+
});
|
|
41731
|
+
}
|
|
41477
41732
|
if (!reply.ok) {
|
|
41478
41733
|
return this.createErrorResponse(reply.message);
|
|
41479
41734
|
}
|
|
@@ -41509,7 +41764,36 @@ class GodotServer {
|
|
|
41509
41764
|
discard(screenshotDir);
|
|
41510
41765
|
}
|
|
41511
41766
|
}
|
|
41512
|
-
|
|
41767
|
+
handleLateAnswer(args) {
|
|
41768
|
+
const requestId = readPositiveNumber(args, "requestId") ?? 0;
|
|
41769
|
+
const late = lateAnswer(requestId);
|
|
41770
|
+
if (late === undefined) {
|
|
41771
|
+
return this.createErrorResponse(`No request ${requestId} is waiting to be collected here: an id is kept only from a pending answer this server gave, and only for its fifty most recent.`);
|
|
41772
|
+
}
|
|
41773
|
+
if (late.state === "waiting") {
|
|
41774
|
+
return this.jsonTextResponse({
|
|
41775
|
+
pending: true,
|
|
41776
|
+
requestId,
|
|
41777
|
+
command: late.command,
|
|
41778
|
+
sentAgoMs: Date.now() - late.sentAt,
|
|
41779
|
+
note: "The game has not answered it yet. It is still being listened for, and this answers with the reply once it comes."
|
|
41780
|
+
});
|
|
41781
|
+
}
|
|
41782
|
+
if (late.state === "never") {
|
|
41783
|
+
return this.createErrorResponse(`Request ${requestId} ('${late.command}') got no answer: ${late.why} Read the state back to see whether it ran.`);
|
|
41784
|
+
}
|
|
41785
|
+
const reply = late.reply;
|
|
41786
|
+
if (!reply.ok) {
|
|
41787
|
+
return this.createErrorResponse(`Request ${requestId} ('${late.command}') was answered with a refusal: ${reply.message}`);
|
|
41788
|
+
}
|
|
41789
|
+
const { id: _id, ...payload } = reply.payload;
|
|
41790
|
+
return this.jsonTextResponse({
|
|
41791
|
+
...payload,
|
|
41792
|
+
requestId,
|
|
41793
|
+
answeredAfterMs: late.answeredAt - late.sentAt
|
|
41794
|
+
});
|
|
41795
|
+
}
|
|
41796
|
+
async handleFindRuntimeNodes(args, patience) {
|
|
41513
41797
|
const filters = {};
|
|
41514
41798
|
const className = readNonEmptyString(args, "className");
|
|
41515
41799
|
const script = readNonEmptyString(args, "script");
|
|
@@ -41538,7 +41822,7 @@ class GodotServer {
|
|
|
41538
41822
|
limit: readPositiveNumber(args, "limit") ?? 100,
|
|
41539
41823
|
...property === undefined ? {} : { property },
|
|
41540
41824
|
...includeHidden === undefined ? {} : { include_hidden: includeHidden }
|
|
41541
|
-
});
|
|
41825
|
+
}, patience);
|
|
41542
41826
|
}
|
|
41543
41827
|
async handleRuntimeWait(op, args) {
|
|
41544
41828
|
if (op === "frames") {
|
|
@@ -41573,7 +41857,7 @@ async function runGodotServer() {
|
|
|
41573
41857
|
const server = new GodotServer;
|
|
41574
41858
|
await server.run();
|
|
41575
41859
|
}
|
|
41576
|
-
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run7, __dirname2, EDITOR_RESTART_TIMEOUT_MS = 90000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, MOST_GROUPS = 20, VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CACHE_WRITE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/";
|
|
41860
|
+
var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run7, __dirname2, EDITOR_RESTART_TIMEOUT_MS = 90000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, STOP_WAIT_MS = 1e4, EXIT_REPORTED_WITHIN_MS = 2000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, MOST_GROUPS = 20, VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/";
|
|
41577
41861
|
var init_server3 = __esm(() => {
|
|
41578
41862
|
init_mcp();
|
|
41579
41863
|
init_stdio2();
|
|
@@ -41620,6 +41904,7 @@ var init_server3 = __esm(() => {
|
|
|
41620
41904
|
boolean: "a boolean",
|
|
41621
41905
|
object: "an object"
|
|
41622
41906
|
};
|
|
41907
|
+
HANDOVER_MS = SUCCESSOR_CHECK_MS + 5000;
|
|
41623
41908
|
PATH_SOLUTIONS = [
|
|
41624
41909
|
'Give the path relative to the project, such as "scenes/main.tscn" or "res://scenes/main.tscn"',
|
|
41625
41910
|
"Point projectPath at the project the file belongs to"
|