gdharness 1.2.1 → 1.2.2
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 +35 -9
- package/build/index.js +35 -9
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -17977,7 +17977,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
17977
17977
|
},
|
|
17978
17978
|
{
|
|
17979
17979
|
name: "project_test",
|
|
17980
|
-
description: "Runs the project's gdUnit4 tests headless and answers with every case that did not pass: where it is, and what the assertion said. Suites where everything passed are counted rather than listed, and an engine message keeps the frames above gdUnit4 rather than the twenty inside it, so a clean tier answers in a few lines; identical engine messages come back once under engineEntries, with times saying how often they were printed, and engineErrors counts every one. The class list is rebuilt first, so a suite written a moment ago is found, and the rebuild's answer is under classes, with the classes each addon declares counted under addedInAddons and changedInAddons rather than named: a class it puts back that the cache had after the last rebuild is named under classes.lostSinceLastRebuild, which is the editor writing its shorter list over the file on every save until restarted. On Windows and Linux the run gets a user:// of its own, so a suite that saves a game writes nowhere near the saves of the copy somebody plays. On macOS the engine reads user:// off HOME and the run writes where the player keeps theirs, which the answer says under savesNote rather than leaving to be found in a save list. A run that found nothing to run is never called a pass: gdUnit4 exits cleanly for one, so the answer says so and names the path it looked in. A script that does not load stops gdUnit4 before any suite runs: its errors come back in full under scriptErrors, and the scripts that failed only because one they depend on did are listed by path under failedThroughDependency. When they failed on a class whose own script has a parser error, which gdUnit4 never names, that script is checked by the engine and its error comes first under scriptErrors, with the class it declares under declares, and the errors a script has only because that class did not resolve, which the engine reads as Variant, are kept apart under followFromUnresolved. A client that sends a progress token is told as each suite finishes, and at least every twenty seconds through a long one, so an idle limit does not end a long tier. A run that ends without a report, at its timeout or otherwise, names the suites it finished under suitesFinished, the failing ones with their failed cases under suitesFailing, and the one it was in under suiteRunning. When a client cuts a run off, that is kept in the project and the next project_test there carries it under previousRunCut. A run where nothing failed but nodes were left in the tree comes back under warnings, with the count per suite: gdUnit4 decides its verdict on those and keeps them out of its report, so they are read off what it printed. A run whose every case passed is still not called passed when it printed a script error that no case failed on: gdUnit4 fails a case on the script errors its logger sees, and it cannot see a stack overflow, so the cases pass on whatever the faulted calls returned. Those errors come back under uncaughtScriptErrors, and under engineEntries by index with detailUnder rather than with their backtrace a second time; a backtrace that repeats, as a recursion's does, has each repeated frame or cycle of frames written once with the frames it covers and how many times. One raised inside assert_error(...).is_runtime_error is the test's own and does not count, and none count when the project turns off gdUnit4's report/godot/script_error setting. Needs gdUnit4 under addons/gdUnit4.",
|
|
17980
|
+
description: "Runs the project's gdUnit4 tests headless and answers with every case that did not pass: where it is, and what the assertion said. Each entry under failed carries its first report as message and detail, and, when it has more than one, every report under reports in the order the case met them: gdUnit4 goes on past a failed assertion, and a case can fail several and then stop on a runtime error, which makes its status error. failures counts the cases that failed an assertion and errors the cases that hit an error, so such a case counts in both. Suites where everything passed are counted rather than listed, and an engine message keeps the frames above gdUnit4 rather than the twenty inside it, so a clean tier answers in a few lines; identical engine messages come back once under engineEntries, with times saying how often they were printed, and engineErrors counts every one. The class list is rebuilt first, so a suite written a moment ago is found, and the rebuild's answer is under classes, with the classes each addon declares counted under addedInAddons and changedInAddons rather than named: a class it puts back that the cache had after the last rebuild is named under classes.lostSinceLastRebuild, which is the editor writing its shorter list over the file on every save until restarted. On Windows and Linux the run gets a user:// of its own, so a suite that saves a game writes nowhere near the saves of the copy somebody plays. On macOS the engine reads user:// off HOME and the run writes where the player keeps theirs, which the answer says under savesNote rather than leaving to be found in a save list. A run that found nothing to run is never called a pass: gdUnit4 exits cleanly for one, so the answer says so and names the path it looked in. A script that does not load stops gdUnit4 before any suite runs: its errors come back in full under scriptErrors, and the scripts that failed only because one they depend on did are listed by path under failedThroughDependency. When they failed on a class whose own script has a parser error, which gdUnit4 never names, that script is checked by the engine and its error comes first under scriptErrors, with the class it declares under declares, and the errors a script has only because that class did not resolve, which the engine reads as Variant, are kept apart under followFromUnresolved. A client that sends a progress token is told as each suite finishes, and at least every twenty seconds through a long one, so an idle limit does not end a long tier. A run that ends without a report, at its timeout or otherwise, names the suites it finished under suitesFinished, the failing ones with their failed cases under suitesFailing, and the one it was in under suiteRunning. When a client cuts a run off, that is kept in the project and the next project_test there carries it under previousRunCut. A run where nothing failed but nodes were left in the tree comes back under warnings, with the count per suite: gdUnit4 decides its verdict on those and keeps them out of its report, so they are read off what it printed. A run whose every case passed is still not called passed when it printed a script error that no case failed on: gdUnit4 fails a case on the script errors its logger sees, and it cannot see a stack overflow, so the cases pass on whatever the faulted calls returned. Those errors come back under uncaughtScriptErrors, and under engineEntries by index with detailUnder rather than with their backtrace a second time; a backtrace that repeats, as a recursion's does, has each repeated frame or cycle of frames written once with the frames it covers and how many times. One raised inside assert_error(...).is_runtime_error is the test's own and does not count, and none count when the project turns off gdUnit4's report/godot/script_error setting. Needs gdUnit4 under addons/gdUnit4.",
|
|
17981
17981
|
parameters: {
|
|
17982
17982
|
projectPath: PROJECT_PATH,
|
|
17983
17983
|
path: {
|
|
@@ -39250,18 +39250,25 @@ function count(attributes, name) {
|
|
|
39250
39250
|
return Number.isFinite(value) ? value : 0;
|
|
39251
39251
|
}
|
|
39252
39252
|
function caseOf(suite, element) {
|
|
39253
|
-
const
|
|
39254
|
-
|
|
39255
|
-
|
|
39253
|
+
const of = (kind) => element.children.filter((child) => child.name === kind).map((child) => {
|
|
39254
|
+
const detail = child.text.trim();
|
|
39255
|
+
return { kind, message: child.attributes["message"] ?? null, detail: detail === "" ? null : detail };
|
|
39256
|
+
});
|
|
39257
|
+
const reports = [...of("failure"), ...of("error"), ...of("skipped")];
|
|
39258
|
+
const status = reports.some((report) => report.kind === "error") ? "error" : reports.some((report) => report.kind === "failure") ? "failed" : reports.length > 0 ? "skipped" : "passed";
|
|
39256
39259
|
return {
|
|
39257
39260
|
suite: suite.attributes["name"] ?? "",
|
|
39258
39261
|
name: element.attributes["name"] ?? "",
|
|
39259
39262
|
status,
|
|
39260
39263
|
time: count(element.attributes, "time"),
|
|
39261
|
-
message:
|
|
39262
|
-
detail: detail
|
|
39264
|
+
message: reports[0]?.message ?? null,
|
|
39265
|
+
detail: reports[0]?.detail ?? null,
|
|
39266
|
+
reports
|
|
39263
39267
|
};
|
|
39264
39268
|
}
|
|
39269
|
+
function failedAnAssertion(entry) {
|
|
39270
|
+
return entry.reports.some((report) => report.kind === "failure");
|
|
39271
|
+
}
|
|
39265
39272
|
function pathOf(suite) {
|
|
39266
39273
|
const directory = suite.attributes["package"];
|
|
39267
39274
|
const name = suite.attributes["name"];
|
|
@@ -39276,7 +39283,7 @@ function parseJUnit(xml) {
|
|
|
39276
39283
|
const suiteElements = top.name === "testsuite" ? [top] : top.children.filter((child) => child.name === "testsuite");
|
|
39277
39284
|
const suites = suiteElements.map((suite) => {
|
|
39278
39285
|
const cases = suite.children.filter((child) => child.name === "testcase").map((element) => caseOf(suite, element));
|
|
39279
|
-
const failures = cases.filter(
|
|
39286
|
+
const failures = cases.filter(failedAnAssertion).length;
|
|
39280
39287
|
const errors = cases.filter((entry) => entry.status === "error").length;
|
|
39281
39288
|
const written = (kind) => suite.children.filter((child) => child.name === "testcase").reduce((sum, element) => sum + element.children.filter((child) => child.name === kind).length, 0);
|
|
39282
39289
|
return {
|
|
@@ -39296,7 +39303,7 @@ function parseJUnit(xml) {
|
|
|
39296
39303
|
const all = suites.flatMap((suite) => suite.cases);
|
|
39297
39304
|
return {
|
|
39298
39305
|
tests: all.length,
|
|
39299
|
-
failures: all.filter(
|
|
39306
|
+
failures: all.filter(failedAnAssertion).length,
|
|
39300
39307
|
errors: all.filter((entry) => entry.status === "error").length,
|
|
39301
39308
|
hookFailures: suites.reduce((sum, suite) => sum + suite.hookFailures, 0),
|
|
39302
39309
|
hookErrors: suites.reduce((sum, suite) => sum + suite.hookErrors, 0),
|
|
@@ -39418,6 +39425,25 @@ ${note}`;
|
|
|
39418
39425
|
return detail === entry.detail ? entry : { ...entry, detail };
|
|
39419
39426
|
});
|
|
39420
39427
|
}
|
|
39428
|
+
function failedCases(report, printed) {
|
|
39429
|
+
const cases = report.suites.flatMap((suite) => suite.cases.filter((entry) => entry.status === "failed" || entry.status === "error").map((entry) => ({ entry, path: suite.path })));
|
|
39430
|
+
const read = withActualsPrinted(cases.flatMap(({ entry }) => entry.reports), printed);
|
|
39431
|
+
let next = 0;
|
|
39432
|
+
return cases.map(({ entry, path }) => {
|
|
39433
|
+
const reports = read.slice(next, next + entry.reports.length);
|
|
39434
|
+
next += entry.reports.length;
|
|
39435
|
+
return {
|
|
39436
|
+
suite: entry.suite,
|
|
39437
|
+
name: entry.name,
|
|
39438
|
+
status: entry.status,
|
|
39439
|
+
time: entry.time,
|
|
39440
|
+
path,
|
|
39441
|
+
message: reports[0]?.message ?? null,
|
|
39442
|
+
detail: reports[0]?.detail ?? null,
|
|
39443
|
+
...reports.length > 1 ? { reports } : {}
|
|
39444
|
+
};
|
|
39445
|
+
});
|
|
39446
|
+
}
|
|
39421
39447
|
function masked(expected, actual) {
|
|
39422
39448
|
const allSpelled = (value) => !PRINTED_DICTIONARY.test(value) && value.split("[").length === value.split(MASKED_BRACKET).length;
|
|
39423
39449
|
return `${expected}${actual}`.includes(MASKED_BRACKET) && allSpelled(expected) && allSpelled(actual);
|
|
@@ -45432,7 +45458,7 @@ class GodotServer {
|
|
|
45432
45458
|
isError: true
|
|
45433
45459
|
};
|
|
45434
45460
|
}
|
|
45435
|
-
const failed =
|
|
45461
|
+
const failed = failedCases(report, Buffer.concat(console2).toString("utf8"));
|
|
45436
45462
|
const orphans = orphansPrinted(said);
|
|
45437
45463
|
const leftBehind = new Map(orphans.suites.map((suite) => [suite.path, suite.orphans]));
|
|
45438
45464
|
const orphansIn = (path) => path === null ? 0 : leftBehind.get(path) ?? 0;
|
package/build/index.js
CHANGED
|
@@ -29817,18 +29817,25 @@ function count(attributes, name) {
|
|
|
29817
29817
|
return Number.isFinite(value) ? value : 0;
|
|
29818
29818
|
}
|
|
29819
29819
|
function caseOf(suite, element) {
|
|
29820
|
-
const
|
|
29821
|
-
|
|
29822
|
-
|
|
29820
|
+
const of = (kind) => element.children.filter((child) => child.name === kind).map((child) => {
|
|
29821
|
+
const detail = child.text.trim();
|
|
29822
|
+
return { kind, message: child.attributes["message"] ?? null, detail: detail === "" ? null : detail };
|
|
29823
|
+
});
|
|
29824
|
+
const reports = [...of("failure"), ...of("error"), ...of("skipped")];
|
|
29825
|
+
const status = reports.some((report) => report.kind === "error") ? "error" : reports.some((report) => report.kind === "failure") ? "failed" : reports.length > 0 ? "skipped" : "passed";
|
|
29823
29826
|
return {
|
|
29824
29827
|
suite: suite.attributes["name"] ?? "",
|
|
29825
29828
|
name: element.attributes["name"] ?? "",
|
|
29826
29829
|
status,
|
|
29827
29830
|
time: count(element.attributes, "time"),
|
|
29828
|
-
message:
|
|
29829
|
-
detail: detail
|
|
29831
|
+
message: reports[0]?.message ?? null,
|
|
29832
|
+
detail: reports[0]?.detail ?? null,
|
|
29833
|
+
reports
|
|
29830
29834
|
};
|
|
29831
29835
|
}
|
|
29836
|
+
function failedAnAssertion(entry) {
|
|
29837
|
+
return entry.reports.some((report) => report.kind === "failure");
|
|
29838
|
+
}
|
|
29832
29839
|
function pathOf(suite) {
|
|
29833
29840
|
const directory = suite.attributes["package"];
|
|
29834
29841
|
const name = suite.attributes["name"];
|
|
@@ -29843,7 +29850,7 @@ function parseJUnit(xml) {
|
|
|
29843
29850
|
const suiteElements = top.name === "testsuite" ? [top] : top.children.filter((child) => child.name === "testsuite");
|
|
29844
29851
|
const suites = suiteElements.map((suite) => {
|
|
29845
29852
|
const cases = suite.children.filter((child) => child.name === "testcase").map((element) => caseOf(suite, element));
|
|
29846
|
-
const failures = cases.filter(
|
|
29853
|
+
const failures = cases.filter(failedAnAssertion).length;
|
|
29847
29854
|
const errors = cases.filter((entry) => entry.status === "error").length;
|
|
29848
29855
|
const written = (kind) => suite.children.filter((child) => child.name === "testcase").reduce((sum, element) => sum + element.children.filter((child) => child.name === kind).length, 0);
|
|
29849
29856
|
return {
|
|
@@ -29863,7 +29870,7 @@ function parseJUnit(xml) {
|
|
|
29863
29870
|
const all = suites.flatMap((suite) => suite.cases);
|
|
29864
29871
|
return {
|
|
29865
29872
|
tests: all.length,
|
|
29866
|
-
failures: all.filter(
|
|
29873
|
+
failures: all.filter(failedAnAssertion).length,
|
|
29867
29874
|
errors: all.filter((entry) => entry.status === "error").length,
|
|
29868
29875
|
hookFailures: suites.reduce((sum, suite) => sum + suite.hookFailures, 0),
|
|
29869
29876
|
hookErrors: suites.reduce((sum, suite) => sum + suite.hookErrors, 0),
|
|
@@ -30010,6 +30017,25 @@ ${note}`;
|
|
|
30010
30017
|
return detail === entry.detail ? entry : { ...entry, detail };
|
|
30011
30018
|
});
|
|
30012
30019
|
}
|
|
30020
|
+
function failedCases(report, printed) {
|
|
30021
|
+
const cases = report.suites.flatMap((suite) => suite.cases.filter((entry) => entry.status === "failed" || entry.status === "error").map((entry) => ({ entry, path: suite.path })));
|
|
30022
|
+
const read = withActualsPrinted(cases.flatMap(({ entry }) => entry.reports), printed);
|
|
30023
|
+
let next = 0;
|
|
30024
|
+
return cases.map(({ entry, path }) => {
|
|
30025
|
+
const reports = read.slice(next, next + entry.reports.length);
|
|
30026
|
+
next += entry.reports.length;
|
|
30027
|
+
return {
|
|
30028
|
+
suite: entry.suite,
|
|
30029
|
+
name: entry.name,
|
|
30030
|
+
status: entry.status,
|
|
30031
|
+
time: entry.time,
|
|
30032
|
+
path,
|
|
30033
|
+
message: reports[0]?.message ?? null,
|
|
30034
|
+
detail: reports[0]?.detail ?? null,
|
|
30035
|
+
...reports.length > 1 ? { reports } : {}
|
|
30036
|
+
};
|
|
30037
|
+
});
|
|
30038
|
+
}
|
|
30013
30039
|
var MASKED_BRACKET = "[lb]";
|
|
30014
30040
|
function masked(expected, actual) {
|
|
30015
30041
|
const allSpelled = (value) => !PRINTED_DICTIONARY.test(value) && value.split("[").length === value.split(MASKED_BRACKET).length;
|
|
@@ -34054,7 +34080,7 @@ var TOOL_SPECS = [
|
|
|
34054
34080
|
},
|
|
34055
34081
|
{
|
|
34056
34082
|
name: "project_test",
|
|
34057
|
-
description: "Runs the project's gdUnit4 tests headless and answers with every case that did not pass: where it is, and what the assertion said. Suites where everything passed are counted rather than listed, and an engine message keeps the frames above gdUnit4 rather than the twenty inside it, so a clean tier answers in a few lines; identical engine messages come back once under engineEntries, with times saying how often they were printed, and engineErrors counts every one. The class list is rebuilt first, so a suite written a moment ago is found, and the rebuild's answer is under classes, with the classes each addon declares counted under addedInAddons and changedInAddons rather than named: a class it puts back that the cache had after the last rebuild is named under classes.lostSinceLastRebuild, which is the editor writing its shorter list over the file on every save until restarted. On Windows and Linux the run gets a user:// of its own, so a suite that saves a game writes nowhere near the saves of the copy somebody plays. On macOS the engine reads user:// off HOME and the run writes where the player keeps theirs, which the answer says under savesNote rather than leaving to be found in a save list. A run that found nothing to run is never called a pass: gdUnit4 exits cleanly for one, so the answer says so and names the path it looked in. A script that does not load stops gdUnit4 before any suite runs: its errors come back in full under scriptErrors, and the scripts that failed only because one they depend on did are listed by path under failedThroughDependency. When they failed on a class whose own script has a parser error, which gdUnit4 never names, that script is checked by the engine and its error comes first under scriptErrors, with the class it declares under declares, and the errors a script has only because that class did not resolve, which the engine reads as Variant, are kept apart under followFromUnresolved. A client that sends a progress token is told as each suite finishes, and at least every twenty seconds through a long one, so an idle limit does not end a long tier. A run that ends without a report, at its timeout or otherwise, names the suites it finished under suitesFinished, the failing ones with their failed cases under suitesFailing, and the one it was in under suiteRunning. When a client cuts a run off, that is kept in the project and the next project_test there carries it under previousRunCut. A run where nothing failed but nodes were left in the tree comes back under warnings, with the count per suite: gdUnit4 decides its verdict on those and keeps them out of its report, so they are read off what it printed. A run whose every case passed is still not called passed when it printed a script error that no case failed on: gdUnit4 fails a case on the script errors its logger sees, and it cannot see a stack overflow, so the cases pass on whatever the faulted calls returned. Those errors come back under uncaughtScriptErrors, and under engineEntries by index with detailUnder rather than with their backtrace a second time; a backtrace that repeats, as a recursion's does, has each repeated frame or cycle of frames written once with the frames it covers and how many times. One raised inside assert_error(...).is_runtime_error is the test's own and does not count, and none count when the project turns off gdUnit4's report/godot/script_error setting. Needs gdUnit4 under addons/gdUnit4.",
|
|
34083
|
+
description: "Runs the project's gdUnit4 tests headless and answers with every case that did not pass: where it is, and what the assertion said. Each entry under failed carries its first report as message and detail, and, when it has more than one, every report under reports in the order the case met them: gdUnit4 goes on past a failed assertion, and a case can fail several and then stop on a runtime error, which makes its status error. failures counts the cases that failed an assertion and errors the cases that hit an error, so such a case counts in both. Suites where everything passed are counted rather than listed, and an engine message keeps the frames above gdUnit4 rather than the twenty inside it, so a clean tier answers in a few lines; identical engine messages come back once under engineEntries, with times saying how often they were printed, and engineErrors counts every one. The class list is rebuilt first, so a suite written a moment ago is found, and the rebuild's answer is under classes, with the classes each addon declares counted under addedInAddons and changedInAddons rather than named: a class it puts back that the cache had after the last rebuild is named under classes.lostSinceLastRebuild, which is the editor writing its shorter list over the file on every save until restarted. On Windows and Linux the run gets a user:// of its own, so a suite that saves a game writes nowhere near the saves of the copy somebody plays. On macOS the engine reads user:// off HOME and the run writes where the player keeps theirs, which the answer says under savesNote rather than leaving to be found in a save list. A run that found nothing to run is never called a pass: gdUnit4 exits cleanly for one, so the answer says so and names the path it looked in. A script that does not load stops gdUnit4 before any suite runs: its errors come back in full under scriptErrors, and the scripts that failed only because one they depend on did are listed by path under failedThroughDependency. When they failed on a class whose own script has a parser error, which gdUnit4 never names, that script is checked by the engine and its error comes first under scriptErrors, with the class it declares under declares, and the errors a script has only because that class did not resolve, which the engine reads as Variant, are kept apart under followFromUnresolved. A client that sends a progress token is told as each suite finishes, and at least every twenty seconds through a long one, so an idle limit does not end a long tier. A run that ends without a report, at its timeout or otherwise, names the suites it finished under suitesFinished, the failing ones with their failed cases under suitesFailing, and the one it was in under suiteRunning. When a client cuts a run off, that is kept in the project and the next project_test there carries it under previousRunCut. A run where nothing failed but nodes were left in the tree comes back under warnings, with the count per suite: gdUnit4 decides its verdict on those and keeps them out of its report, so they are read off what it printed. A run whose every case passed is still not called passed when it printed a script error that no case failed on: gdUnit4 fails a case on the script errors its logger sees, and it cannot see a stack overflow, so the cases pass on whatever the faulted calls returned. Those errors come back under uncaughtScriptErrors, and under engineEntries by index with detailUnder rather than with their backtrace a second time; a backtrace that repeats, as a recursion's does, has each repeated frame or cycle of frames written once with the frames it covers and how many times. One raised inside assert_error(...).is_runtime_error is the test's own and does not count, and none count when the project turns off gdUnit4's report/godot/script_error setting. Needs gdUnit4 under addons/gdUnit4.",
|
|
34058
34084
|
parameters: {
|
|
34059
34085
|
projectPath: PROJECT_PATH,
|
|
34060
34086
|
path: {
|
|
@@ -37929,7 +37955,7 @@ class GodotServer {
|
|
|
37929
37955
|
isError: true
|
|
37930
37956
|
};
|
|
37931
37957
|
}
|
|
37932
|
-
const failed =
|
|
37958
|
+
const failed = failedCases(report, Buffer.concat(console2).toString("utf8"));
|
|
37933
37959
|
const orphans = orphansPrinted(said);
|
|
37934
37960
|
const leftBehind = new Map(orphans.suites.map((suite) => [suite.path, suite.orphans]));
|
|
37935
37961
|
const orphansIn = (path) => path === null ? 0 : leftBehind.get(path) ?? 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"mcpName": "io.github.Aureliolo/gdharness",
|
|
5
5
|
"description": "A harness for driving a Godot 4 project from an agent: editor addons, a runtime bridge into the running game, an MCP server in front of them, and a CLI that installs and diagnoses the Godot side.",
|
|
6
6
|
"type": "module",
|