gdharness 1.3.19 → 1.3.20
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 +28 -26
- package/build/godot/tests/listed.gd +54 -2
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -48378,14 +48378,14 @@ var init_suites_scene = __esm(() => {
|
|
|
48378
48378
|
});
|
|
48379
48379
|
|
|
48380
48380
|
// src/test-selection.ts
|
|
48381
|
-
import { existsSync as existsSync33
|
|
48381
|
+
import { existsSync as existsSync33 } from "node:fs";
|
|
48382
48382
|
import { basename as basename12, join as join42 } from "node:path";
|
|
48383
|
-
function casesIn(
|
|
48384
|
-
|
|
48385
|
-
return [...text.matchAll(/^func\s+(test_\w*)\s*\(/gm)].map((match) => match[1] ?? "");
|
|
48383
|
+
function casesIn(scripts, suite) {
|
|
48384
|
+
return scripts.suite(suite)?.cases ?? scripts.script(suite)?.funcs.filter((name) => name.startsWith("test_")) ?? [];
|
|
48386
48385
|
}
|
|
48387
48386
|
function scriptsWithoutCases(projectPath, asked) {
|
|
48388
|
-
|
|
48387
|
+
const scripts = new ProjectScripts(projectPath);
|
|
48388
|
+
return asked.filter((path) => path.endsWith(".gd") && existsSync33(join42(projectPath, path.slice("res://".length))) && casesIn(scripts, path).length === 0);
|
|
48389
48389
|
}
|
|
48390
48390
|
function suitesUnder(projectPath, asked) {
|
|
48391
48391
|
const scripts = projectFiles(projectPath, [".gd"]).map((path) => `res://${path}`);
|
|
@@ -48401,7 +48401,8 @@ function selectTests(projectPath, asked, given) {
|
|
|
48401
48401
|
if (entries.length === 0) {
|
|
48402
48402
|
return { ok: false, reason: "tests is an empty list: name a case, or leave tests out to run every one." };
|
|
48403
48403
|
}
|
|
48404
|
-
const
|
|
48404
|
+
const scripts = new ProjectScripts(projectPath);
|
|
48405
|
+
const declared = suitesUnder(projectPath, asked).flatMap((suite) => casesIn(scripts, suite).map((test) => ({ suite, test })));
|
|
48405
48406
|
const nameOf = (suite) => basename12(suite, ".gd");
|
|
48406
48407
|
const selected = [];
|
|
48407
48408
|
for (const [index, entry] of entries.entries()) {
|
|
@@ -48442,6 +48443,7 @@ function selectTests(projectPath, asked, given) {
|
|
|
48442
48443
|
}
|
|
48443
48444
|
var init_test_selection = __esm(() => {
|
|
48444
48445
|
init_project_scan();
|
|
48446
|
+
init_suite_plan();
|
|
48445
48447
|
});
|
|
48446
48448
|
|
|
48447
48449
|
// src/server.ts
|
|
@@ -48456,7 +48458,7 @@ import {
|
|
|
48456
48458
|
mkdirSync as mkdirSync23,
|
|
48457
48459
|
openSync as openSync6,
|
|
48458
48460
|
readdirSync as readdirSync19,
|
|
48459
|
-
readFileSync as
|
|
48461
|
+
readFileSync as readFileSync36,
|
|
48460
48462
|
readSync as readSync3,
|
|
48461
48463
|
realpathSync as realpathSync5,
|
|
48462
48464
|
rmdirSync as rmdirSync2,
|
|
@@ -48477,9 +48479,9 @@ function greetingWaitMs() {
|
|
|
48477
48479
|
return waitFromEnv("GDHARNESS_GREETING_WAIT_MS", 1e4);
|
|
48478
48480
|
}
|
|
48479
48481
|
function pictureToSend(pngPath, jpegPath) {
|
|
48480
|
-
const png =
|
|
48482
|
+
const png = readFileSync36(pngPath);
|
|
48481
48483
|
const beside = pngPath.replace(/\.png$/, ".jpg");
|
|
48482
|
-
const jpeg = jpegPath !== undefined && normalize2(jpegPath) === normalize2(beside) && existsSync34(beside) ?
|
|
48484
|
+
const jpeg = jpegPath !== undefined && normalize2(jpegPath) === normalize2(beside) && existsSync34(beside) ? readFileSync36(beside) : null;
|
|
48483
48485
|
const sent = jpeg !== null && jpeg.length < png.length ? jpeg : png;
|
|
48484
48486
|
return {
|
|
48485
48487
|
data: sent.toString("base64"),
|
|
@@ -48958,7 +48960,7 @@ function endedToStartThis(ended) {
|
|
|
48958
48960
|
return ` ${which} was ended to start this one; its output is no longer what editor_output answers about.`;
|
|
48959
48961
|
}
|
|
48960
48962
|
function lineNothingStopsOn(absolutePath, relativePath, line) {
|
|
48961
|
-
const text =
|
|
48963
|
+
const text = readFileSync36(absolutePath, "utf8");
|
|
48962
48964
|
const lines = text === "" ? 0 : text.split(`
|
|
48963
48965
|
`).length - (text.endsWith(`
|
|
48964
48966
|
`) ? 1 : 0);
|
|
@@ -49169,7 +49171,7 @@ function notRunNote(notRun, failFast) {
|
|
|
49169
49171
|
function presetsIn(projectPath) {
|
|
49170
49172
|
let text;
|
|
49171
49173
|
try {
|
|
49172
|
-
text =
|
|
49174
|
+
text = readFileSync36(join43(projectPath, "export_presets.cfg"), "utf8");
|
|
49173
49175
|
} catch {
|
|
49174
49176
|
return null;
|
|
49175
49177
|
}
|
|
@@ -49319,7 +49321,7 @@ function rewriteBusNamed(projectPath, from, to) {
|
|
|
49319
49321
|
const rewritten = [];
|
|
49320
49322
|
for (const file of projectFiles(projectPath, [".tscn", ".tres"])) {
|
|
49321
49323
|
const absolute = join43(projectPath, file);
|
|
49322
|
-
const lines =
|
|
49324
|
+
const lines = readFileSync36(absolute, "utf8").split(`
|
|
49323
49325
|
`);
|
|
49324
49326
|
const at = [];
|
|
49325
49327
|
const after = lines.map((line, index) => {
|
|
@@ -49338,7 +49340,7 @@ function rewriteBusNamed(projectPath, from, to) {
|
|
|
49338
49340
|
}
|
|
49339
49341
|
const namedInScripts = [];
|
|
49340
49342
|
for (const file of projectFiles(projectPath, [".gd"])) {
|
|
49341
|
-
|
|
49343
|
+
readFileSync36(join43(projectPath, file), "utf8").split(`
|
|
49342
49344
|
`).forEach((line, index) => {
|
|
49343
49345
|
if (line.includes(`"${from}"`)) {
|
|
49344
49346
|
namedInScripts.push({ file: `res://${file}`, line: index + 1, text: line.trim() });
|
|
@@ -49613,7 +49615,7 @@ function profileFrameFunctions(projectPath) {
|
|
|
49613
49615
|
let projectGodot = null;
|
|
49614
49616
|
if (projectPath !== null) {
|
|
49615
49617
|
try {
|
|
49616
|
-
projectGodot =
|
|
49618
|
+
projectGodot = readFileSync36(join43(projectPath, "project.godot"), "utf8");
|
|
49617
49619
|
} catch {}
|
|
49618
49620
|
}
|
|
49619
49621
|
return frameFunctionsFor(queuedMessagesOf(projectGodot));
|
|
@@ -49637,7 +49639,7 @@ function projectSourceOf(projectPath) {
|
|
|
49637
49639
|
let source = null;
|
|
49638
49640
|
if (inside.ok) {
|
|
49639
49641
|
try {
|
|
49640
|
-
source =
|
|
49642
|
+
source = readFileSync36(inside.absolutePath, "utf8");
|
|
49641
49643
|
} catch {}
|
|
49642
49644
|
}
|
|
49643
49645
|
read.set(script, source);
|
|
@@ -49796,7 +49798,7 @@ function foldedEntriesAnswer(log, floor = "warning") {
|
|
|
49796
49798
|
}
|
|
49797
49799
|
function gdUnitFailsOnScriptErrors(projectPath) {
|
|
49798
49800
|
try {
|
|
49799
|
-
return settingKeys(
|
|
49801
|
+
return settingKeys(readFileSync36(join43(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
|
|
49800
49802
|
} catch {
|
|
49801
49803
|
return true;
|
|
49802
49804
|
}
|
|
@@ -49924,11 +49926,11 @@ function isDirectory(path) {
|
|
|
49924
49926
|
}
|
|
49925
49927
|
}
|
|
49926
49928
|
function mainSceneOf(projectFile) {
|
|
49927
|
-
const scene = parseProjectGodot(
|
|
49929
|
+
const scene = parseProjectGodot(readFileSync36(projectFile, "utf8"))["application"]?.["run/main_scene"];
|
|
49928
49930
|
return typeof scene === "string" && scene !== "" ? scene : null;
|
|
49929
49931
|
}
|
|
49930
49932
|
function editorPlaysHeadless(projectFile) {
|
|
49931
|
-
const runArgs = parseProjectGodot(
|
|
49933
|
+
const runArgs = parseProjectGodot(readFileSync36(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
|
|
49932
49934
|
return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
|
|
49933
49935
|
}
|
|
49934
49936
|
function ownArguments(params) {
|
|
@@ -51823,7 +51825,7 @@ class GodotServer {
|
|
|
51823
51825
|
if (!engine.ok) {
|
|
51824
51826
|
return engine.response;
|
|
51825
51827
|
}
|
|
51826
|
-
const application = parseProjectGodot(
|
|
51828
|
+
const application = parseProjectGodot(readFileSync36(project.value.file, "utf8"))["application"] ?? {};
|
|
51827
51829
|
const name = application["config/name"];
|
|
51828
51830
|
const mainScene = application["run/main_scene"];
|
|
51829
51831
|
const info = {
|
|
@@ -52343,13 +52345,13 @@ ${platform}`;
|
|
|
52343
52345
|
const reportsDir = join43(project.value.path, ".godot", ...ours.split("/"));
|
|
52344
52346
|
let report = null;
|
|
52345
52347
|
let reportProblem = null;
|
|
52346
|
-
const casePrinted = withNestedErrors(readRun(existsSync34(engineLog) ?
|
|
52348
|
+
const casePrinted = withNestedErrors(readRun(existsSync34(engineLog) ? readFileSync36(engineLog, "utf8") : ""), run.log.all);
|
|
52347
52349
|
const declaredErrors = readExpected(expectedFile);
|
|
52348
52350
|
try {
|
|
52349
52351
|
const written = existsSync34(reportsDir) && notCompiled === null ? readdirSync19(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
|
|
52350
52352
|
const newest = written.at(-1);
|
|
52351
52353
|
if (newest !== undefined) {
|
|
52352
|
-
report = parseJUnit(
|
|
52354
|
+
report = parseJUnit(readFileSync36(join43(reportsDir, newest, "results.xml"), "utf8"));
|
|
52353
52355
|
}
|
|
52354
52356
|
} catch (error) {
|
|
52355
52357
|
reportProblem = errorMessage(error);
|
|
@@ -52828,7 +52830,7 @@ ${error.text}`;
|
|
|
52828
52830
|
return null;
|
|
52829
52831
|
}
|
|
52830
52832
|
try {
|
|
52831
|
-
return
|
|
52833
|
+
return readFileSync36(contained.absolutePath, "utf8");
|
|
52832
52834
|
} catch {
|
|
52833
52835
|
return null;
|
|
52834
52836
|
}
|
|
@@ -52922,7 +52924,7 @@ ${error.text}`;
|
|
|
52922
52924
|
return null;
|
|
52923
52925
|
}
|
|
52924
52926
|
try {
|
|
52925
|
-
return
|
|
52927
|
+
return readFileSync36(contained.absolutePath, "utf8");
|
|
52926
52928
|
} catch {
|
|
52927
52929
|
return null;
|
|
52928
52930
|
}
|
|
@@ -53425,7 +53427,7 @@ ${error.text}`;
|
|
|
53425
53427
|
return null;
|
|
53426
53428
|
}
|
|
53427
53429
|
try {
|
|
53428
|
-
return settingKeys(
|
|
53430
|
+
return settingKeys(readFileSync36(join43(projectPath, "project.godot"), "utf8"));
|
|
53429
53431
|
} catch {
|
|
53430
53432
|
return null;
|
|
53431
53433
|
}
|
|
@@ -54205,7 +54207,7 @@ ${error.text}`;
|
|
|
54205
54207
|
return 0;
|
|
54206
54208
|
}
|
|
54207
54209
|
try {
|
|
54208
|
-
return reportAlreadyInTranscript(
|
|
54210
|
+
return reportAlreadyInTranscript(readFileSync36(game.transcript), readFileSync36(report));
|
|
54209
54211
|
} catch (error) {
|
|
54210
54212
|
this.logDebug(`Could not compare a picked-up run's report with its transcript: ${errorMessage(error)}`);
|
|
54211
54213
|
return 0;
|
|
@@ -56538,7 +56540,7 @@ ${error.text}`;
|
|
|
56538
56540
|
}
|
|
56539
56541
|
const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
56540
56542
|
const declared = new RegExp(`^\\s*(?:@\\w+(?:\\([^)]*\\))?\\s+)*class_name\\s+${escaped}\\b`, "m");
|
|
56541
|
-
if (declared.test(
|
|
56543
|
+
if (declared.test(readFileSync36(scriptPath, "utf8"))) {
|
|
56542
56544
|
return null;
|
|
56543
56545
|
}
|
|
56544
56546
|
const checked = await this.classesTheEditorCannotSee(args);
|
|
@@ -80,6 +80,11 @@ class GdhRunner:
|
|
|
80
80
|
var together: GDScript = null
|
|
81
81
|
## The cases dealt by their rows, by `res://suite.gd:test_case`.
|
|
82
82
|
var row_shares: Dictionary[String, GdhRowShare] = {}
|
|
83
|
+
## The cases to leave out, by `res://suite.gd:test_case` of the suite running them. gdUnit4's own
|
|
84
|
+
## list is matched against the script declaring a case, which for a case a suite inherits is its
|
|
85
|
+
## base, so an inherited case left out under its suite ran in every engine the suite was dealt to,
|
|
86
|
+
## and a case of `res://tests/a_test.gd` was also left out of `res://more/tests/a_test.gd`.
|
|
87
|
+
var left_out: Dictionary[String, bool] = {}
|
|
83
88
|
## The cases gdUnit4 said it finished, which [GdhSessionCheck] holds the session's lookups against.
|
|
84
89
|
var cases_ended: int = 0
|
|
85
90
|
## The marker of the shutdown walk's oldest end, made as the run starts.
|
|
@@ -106,10 +111,40 @@ class GdhRunner:
|
|
|
106
111
|
return options
|
|
107
112
|
return options_of(OS.get_cmdline_args(), own_path)
|
|
108
113
|
|
|
114
|
+
## gdUnit4's own discovery, with each case filed under the suite it was found in before it is
|
|
115
|
+
## asked whether to leave the case out. gdUnit4 files a case under the script declaring it, which
|
|
116
|
+
## for a case a suite inherits is its base: its reports added such a case to no suite, so
|
|
117
|
+
## project_test counted none of them, and a parameterised one ran as a suite of the base's own,
|
|
118
|
+
## outside the suite inheriting it and its hooks, and was judged a case that suite never ran.
|
|
119
|
+
func discover_tests() -> Array[GdUnitTestCase]:
|
|
120
|
+
var added: Signal = GdUnitSignals.instance().gdunit_test_discover_added
|
|
121
|
+
_test_cases = _runner_config.test_cases()
|
|
122
|
+
var scanner: GdUnitTestSuiteScanner = GdUnitTestSuiteScanner.new()
|
|
123
|
+
for path: String in _included_tests:
|
|
124
|
+
for script: Script in scanner.scan(path):
|
|
125
|
+
var suite: String = script.resource_path
|
|
126
|
+
var found: Callable = func(test: GdUnitTestCase) -> void:
|
|
127
|
+
filed_under(test, suite)
|
|
128
|
+
if not is_skipped(test):
|
|
129
|
+
_test_cases.append(test)
|
|
130
|
+
added.emit(test)
|
|
131
|
+
GdUnitTestDiscoverer.discover_tests(script, found)
|
|
132
|
+
return _test_cases
|
|
133
|
+
|
|
134
|
+
## [param test] as a case of the suite at [param suite], under which gdUnit4 groups, reports and
|
|
135
|
+
## leaves out its cases.
|
|
136
|
+
static func filed_under(test: GdUnitTestCase, suite: String) -> void:
|
|
137
|
+
if test.suite_resource_path == suite and test.source_file == suite:
|
|
138
|
+
return
|
|
139
|
+
test.suite_resource_path = suite
|
|
140
|
+
test.source_file = suite
|
|
141
|
+
test.suite_name = suite.get_file().get_basename()
|
|
142
|
+
test._build_fully_qualified_name(suite)
|
|
143
|
+
|
|
109
144
|
## gdUnit4 asks this of every case it finds, a parameterised case once a row, each row carrying
|
|
110
145
|
## its index.
|
|
111
146
|
func is_skipped(test: GdUnitTestCase) -> bool:
|
|
112
|
-
if super(test):
|
|
147
|
+
if left_out.has("%s:%s" % [test.suite_resource_path, test.test_name]) or super(test):
|
|
113
148
|
return true
|
|
114
149
|
var share: GdhRowShare = row_shares.get("%s:%s" % [test.suite_resource_path, test.test_name], null)
|
|
115
150
|
if share == null or not share.leaves_out(test.attribute_index):
|
|
@@ -348,7 +383,11 @@ static func started(given: PackedStringArray) -> GdhRunner:
|
|
|
348
383
|
if not expected_out.is_empty():
|
|
349
384
|
write_expected(expected_out, suites)
|
|
350
385
|
runner.add_test_suites(suites)
|
|
351
|
-
|
|
386
|
+
for entry: String in listed(asked, SKIPPING):
|
|
387
|
+
if is_one_case(entry):
|
|
388
|
+
runner.left_out[entry] = true
|
|
389
|
+
else:
|
|
390
|
+
runner.skip_test_suite(entry)
|
|
352
391
|
runner.row_shares = row_shares_of(listed(asked, ROWS))
|
|
353
392
|
# Added to the list itself rather than through `register`, which saves every hook it holds into
|
|
354
393
|
# `project.godot`.
|
|
@@ -426,6 +465,19 @@ static func listed(args: PackedStringArray, flag: String) -> PackedStringArray:
|
|
|
426
465
|
return named
|
|
427
466
|
|
|
428
467
|
|
|
468
|
+
## Whether [param entry] of a [constant SKIPPING] file names one case of one suite file, as
|
|
469
|
+
## `res://suite.gd:test_case`, rather than a suite or a case by gdUnit4's looser names, which go to
|
|
470
|
+
## gdUnit4 as they are.
|
|
471
|
+
static func is_one_case(entry: String) -> bool:
|
|
472
|
+
var at: int = entry.rfind(":")
|
|
473
|
+
return (
|
|
474
|
+
entry.begins_with("res://")
|
|
475
|
+
and at > "res://".length()
|
|
476
|
+
and entry.substr(0, at).ends_with(".gd")
|
|
477
|
+
and entry.substr(at + 1).is_valid_ascii_identifier()
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
|
|
429
481
|
## The row shares [param lines] of a [constant ROWS] file give, by case.
|
|
430
482
|
static func row_shares_of(lines: PackedStringArray) -> Dictionary[String, GdhRowShare]:
|
|
431
483
|
var shares: Dictionary[String, GdhRowShare] = {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.20",
|
|
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",
|