gdharness 1.3.42 → 1.3.43
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 +62 -9
- package/build/godot/addons/auto_reload/auto_reload.gd +18 -19
- package/build/godot/addons/gdharness_editor/animation_reads.gd +55 -33
- package/build/godot/addons/gdharness_editor/bridge_client.gd +53 -58
- package/build/godot/addons/gdharness_editor/declared_values.gd +68 -32
- package/build/godot/addons/gdharness_editor/file_reads.gd +83 -42
- package/build/godot/addons/gdharness_editor/file_walk.gd +12 -5
- package/build/godot/addons/gdharness_editor/headless_check.gd +68 -44
- package/build/godot/addons/gdharness_editor/plugin.gd +2 -3
- package/build/godot/addons/gdharness_editor/property_paths.gd +22 -12
- package/build/godot/addons/gdharness_editor/property_values.gd +143 -78
- package/build/godot/addons/gdharness_editor/reading.gd +37 -12
- package/build/godot/addons/gdharness_editor/same_values.gd +4 -4
- package/build/godot/addons/gdharness_editor/scene_file.gd +109 -79
- package/build/godot/addons/gdharness_editor/scene_uids.gd +12 -6
- package/build/godot/addons/gdharness_editor/serialisation.gd +75 -51
- package/build/godot/addons/gdharness_editor/setting_reads.gd +7 -6
- package/build/godot/addons/gdharness_editor/tool_executor.gd +3 -6
- package/build/godot/addons/gdharness_editor/tools/animation_states.gd +37 -27
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +79 -49
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +70 -40
- package/build/godot/addons/gdharness_editor/tools/class_tools.gd +34 -30
- package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +24 -22
- package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +49 -29
- package/build/godot/addons/gdharness_editor/tools/play_tools.gd +26 -21
- package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +40 -40
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +166 -107
- package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +16 -9
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +68 -41
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +35 -20
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +28 -13
- package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd +12 -5
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +113 -67
- package/build/godot/addons/gdharness_editor/tools/script_paths.gd +20 -8
- package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +11 -8
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +47 -34
- package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +77 -50
- package/build/godot/addons/gdharness_editor/value_shapes.gd +5 -6
- package/build/godot/addons/gdharness_runtime/call_errors.gd +1 -2
- package/build/godot/addons/gdharness_runtime/declared_values.gd +68 -32
- package/build/godot/addons/gdharness_runtime/error_report.gd +1 -2
- package/build/godot/addons/gdharness_runtime/reading.gd +37 -12
- package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +84 -63
- package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +149 -147
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +64 -42
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +184 -122
- package/build/godot/addons/gdharness_runtime/runtime_frames.gd +2 -2
- package/build/godot/addons/gdharness_runtime/runtime_hover.gd +24 -20
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +134 -92
- package/build/godot/addons/gdharness_runtime/runtime_items.gd +29 -13
- package/build/godot/addons/gdharness_runtime/runtime_menus.gd +0 -1
- package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +34 -17
- package/build/godot/addons/gdharness_runtime/runtime_pad.gd +17 -13
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +84 -46
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +117 -79
- package/build/godot/addons/gdharness_runtime/runtime_says.gd +14 -17
- package/build/godot/addons/gdharness_runtime/runtime_screen.gd +23 -16
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +117 -66
- package/build/godot/addons/gdharness_runtime/runtime_touch.gd +15 -15
- package/build/godot/addons/gdharness_runtime/runtime_typing.gd +44 -27
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +108 -73
- package/build/godot/addons/gdharness_runtime/runtime_waits.gd +111 -84
- package/build/godot/addons/gdharness_runtime/runtime_words.gd +10 -7
- package/build/godot/addons/gdharness_runtime/serialisation.gd +75 -51
- package/build/godot/addons/gdharness_runtime/value_shapes.gd +5 -6
- package/build/godot/addons/gdharness_tests/bench.gd +4 -5
- package/build/godot/addons/gdharness_tests/event.gd +10 -5
- package/build/godot/addons/gdharness_tests/share.gd +38 -18
- package/build/godot/addons/gdharness_tests/suites.gd +3 -3
- package/build/godot/addons/gdharness_tests/wait.gd +3 -2
- package/build/godot/loader/loader.gd +9 -9
- package/build/godot/operations/animation_reads.gd +55 -33
- package/build/godot/operations/audio_buses.gd +48 -19
- package/build/godot/operations/class_cache.gd +16 -11
- package/build/godot/operations/classdb_queries.gd +89 -44
- package/build/godot/operations/declared_values.gd +68 -32
- package/build/godot/operations/dependencies.gd +139 -93
- package/build/godot/operations/editor_tools.gd +9 -10
- package/build/godot/operations/export_presets.gd +100 -54
- package/build/godot/operations/file_reads.gd +83 -42
- package/build/godot/operations/file_walk.gd +12 -5
- package/build/godot/operations/gdscript_analysis.gd +86 -65
- package/build/godot/operations/gdscript_authoring.gd +83 -36
- package/build/godot/operations/godot_operations.gd +17 -11
- package/build/godot/operations/import_pipeline.gd +110 -59
- package/build/godot/operations/input_actions.gd +62 -39
- package/build/godot/operations/patterns.gd +3 -1
- package/build/godot/operations/plugins.gd +36 -16
- package/build/godot/operations/project_config.gd +53 -29
- package/build/godot/operations/project_diagnostics.gd +24 -11
- package/build/godot/operations/project_file.gd +16 -16
- package/build/godot/operations/project_validation.gd +71 -44
- package/build/godot/operations/property_paths.gd +22 -12
- package/build/godot/operations/property_values.gd +143 -78
- package/build/godot/operations/reading.gd +37 -12
- package/build/godot/operations/rename_checks.gd +2 -2
- package/build/godot/operations/resource_files.gd +11 -9
- package/build/godot/operations/same_values.gd +4 -4
- package/build/godot/operations/scene_connections.gd +27 -13
- package/build/godot/operations/script_check.gd +52 -49
- package/build/godot/operations/serialisation.gd +75 -51
- package/build/godot/operations/setting_reads.gd +7 -6
- package/build/godot/operations/value_shapes.gd +5 -6
- package/build/godot/tests/listed.gd +321 -268
- package/package.json +1 -1
package/build/cli.js
CHANGED
|
@@ -10914,6 +10914,36 @@ function rowOf(name) {
|
|
|
10914
10914
|
function rowRuns(share, row) {
|
|
10915
10915
|
return "only" in share ? row !== null && share.only.includes(row) : row === null || !share.besides.includes(row);
|
|
10916
10916
|
}
|
|
10917
|
+
function mismatchedRowsIn(printed) {
|
|
10918
|
+
const lines = printed.flatMap((text) => stripVTControlCharacters(text).split(/\r?\n/));
|
|
10919
|
+
const found = [];
|
|
10920
|
+
let suite = null;
|
|
10921
|
+
let lastCase = null;
|
|
10922
|
+
for (let at = 0;at < lines.length; at += 1) {
|
|
10923
|
+
const line = lines[at] ?? "";
|
|
10924
|
+
const opened = SUITE_OPENED.exec(line)?.[1];
|
|
10925
|
+
if (opened !== undefined) {
|
|
10926
|
+
suite = opened;
|
|
10927
|
+
lastCase = null;
|
|
10928
|
+
continue;
|
|
10929
|
+
}
|
|
10930
|
+
const caseLine = CASE_LINE.exec(line);
|
|
10931
|
+
if (caseLine !== null) {
|
|
10932
|
+
lastCase = caseLine[3] === "STARTED" ? lastCase : caseLine[2] ?? null;
|
|
10933
|
+
continue;
|
|
10934
|
+
}
|
|
10935
|
+
if (suite === null || lastCase === null || !MISMATCHED_ROW.test(line)) {
|
|
10936
|
+
continue;
|
|
10937
|
+
}
|
|
10938
|
+
const parts = [line.trim()];
|
|
10939
|
+
while (at + 1 < lines.length && !["", "'"].includes((lines[at + 1] ?? "").trim())) {
|
|
10940
|
+
at += 1;
|
|
10941
|
+
parts.push((lines[at] ?? "").trim());
|
|
10942
|
+
}
|
|
10943
|
+
found.push({ suite, name: lastCase, reason: parts.join(" ").replaceAll(/\[\/?color[^\]]*\]/g, "") });
|
|
10944
|
+
}
|
|
10945
|
+
return found;
|
|
10946
|
+
}
|
|
10917
10947
|
function readRun(printed) {
|
|
10918
10948
|
const lines = stripVTControlCharacters(printed).replaceAll(`\r
|
|
10919
10949
|
`, `
|
|
@@ -11045,7 +11075,8 @@ function readRun(printed) {
|
|
|
11045
11075
|
running: last === null ? null : { suite: last.path, during },
|
|
11046
11076
|
leftOut,
|
|
11047
11077
|
engineVersion,
|
|
11048
|
-
orphansLeft
|
|
11078
|
+
orphansLeft,
|
|
11079
|
+
mismatched: mismatchedRowsIn(lines)
|
|
11049
11080
|
};
|
|
11050
11081
|
}
|
|
11051
11082
|
function withNestedErrors(printed, piped) {
|
|
@@ -11170,6 +11201,10 @@ function judgeEngine(printed, handed, end) {
|
|
|
11170
11201
|
if (printed.executed === null || printed.executed === 0) {
|
|
11171
11202
|
faults.push(printed.executed === null ? "the run never printed its summary" : "the run executed no test case at all, whatever it exited with");
|
|
11172
11203
|
}
|
|
11204
|
+
for (const row of printed.mismatched) {
|
|
11205
|
+
faults.push(`${row.suite} > ${row.name} never ran: gdUnit4 skipped it, saying "${row.reason}"`);
|
|
11206
|
+
failing.add(row.suite);
|
|
11207
|
+
}
|
|
11173
11208
|
for (const { path, cases, rows = {} } of handed.suites) {
|
|
11174
11209
|
const suite = printed.suites.get(path);
|
|
11175
11210
|
if (suite === undefined) {
|
|
@@ -11333,7 +11368,7 @@ ${String(error.warning)}`);
|
|
|
11333
11368
|
const neverPrinted = [...owed.values()].flatMap((debt) => debt.texts.map((text) => ({ suite: debt.suite, during: debt.during, text, warning: debt.warning })));
|
|
11334
11369
|
return { printed: unexpected, neverPrinted };
|
|
11335
11370
|
}
|
|
11336
|
-
var SUITE_OPENED, TIME, CASE_LINE, STATISTICS, ERROR_LINE, WARNING_LINE, EXECUTED, BANNER, LEFT_OUT, DRIVER_FALLBACK, MACHINE_WARNED, ORPHANS_REPORTED, STARVED, NESTED_ERROR = "While attempting to print an error, another error was printed:", WINDOWS_EXITS, MEMORY_EXITS, GDUNIT_FAILED = 100, GDUNIT_ORPHANS = 101, GDUNIT_EXITS, GODOT_EXITS, TAIL = 60, MOST_NAMED = 8;
|
|
11371
|
+
var SUITE_OPENED, TIME, CASE_LINE, STATISTICS, ERROR_LINE, WARNING_LINE, EXECUTED, MISMATCHED_ROW, BANNER, LEFT_OUT, DRIVER_FALLBACK, MACHINE_WARNED, ORPHANS_REPORTED, STARVED, NESTED_ERROR = "While attempting to print an error, another error was printed:", WINDOWS_EXITS, MEMORY_EXITS, GDUNIT_FAILED = 100, GDUNIT_ORPHANS = 101, GDUNIT_EXITS, GODOT_EXITS, TAIL = 60, MOST_NAMED = 8;
|
|
11337
11372
|
var init_suite_judge = __esm(() => {
|
|
11338
11373
|
init_engine_leaks();
|
|
11339
11374
|
init_junit();
|
|
@@ -11344,6 +11379,7 @@ var init_suite_judge = __esm(() => {
|
|
|
11344
11379
|
ERROR_LINE = /^\s*(?:SCRIPT |USER )?ERROR: (.*?)\s*$/;
|
|
11345
11380
|
WARNING_LINE = /^\s*(?:SCRIPT |USER )?WARNING: (.*?)\s*$/;
|
|
11346
11381
|
EXECUTED = /^Executed test cases\s*:\s*\((\d+)\//;
|
|
11382
|
+
MISMATCHED_ROW = /^\s*The test data set at index \(\d+\) does not match the expected test parameters/;
|
|
11347
11383
|
BANNER = /^\s*Godot Engine v(\S+?)(?:\s+-\s+\S+)?\s*$/;
|
|
11348
11384
|
LEFT_OUT = /^gdharness left out (res:\/\/.+?\.gd) > ([^:\s]+):(-?\d+)\s*$/;
|
|
11349
11385
|
DRIVER_FALLBACK = /^\s*WARNING: Your video card drivers .*, switching to /;
|
|
@@ -16472,7 +16508,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16472
16508
|
position: {
|
|
16473
16509
|
type: "string",
|
|
16474
16510
|
enum: ["end", "after_ready", "after_init"],
|
|
16475
|
-
description: "Functions: where to insert. end, the default, is the end of the file; after_ready and after_init are after _ready or _init, above the comments and annotation lines that open the function after it, and the end when nothing follows. A function goes in with two blank lines on each side, as gdformat
|
|
16511
|
+
description: "Functions: where to insert. end, the default, is the end of the file; after_ready and after_init are after _ready or _init, above the comments and annotation lines that open the function after it, and the end when nothing follows. A function goes in with two blank lines on each side, as gdstyle and gdformat write them."
|
|
16476
16512
|
}
|
|
16477
16513
|
},
|
|
16478
16514
|
required: ["type", "name"],
|
|
@@ -16947,7 +16983,7 @@ var init_tool_definitions = __esm(() => {
|
|
|
16947
16983
|
path: {
|
|
16948
16984
|
type: ["string", "array"],
|
|
16949
16985
|
items: { type: "string" },
|
|
16950
|
-
description: 'A test directory or suite file inside the project, or a list of them, all run on one engine: a named set of suites boots once rather than once per suite. An entry given twice, or a suite inside a directory also listed, runs once. An entry "suite.gd:test_name" runs that case alone, as gdharness test reads it and as tests runs it, so it cannot share the list with a whole suite or a directory. An entry that is not there is left out and named under missing, the rest run, and such a run is never called passed; when no entry is there the run is refused before the engine starts, naming each one. A script named that declares no test_ function at the top level is refused the same way, and so is a directory holding no suite. A script that declares test_ functions and does not extend GdUnitTestSuite, which gdUnit4 never runs and never mentions, is refused when named, and under a directory run whole it is named under notSuites and the run is not called passed. Default tests and test, those the project has, which is where gdharness test looks.'
|
|
16986
|
+
description: 'A test directory or suite file inside the project, or a list of them, all run on one engine: a named set of suites boots once rather than once per suite. An entry given twice, or a suite inside a directory also listed, runs once. An entry "suite.gd:test_name" runs that case alone, as gdharness test reads it and as tests runs it, so it cannot share the list with a whole suite or a directory. An entry that is not there is left out and named under missing, the rest run, and such a run is never called passed; when no entry is there the run is refused before the engine starts, naming each one. A script named that declares no test_ function at the top level is refused the same way, and so is a directory holding no suite. A script that declares test_ functions and does not extend GdUnitTestSuite, which gdUnit4 never runs and never mentions, is refused when named, and under a directory run whole it is named under notSuites and the run is not called passed. A case declared there that gdUnit4 never ran, in a suite that passed, comes back under testsNotRun, and a row it skipped for data that does not fit the case under rowsMismatched with the reason gdUnit4 gave; neither run is called passed. Default tests and test, those the project has, which is where gdharness test looks.'
|
|
16951
16987
|
},
|
|
16952
16988
|
ignore: {
|
|
16953
16989
|
type: "array",
|
|
@@ -53287,10 +53323,23 @@ ${error.text}`;
|
|
|
53287
53323
|
const testsNotRun = [
|
|
53288
53324
|
...new Set((selection.value?.selected ?? []).filter((one) => !report.suites.some((suite) => (suite.path === one.suite || suite.name === basename13(one.suite, ".gd")) && suite.cases.some((ran) => ran.name === one.test || new RegExp(`^${one.test}\\W`).test(ran.name)))).map((one) => `${basename13(one.suite, ".gd")}:${one.test}`))
|
|
53289
53325
|
];
|
|
53326
|
+
const casesNotRun = selection.value === null ? asked.flatMap((path) => path.endsWith(".gd") ? projectScripts.suite(path) ?? [] : projectScripts.suitesUnder([path])).flatMap((suite) => {
|
|
53327
|
+
const ran = casePrinted.suites.get(suite.path);
|
|
53328
|
+
if (ran?.closed?.verdict !== "PASSED") {
|
|
53329
|
+
return [];
|
|
53330
|
+
}
|
|
53331
|
+
return suite.cases.filter((func) => !ran.cases.some((one) => one.func === func)).map((func) => `${basename13(suite.path, ".gd")}:${func}`).filter((one) => !ignoreRead.leftOut.includes(one));
|
|
53332
|
+
}) : [];
|
|
53333
|
+
const firstUndeclared = undeclaredErrors[0];
|
|
53334
|
+
const declaredAs = firstUndeclared?.case === undefined ? "" : ` A suite declaring none yet does it as const ${firstUndeclared.warning === true ? "GDHARNESS_EXPECTED_WARNINGS" : "GDHARNESS_EXPECTED_ERRORS"}: Dictionary = ${JSON.stringify({ [funcOf(firstUndeclared.case)]: [firstUndeclared.text] })}, keyed by case function, or by before and after for the suite's own hooks.`;
|
|
53335
|
+
const rowsMismatched = casePrinted.mismatched.map((row) => ({
|
|
53336
|
+
test: `${row.suite} > ${row.name}`,
|
|
53337
|
+
reason: row.reason
|
|
53338
|
+
}));
|
|
53290
53339
|
return this.jsonTextResponse({
|
|
53291
|
-
passed: !timedOut && exitCode === 0 && nothingRan === null && report.tests > 0 && report.failures === 0 && report.errors === 0 && hookFailures.length === 0 && uncaught.length === 0 && undeclaredPrinted.length === 0 && declaredNotPrinted.length === 0 && countedLeaks.length === 0 && missing.length === 0 && testsNotRun.length === 0 && notSuites.length === 0,
|
|
53340
|
+
passed: !timedOut && exitCode === 0 && nothingRan === null && report.tests > 0 && report.failures === 0 && report.errors === 0 && hookFailures.length === 0 && uncaught.length === 0 && undeclaredPrinted.length === 0 && declaredNotPrinted.length === 0 && countedLeaks.length === 0 && missing.length === 0 && testsNotRun.length === 0 && casesNotRun.length === 0 && rowsMismatched.length === 0 && notSuites.length === 0,
|
|
53292
53341
|
verdict: [
|
|
53293
|
-
verdict === "passed" && undeclaredPrinted.length + declaredNotPrinted.length + countedLeaks.length + testsNotRun.length + notSuites.length > 0 ? undefined : verdict,
|
|
53342
|
+
verdict === "passed" && undeclaredPrinted.length + declaredNotPrinted.length + countedLeaks.length + testsNotRun.length + casesNotRun.length + rowsMismatched.length + notSuites.length > 0 ? undefined : verdict,
|
|
53294
53343
|
...[false, true].map((warning) => {
|
|
53295
53344
|
const count = undeclaredPrinted.filter((error) => error.warning === warning).length;
|
|
53296
53345
|
const word = warning ? "warning" : "error";
|
|
@@ -53302,12 +53351,14 @@ ${error.text}`;
|
|
|
53302
53351
|
return count > 0 ? `${count === 1 ? `one declared ${word}` : `${String(count)} declared ${word}s`} never printed` : undefined;
|
|
53303
53352
|
}),
|
|
53304
53353
|
countedLeaks.length > 0 ? "the engine exited holding what it should have freed" : undefined,
|
|
53305
|
-
testsNotRun.length > 0 ? `not run: ${testsNotRun.join(", ")}` : undefined,
|
|
53354
|
+
testsNotRun.length + casesNotRun.length > 0 ? `not run: ${[...testsNotRun, ...casesNotRun].join(", ")}` : undefined,
|
|
53355
|
+
rowsMismatched.length > 0 ? `${rowsMismatched.length === 1 ? "one row" : `${String(rowsMismatched.length)} rows`} skipped for data that does not fit the case` : undefined,
|
|
53306
53356
|
notSuites.length > 0 ? `${notSuites.length === 1 ? "one script declares" : `${String(notSuites.length)} scripts declare`} cases gdUnit4 never runs` : undefined
|
|
53307
53357
|
].filter((part) => part !== undefined).join("; "),
|
|
53308
53358
|
...missing.length > 0 ? { missing } : {},
|
|
53309
53359
|
...notSuites.length > 0 ? { notSuites } : {},
|
|
53310
|
-
...testsNotRun.length > 0 ? { testsNotRun } : {},
|
|
53360
|
+
...testsNotRun.length + casesNotRun.length > 0 ? { testsNotRun: [...testsNotRun, ...casesNotRun] } : {},
|
|
53361
|
+
...rowsMismatched.length > 0 ? { rowsMismatched } : {},
|
|
53311
53362
|
...timedOut ? { timedOut, hung, silentForMs } : {},
|
|
53312
53363
|
exitCode,
|
|
53313
53364
|
exitSignal: run.exitSignal ?? undefined,
|
|
@@ -53339,12 +53390,14 @@ ${error.text}`;
|
|
|
53339
53390
|
note: [
|
|
53340
53391
|
missingNote,
|
|
53341
53392
|
testsNotRun.length > 0 ? `${testsNotRun.join(", ")} ${testsNotRun.length === 1 ? "was" : "were"} asked for and not run, so the run is not called passed: gdUnit4 runs a test_ function only in a script that extends GdUnitTestSuite, and leaves out a suite whose script does not load.` : undefined,
|
|
53393
|
+
casesNotRun.length > 0 ? `${casesNotRun.join(", ")} ${casesNotRun.length === 1 ? "is a case" : "are cases"} of a suite that ran and passed, and gdUnit4 never ran ${casesNotRun.length === 1 ? "it" : "them"} and said nothing of it, so the run is not called passed, as gdharness test judges it.` : undefined,
|
|
53394
|
+
rowsMismatched.length > 0 ? `gdUnit4 skipped what rowsMismatched names because the row's data does not fit the case's parameters, though it printed ${rowsMismatched.length === 1 ? "the row" : "each row"} passed first; each entry carries gdUnit4's reason. The run is not called passed, as gdharness test judges it.` : undefined,
|
|
53342
53395
|
notSuites.length > 0 ? "What notSuites names declares cases gdUnit4 never runs, so the run is not called passed, as gdharness test judges it." : undefined,
|
|
53343
53396
|
briefed > 0 ? `${briefed} of the ${failed.length} failing cases, those with the largest reports, are named with their first message only, since their reports would have made the answer too large to deliver; pass one case's suite path to see its reports.` : undefined,
|
|
53344
53397
|
notApplied.length > 0 ? "An ignore entry named a case that ran all the same: gdUnit4 leaves a case out only as suite_name:test_name, and reads an entry with no colon as a suite name or a piece of a suite path. ignoreNotApplied gives the form for each case that ran." : undefined,
|
|
53345
53398
|
unmatched.length > 0 ? `${unmatched.join(", ")} ${unmatched.length === 1 ? "names" : "name"} no suite, case or row under the paths run, so ${unmatched.length === 1 ? "it" : "they"} left nothing out.` : undefined,
|
|
53346
53399
|
uncaughtNote(uncaught),
|
|
53347
|
-
undeclaredErrors.length > 0 ? `An engine error or warning a suite does not declare fails the run, as gdharness test judges it; ${declaringAdvice(undeclaredErrors[0]?.case ?? "test_name", undeclaredErrors[0]?.warning === true)}, once for each time it prints it${undeclaredErrors.some((error) => error.warning !== undeclaredErrors[0]?.warning) ? undeclaredErrors[0]?.warning === true ? ", and an error in its GDHARNESS_EXPECTED_ERRORS" : ", and a warning in its GDHARNESS_EXPECTED_WARNINGS" : ""}
|
|
53400
|
+
undeclaredErrors.length > 0 ? `An engine error or warning a suite does not declare fails the run, as gdharness test judges it; ${declaringAdvice(undeclaredErrors[0]?.case ?? "test_name", undeclaredErrors[0]?.warning === true)}, once for each time it prints it${undeclaredErrors.some((error) => error.warning !== undeclaredErrors[0]?.warning) ? undeclaredErrors[0]?.warning === true ? ", and an error in its GDHARNESS_EXPECTED_ERRORS" : ", and a warning in its GDHARNESS_EXPECTED_WARNINGS" : ""}.${declaredAs}` : undefined,
|
|
53348
53401
|
declaredNotPrinted.length > 0 ? "A declared error or warning the case did not print fails the run too, since the case no longer reaches what it was written to test." : undefined,
|
|
53349
53402
|
walkStopped ? WALK_STOPPED_SENTENCE : undefined,
|
|
53350
53403
|
countedLeaks.length > 0 ? [
|
|
@@ -1,24 +1,10 @@
|
|
|
1
1
|
@tool
|
|
2
2
|
extends EditorPlugin
|
|
3
|
-
|
|
4
3
|
## Reloads the open scene and the scripts it carries when they change on disk, without the
|
|
5
4
|
## editor's confirmation popup. Every edit made through gdharness lands on disk, and an editor
|
|
6
5
|
## that keeps showing the old file is how a change reads as one that never happened. The
|
|
7
6
|
## files are polled once a second; the editor's own watcher only fires on window focus.
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
## What was last seen of a watched file.
|
|
11
|
-
class GdhSeen:
|
|
12
|
-
## Its modification time, in whole seconds.
|
|
13
|
-
var time: int
|
|
14
|
-
## The MD5 of its contents.
|
|
15
|
-
var md5: String
|
|
16
|
-
|
|
17
|
-
func _init(seen_time: int, seen_md5: String) -> void:
|
|
18
|
-
time = seen_time
|
|
19
|
-
md5 = seen_md5
|
|
20
|
-
|
|
21
|
-
|
|
22
8
|
const CHECK_INTERVAL_SECONDS: float = 1.0
|
|
23
9
|
## How long after a file's modification time its contents are compared as well. The time is in
|
|
24
10
|
## whole seconds, so a second write inside the second of the first leaves it as it was and changes
|
|
@@ -135,15 +121,16 @@ func _reload_scene(path: String) -> void:
|
|
|
135
121
|
var edited_scene: Node = EditorInterface.get_edited_scene_root()
|
|
136
122
|
if not edited_scene or edited_scene.scene_file_path != path:
|
|
137
123
|
return
|
|
138
|
-
# A reload from the file drops the edits the editor holds, without a prompt. gdharness refuses
|
|
139
|
-
# write over them, so what is seen here is a write by somebody else or one the editor's tab
|
|
140
|
-
# already taken in, made just before an edit inside the same poll, and neither is worth the
|
|
124
|
+
# A reload from the file drops the edits the editor holds, without a prompt. gdharness refuses
|
|
125
|
+
# to write over them, so what is seen here is a write by somebody else or one the editor's tab
|
|
126
|
+
# has already taken in, made just before an edit inside the same poll, and neither is worth the
|
|
127
|
+
# edit.
|
|
141
128
|
if EditorInterface.get_unsaved_scenes().has(path):
|
|
142
129
|
push_warning(
|
|
143
130
|
(
|
|
144
131
|
(
|
|
145
|
-
"gdharness: %s changed on disk while the editor holds unsaved changes to it,
|
|
146
|
-
+ "was not reloaded; saving it writes the editor's copy over the file"
|
|
132
|
+
"gdharness: %s changed on disk while the editor holds unsaved changes to it, "
|
|
133
|
+
+ "so it was not reloaded; saving it writes the editor's copy over the file"
|
|
147
134
|
)
|
|
148
135
|
% path
|
|
149
136
|
)
|
|
@@ -151,3 +138,15 @@ func _reload_scene(path: String) -> void:
|
|
|
151
138
|
return
|
|
152
139
|
print("[gdharness] Scene changed on disk, reloading: ", path)
|
|
153
140
|
EditorInterface.reload_scene_from_path(path)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
## What was last seen of a watched file.
|
|
144
|
+
class GdhSeen:
|
|
145
|
+
## Its modification time, in whole seconds.
|
|
146
|
+
var time: int
|
|
147
|
+
## The MD5 of its contents.
|
|
148
|
+
var md5: String
|
|
149
|
+
|
|
150
|
+
func _init(seen_time: int, seen_md5: String) -> void:
|
|
151
|
+
time = seen_time
|
|
152
|
+
md5 = seen_md5
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
extends RefCounted
|
|
2
|
-
|
|
3
|
-
##
|
|
4
|
-
##
|
|
5
|
-
## stores, which runs no script of the scene's, so a read answers the same either way.
|
|
2
|
+
## Animations and state machines read off their nodes: by the editor's tools from the scene it
|
|
3
|
+
## holds, and by a headless engine with none connected from a bare node of the class given what the
|
|
4
|
+
## file stores, which runs no script of the scene's, so a read answers the same either way.
|
|
6
5
|
|
|
7
6
|
const GdhFileReads = preload("file_reads.gd")
|
|
8
7
|
const GdhRead = preload("reading.gd")
|
|
9
8
|
const GdhSerialisation = preload("serialisation.gd")
|
|
10
|
-
|
|
11
9
|
const LOOP_MODE_NAMES: Dictionary[int, String] = {
|
|
12
10
|
Animation.LOOP_NONE: "none",
|
|
13
11
|
Animation.LOOP_LINEAR: "linear",
|
|
@@ -37,7 +35,9 @@ const ADVANCE_MODE_NAMES: Dictionary[int, String] = {
|
|
|
37
35
|
|
|
38
36
|
|
|
39
37
|
## [method animations] of the AnimationPlayer at [param player_path] in the file [param scene_path].
|
|
40
|
-
static func scene_animations(
|
|
38
|
+
static func scene_animations(
|
|
39
|
+
scene_path: String, player_path: String, animation_name: String
|
|
40
|
+
) -> Dictionary:
|
|
41
41
|
var made: Array = _bare(scene_path, player_path, "AnimationPlayer")
|
|
42
42
|
if made[0] == null:
|
|
43
43
|
return made[1]
|
|
@@ -48,7 +48,9 @@ static func scene_animations(scene_path: String, player_path: String, animation_
|
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
## [method states] of the AnimationTree at [param tree_path] in the file [param scene_path].
|
|
51
|
-
static func scene_states(
|
|
51
|
+
static func scene_states(
|
|
52
|
+
scene_path: String, tree_path: String, state_machine_path: String
|
|
53
|
+
) -> Dictionary:
|
|
52
54
|
var made: Array = _bare(scene_path, tree_path, "AnimationTree")
|
|
53
55
|
if made[0] == null:
|
|
54
56
|
return made[1]
|
|
@@ -104,9 +106,9 @@ static func animations(player: AnimationPlayer, animation_name: String) -> Dicti
|
|
|
104
106
|
summary["animationName"] = called
|
|
105
107
|
summary["tracks"] = listed_one.get_track_count()
|
|
106
108
|
listed.append(summary)
|
|
107
|
-
return {"ok": true, "animations": listed}
|
|
109
|
+
return { "ok": true, "animations": listed }
|
|
108
110
|
if not player.has_animation(StringName(animation_name)):
|
|
109
|
-
return {"ok": false, "error": no_animation(player, str(player.name), animation_name)}
|
|
111
|
+
return { "ok": false, "error": no_animation(player, str(player.name), animation_name) }
|
|
110
112
|
var anim: Animation = player.get_animation(StringName(animation_name))
|
|
111
113
|
var answer: Dictionary = _summary(anim)
|
|
112
114
|
answer["ok"] = true
|
|
@@ -158,7 +160,8 @@ static func _track(anim: Animation, track: int, values: GdhSerialisation) -> Dic
|
|
|
158
160
|
var kind: int = anim.track_get_type(track)
|
|
159
161
|
var path: NodePath = anim.track_get_path(track)
|
|
160
162
|
var shaped: Dictionary = {
|
|
161
|
-
"type": TRACK_TYPE_NAMES.get(kind, str(kind)),
|
|
163
|
+
"type": TRACK_TYPE_NAMES.get(kind, str(kind)),
|
|
164
|
+
"nodePath": str(path.get_concatenated_names()),
|
|
162
165
|
}
|
|
163
166
|
var property: String = str(path.get_concatenated_subnames())
|
|
164
167
|
if not property.is_empty():
|
|
@@ -166,7 +169,7 @@ static func _track(anim: Animation, track: int, values: GdhSerialisation) -> Dic
|
|
|
166
169
|
var keyframes: Array[Dictionary] = []
|
|
167
170
|
var methods: Dictionary = {}
|
|
168
171
|
for key: int in anim.track_get_key_count(track):
|
|
169
|
-
var keyframe: Dictionary = {"time": held_seconds(anim.track_get_key_time(track, key))}
|
|
172
|
+
var keyframe: Dictionary = { "time": held_seconds(anim.track_get_key_time(track, key)) }
|
|
170
173
|
if kind == Animation.TYPE_METHOD:
|
|
171
174
|
var method: String = str(anim.method_track_get_name(track, key))
|
|
172
175
|
methods[method] = true
|
|
@@ -188,7 +191,9 @@ static func _track(anim: Animation, track: int, values: GdhSerialisation) -> Dic
|
|
|
188
191
|
## The state machine at a slash-separated path of nested state machine names in [param tree], or
|
|
189
192
|
## null when any step of the path is not one. Every node in a tree of state machines is a state
|
|
190
193
|
## machine itself, so the walk asks each step for the child by name and refuses anything else.
|
|
191
|
-
static func state_machine(
|
|
194
|
+
static func state_machine(
|
|
195
|
+
tree: AnimationTree, state_machine_path: String
|
|
196
|
+
) -> AnimationNodeStateMachine:
|
|
192
197
|
if tree == null:
|
|
193
198
|
return null
|
|
194
199
|
var current: AnimationNodeStateMachine = tree.tree_root as AnimationNodeStateMachine
|
|
@@ -207,8 +212,8 @@ static func no_machine(tree: AnimationTree, state_machine_path: String) -> Strin
|
|
|
207
212
|
if tree == null or tree.tree_root == null:
|
|
208
213
|
return (
|
|
209
214
|
"The AnimationTree has no tree_root. Give it one with scene_node set: tree_root "
|
|
210
|
-
+ '{"_type": "AnimationNodeStateMachine"} builds one into the scene, and the res://
|
|
211
|
-
+ "of one made with resource_edit create keeps it in a file of its own."
|
|
215
|
+
+ '{"_type": "AnimationNodeStateMachine"} builds one into the scene, and the res:// '
|
|
216
|
+
+ "path of one made with resource_edit create keeps it in a file of its own."
|
|
212
217
|
)
|
|
213
218
|
var current: AnimationNodeStateMachine = tree.tree_root as AnimationNodeStateMachine
|
|
214
219
|
if current == null:
|
|
@@ -242,8 +247,8 @@ static func no_machine(tree: AnimationTree, state_machine_path: String) -> Strin
|
|
|
242
247
|
return "No state machine at " + state_machine_path
|
|
243
248
|
|
|
244
249
|
|
|
245
|
-
## Why no state finds its animation when [param anim_player], an AnimationTree's path to its
|
|
246
|
-
##
|
|
250
|
+
## Why no state finds its animation when [param anim_player], an AnimationTree's path to its player,
|
|
251
|
+
## reaches no AnimationPlayer: [param reached] is the node it reaches instead, null for none.
|
|
247
252
|
static func no_player(anim_player: String, reached: Node) -> String:
|
|
248
253
|
var until: String = "so no state plays anything until scene_node set points anim_player at one."
|
|
249
254
|
if anim_player.is_empty():
|
|
@@ -259,16 +264,25 @@ static func no_player(anim_player: String, reached: Node) -> String:
|
|
|
259
264
|
## Why a state playing [param animation_name] from [param player], at [param player_path] from the
|
|
260
265
|
## scene root as playerNodePath takes it, plays nothing yet. anim_player is relative to the tree
|
|
261
266
|
## ("../Anim"), which no argument takes.
|
|
262
|
-
static func missing_animation(
|
|
267
|
+
static func missing_animation(
|
|
268
|
+
player: AnimationPlayer, player_path: String, animation_name: String
|
|
269
|
+
) -> String:
|
|
263
270
|
var said: String = (
|
|
264
|
-
|
|
271
|
+
(
|
|
272
|
+
"The AnimationPlayer at %s has no animation %s yet, so the state plays nothing until "
|
|
273
|
+
+ "it has one."
|
|
274
|
+
)
|
|
265
275
|
% [player_path, animation_name]
|
|
266
276
|
)
|
|
267
277
|
var close: PackedStringArray = GdhRead.nearest(animation_name, player.get_animation_list())
|
|
268
278
|
if close.is_empty():
|
|
269
279
|
return said
|
|
270
280
|
return (
|
|
271
|
-
said
|
|
281
|
+
said
|
|
282
|
+
+ (
|
|
283
|
+
" The nearest %s %s."
|
|
284
|
+
% ["it has is" if close.size() == 1 else "it has are", ", ".join(close)]
|
|
285
|
+
)
|
|
272
286
|
)
|
|
273
287
|
|
|
274
288
|
|
|
@@ -285,11 +299,11 @@ static func states(
|
|
|
285
299
|
) -> Dictionary:
|
|
286
300
|
var sm: AnimationNodeStateMachine = state_machine(tree, state_machine_path)
|
|
287
301
|
if sm == null:
|
|
288
|
-
return {"ok": false, "error": no_machine(tree, state_machine_path)}
|
|
302
|
+
return { "ok": false, "error": no_machine(tree, state_machine_path) }
|
|
289
303
|
var listed: Array[Dictionary] = []
|
|
290
304
|
for state: StringName in sm.get_node_list():
|
|
291
305
|
var node: AnimationNode = sm.get_node(state)
|
|
292
|
-
var entry: Dictionary = {"stateName": str(state), "type": node.get_class()}
|
|
306
|
+
var entry: Dictionary = { "stateName": str(state), "type": node.get_class() }
|
|
293
307
|
if node is AnimationNodeAnimation:
|
|
294
308
|
var plays: AnimationNodeAnimation = node
|
|
295
309
|
entry["animationName"] = str(plays.animation)
|
|
@@ -298,7 +312,9 @@ static func states(
|
|
|
298
312
|
if player == null:
|
|
299
313
|
entry["animationMissing"] = unplayed
|
|
300
314
|
elif not found:
|
|
301
|
-
entry["animationMissing"] = missing_animation(
|
|
315
|
+
entry["animationMissing"] = missing_animation(
|
|
316
|
+
player, player_path, str(plays.animation)
|
|
317
|
+
)
|
|
302
318
|
listed.append(entry)
|
|
303
319
|
var transitions: Array[Dictionary] = []
|
|
304
320
|
for index: int in sm.get_transition_count():
|
|
@@ -306,8 +322,10 @@ static func states(
|
|
|
306
322
|
var shaped: Dictionary = {
|
|
307
323
|
"from": str(sm.get_transition_from(index)),
|
|
308
324
|
"to": str(sm.get_transition_to(index)),
|
|
309
|
-
"transitionType":
|
|
310
|
-
|
|
325
|
+
"transitionType":
|
|
326
|
+
SWITCH_MODE_NAMES.get(transition.switch_mode, str(transition.switch_mode)),
|
|
327
|
+
"advanceMode":
|
|
328
|
+
ADVANCE_MODE_NAMES.get(transition.advance_mode, str(transition.advance_mode)),
|
|
311
329
|
}
|
|
312
330
|
var condition: String = str(transition.advance_condition)
|
|
313
331
|
if not condition.is_empty():
|
|
@@ -317,9 +335,9 @@ static func states(
|
|
|
317
335
|
if not condition.is_empty() and transition.advance_mode != transition.ADVANCE_MODE_AUTO:
|
|
318
336
|
shaped["conditionNote"] = (
|
|
319
337
|
(
|
|
320
|
-
"advanceMode is %s, so %s never advances it: the engine checks a condition
|
|
321
|
-
+ " auto transition%s. disconnect_states and connect_states again
|
|
322
|
-
+ " write it as auto"
|
|
338
|
+
"advanceMode is %s, so %s never advances it: the engine checks a condition "
|
|
339
|
+
+ "only on an auto transition%s. disconnect_states and connect_states again "
|
|
340
|
+
+ "with the condition write it as auto"
|
|
323
341
|
)
|
|
324
342
|
% [
|
|
325
343
|
shaped["advanceMode"],
|
|
@@ -328,16 +346,16 @@ static func states(
|
|
|
328
346
|
", and travel() still takes this one"
|
|
329
347
|
if transition.advance_mode == transition.ADVANCE_MODE_ENABLED
|
|
330
348
|
else ""
|
|
331
|
-
)
|
|
349
|
+
),
|
|
332
350
|
]
|
|
333
351
|
)
|
|
334
352
|
transitions.append(shaped)
|
|
335
|
-
return {"ok": true, "states": listed, "transitions": transitions}
|
|
353
|
+
return { "ok": true, "states": listed, "transitions": transitions }
|
|
336
354
|
|
|
337
355
|
|
|
338
356
|
## The states of the AnimationTrees under [param root] that play [param animation_name] from
|
|
339
|
-
## [param player], as [[node, {animTreePath, state}]], a state named by the path of the nodes
|
|
340
|
-
## to it.
|
|
357
|
+
## [param player], as [[node, {animTreePath, state}]], a state named by the path of the nodes
|
|
358
|
+
## leading to it.
|
|
341
359
|
static func states_playing(root: Node, player: AnimationPlayer, animation_name: String) -> Array:
|
|
342
360
|
var found: Array = []
|
|
343
361
|
var trees: Array[Node] = root.find_children("*", "AnimationTree", true, false)
|
|
@@ -358,7 +376,9 @@ static func _collect_playing(
|
|
|
358
376
|
if node is AnimationNodeAnimation:
|
|
359
377
|
var plays: AnimationNodeAnimation = node
|
|
360
378
|
if plays.animation == StringName(animation_name):
|
|
361
|
-
found.append(
|
|
379
|
+
found.append(
|
|
380
|
+
[plays, { "animTreePath": tree_path, "state": "." if at.is_empty() else at }]
|
|
381
|
+
)
|
|
362
382
|
return
|
|
363
383
|
var children: Dictionary = {}
|
|
364
384
|
if node is AnimationNodeStateMachine:
|
|
@@ -379,4 +399,6 @@ static func _collect_playing(
|
|
|
379
399
|
children[str(point)] = plane.get_blend_point_node(point)
|
|
380
400
|
for step: String in children:
|
|
381
401
|
var child: AnimationNode = children[step]
|
|
382
|
-
_collect_playing(
|
|
402
|
+
_collect_playing(
|
|
403
|
+
child, step if at.is_empty() else at + "/" + step, tree_path, animation_name, found
|
|
404
|
+
)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
@tool
|
|
2
2
|
extends Node
|
|
3
|
-
|
|
4
3
|
## The editor's end of the bridge: one WebSocket to the gdharness server, reconnected on its
|
|
5
4
|
## own when it drops, carrying tool requests in and their results out.
|
|
6
5
|
|
|
@@ -9,7 +8,6 @@ signal disconnected
|
|
|
9
8
|
signal tool_requested(request_id: String, tool_name: String, args: Dictionary)
|
|
10
9
|
|
|
11
10
|
const GdhRead = preload("reading.gd")
|
|
12
|
-
|
|
13
11
|
const DEFAULT_URL: String = "ws://127.0.0.1:6505/godot"
|
|
14
12
|
## Written beside the addon by the install, so it names the version this copy came from.
|
|
15
13
|
const VERSION_MARKER: String = "res://addons/gdharness_editor/.gdharness-version"
|
|
@@ -24,7 +22,6 @@ const EDITORS_RECORDED: String = "res://.godot/gdharness-editors"
|
|
|
24
22
|
const EDITORS_RECORDED_PROTOCOL: int = 1
|
|
25
23
|
const RECONNECT_DELAY: float = 3.0
|
|
26
24
|
const MAX_RECONNECT_DELAY: float = 30.0
|
|
27
|
-
|
|
28
25
|
## What a server closes with when the editor that said hello belongs to another project. Kept in
|
|
29
26
|
## step with `OTHER_PROJECT_CLOSE_CODE` in src/godot-bridge.ts.
|
|
30
27
|
const ELSEWHERE_CLOSE_CODE: int = 4001
|
|
@@ -32,7 +29,6 @@ const ELSEWHERE_CLOSE_CODE: int = 4001
|
|
|
32
29
|
## rather than an editor: dialling in again changes nothing for the rest of the run. Kept in step
|
|
33
30
|
## with `ONE_SHOT_CLOSE_CODE` in src/godot-bridge.ts.
|
|
34
31
|
const ONE_SHOT_CLOSE_CODE: int = 4002
|
|
35
|
-
|
|
36
32
|
## Where Godot keeps the three ports an editor serves. None of them is per editor: the settings
|
|
37
33
|
## file is one for every editor of this engine version on the machine, so two open at once want the
|
|
38
34
|
## same three and the second one binds nothing. Read here and never written, for the reason
|
|
@@ -58,7 +54,6 @@ const OPENED_BY_A_SERVER: String = "GDHARNESS_OPENED_BY_A_SERVER"
|
|
|
58
54
|
## tells the two apart when both are announced at once. Kept in step with `EDITOR_PID_VARIABLE`
|
|
59
55
|
## in the runtime autoload.
|
|
60
56
|
const EDITOR_PID_VARIABLE: String = "GDHARNESS_EDITOR_PID"
|
|
61
|
-
|
|
62
57
|
## How long one attempt is given before the address is called a bad one.
|
|
63
58
|
##
|
|
64
59
|
## A socket pointed at a port nothing holds gives up by itself, in thirty seconds on Windows. One
|
|
@@ -67,7 +62,6 @@ const EDITOR_PID_VARIABLE: String = "GDHARNESS_EDITOR_PID"
|
|
|
67
62
|
## timeout of its own in 4.7. A leftover announcement can be either, and a port that has been
|
|
68
63
|
## reused by some other program is the second.
|
|
69
64
|
const CONNECT_TIMEOUT: float = 10.0
|
|
70
|
-
|
|
71
65
|
## How often the announcement is read again while connected, so a newer server is moved to
|
|
72
66
|
## rather than waited for. A harness reconnect leaves the server it replaced running and holding
|
|
73
67
|
## the old port, and an editor with no reason to look elsewhere stayed on it for the session.
|
|
@@ -75,13 +69,11 @@ const FOLLOW_INTERVAL: float = 5.0
|
|
|
75
69
|
|
|
76
70
|
var socket: WebSocketPeer = WebSocketPeer.new()
|
|
77
71
|
var server_url: String = DEFAULT_URL
|
|
78
|
-
|
|
79
72
|
## What this copy was when it loaded, which is what the editor is running until it is restarted.
|
|
80
73
|
## See [method _loaded_version] for why it is held rather than read when it is wanted.
|
|
81
74
|
var version_at_load: String = ""
|
|
82
75
|
## The digest of the editor addon code this copy loaded, held for the same reason.
|
|
83
76
|
var digest_at_load: String = ""
|
|
84
|
-
|
|
85
77
|
var _is_connected: bool = false
|
|
86
78
|
var _reconnect_timer: Timer
|
|
87
79
|
var _current_reconnect_delay: float = RECONNECT_DELAY
|
|
@@ -89,27 +81,23 @@ var _current_reconnect_delay: float = RECONNECT_DELAY
|
|
|
89
81
|
var _should_reconnect: bool = false
|
|
90
82
|
var _project_path: String
|
|
91
83
|
var _initialized: bool = false
|
|
92
|
-
|
|
93
84
|
## Whether the address came from a caller rather than from the project, in which case it is not
|
|
94
85
|
## this node's to change.
|
|
95
86
|
var _named_by_caller: bool = false
|
|
96
87
|
var _since_looked: float = 0.0
|
|
97
|
-
|
|
98
88
|
## The announced address that did not answer, so the fallback gets a turn. Cleared the moment
|
|
99
89
|
## anything connects or the project names a different one.
|
|
100
90
|
var _refused_url: String = ""
|
|
101
91
|
var _tried_announced: bool = false
|
|
102
|
-
|
|
103
92
|
## The address this editor has already been told is another project's, so a reconnect loop says it
|
|
104
93
|
## once rather than every few seconds for the rest of the session.
|
|
105
94
|
var _told_about_url: String = ""
|
|
106
95
|
var _connecting_for: float = 0.0
|
|
107
|
-
|
|
108
96
|
## Whether this editor was started by another editor, which is Godot's own restart and the project
|
|
109
97
|
## manager opening a project: the new editor is the old one's child and has its environment, with
|
|
110
98
|
## the variable this addon sets as it loads still naming the old one. Its arguments are gone, since
|
|
111
|
-
## the engine consumes `--lsp-port`, `--dap-port` and `--log-file` and hands none of them back, so
|
|
112
|
-
## ports and the marker a server put in that environment describe the editor before this one.
|
|
99
|
+
## the engine consumes `--lsp-port`, `--dap-port` and `--log-file` and hands none of them back, so
|
|
100
|
+
## the ports and the marker a server put in that environment describe the editor before this one.
|
|
113
101
|
var _started_by_an_editor: bool = false
|
|
114
102
|
|
|
115
103
|
|
|
@@ -140,45 +128,6 @@ func _exit_tree() -> void:
|
|
|
140
128
|
var _removed: Error = DirAccess.remove_absolute(record)
|
|
141
129
|
|
|
142
130
|
|
|
143
|
-
## The file saying which ports this editor serves, named by its process id, so `gdharness lsp` can
|
|
144
|
-
## find its language server when a server moved it off the default. One for each process rather
|
|
145
|
-
## than one for the project, because a gate's `--import` loads this addon too, and its plugin is
|
|
146
|
-
## shown none of the options that would say so; the reader tells the two apart by command line.
|
|
147
|
-
## Kept in step with `editorRecordsIn` in src/lsp-bridge.ts.
|
|
148
|
-
static func _ports_record() -> String:
|
|
149
|
-
return ProjectSettings.globalize_path(EDITORS_RECORDED).path_join("%d.json" % OS.get_process_id())
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
func _record_ports() -> void:
|
|
153
|
-
if not Engine.is_editor_hint():
|
|
154
|
-
return
|
|
155
|
-
var made: Error = DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(EDITORS_RECORDED))
|
|
156
|
-
if made != OK and made != ERR_ALREADY_EXISTS:
|
|
157
|
-
return
|
|
158
|
-
var file: FileAccess = FileAccess.open(_ports_record(), FileAccess.WRITE)
|
|
159
|
-
if file == null:
|
|
160
|
-
return
|
|
161
|
-
var written: bool = (
|
|
162
|
-
file
|
|
163
|
-
. store_string(
|
|
164
|
-
(
|
|
165
|
-
JSON
|
|
166
|
-
. stringify(
|
|
167
|
-
{
|
|
168
|
-
"protocol": EDITORS_RECORDED_PROTOCOL,
|
|
169
|
-
"pid": OS.get_process_id(),
|
|
170
|
-
"lsp_port": _serves(LSP_ASKED, LSP_SETTING),
|
|
171
|
-
"dap_port": _serves(DAP_ASKED, DAP_SETTING),
|
|
172
|
-
"written_at_ms": int(Time.get_unix_time_from_system() * 1000.0),
|
|
173
|
-
}
|
|
174
|
-
)
|
|
175
|
-
)
|
|
176
|
-
)
|
|
177
|
-
)
|
|
178
|
-
if not written:
|
|
179
|
-
push_warning("gdharness: could not write " + _ports_record())
|
|
180
|
-
|
|
181
|
-
|
|
182
131
|
func _process(_delta: float) -> void:
|
|
183
132
|
if not _initialized:
|
|
184
133
|
return
|
|
@@ -220,6 +169,49 @@ func _process(_delta: float) -> void:
|
|
|
220
169
|
_handle_disconnect()
|
|
221
170
|
|
|
222
171
|
|
|
172
|
+
## The file saying which ports this editor serves, named by its process id, so `gdharness lsp` can
|
|
173
|
+
## find its language server when a server moved it off the default. One for each process rather
|
|
174
|
+
## than one for the project, because a gate's `--import` loads this addon too, and its plugin is
|
|
175
|
+
## shown none of the options that would say so; the reader tells the two apart by command line.
|
|
176
|
+
## Kept in step with `editorRecordsIn` in src/lsp-bridge.ts.
|
|
177
|
+
static func _ports_record() -> String:
|
|
178
|
+
return ProjectSettings.globalize_path(EDITORS_RECORDED).path_join(
|
|
179
|
+
"%d.json" % OS.get_process_id()
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
func _record_ports() -> void:
|
|
184
|
+
if not Engine.is_editor_hint():
|
|
185
|
+
return
|
|
186
|
+
var made: Error = DirAccess.make_dir_recursive_absolute(
|
|
187
|
+
ProjectSettings.globalize_path(EDITORS_RECORDED)
|
|
188
|
+
)
|
|
189
|
+
if made != OK and made != ERR_ALREADY_EXISTS:
|
|
190
|
+
return
|
|
191
|
+
var file: FileAccess = FileAccess.open(_ports_record(), FileAccess.WRITE)
|
|
192
|
+
if file == null:
|
|
193
|
+
return
|
|
194
|
+
var written: bool = (
|
|
195
|
+
file
|
|
196
|
+
. store_string(
|
|
197
|
+
(
|
|
198
|
+
JSON
|
|
199
|
+
. stringify(
|
|
200
|
+
{
|
|
201
|
+
"protocol": EDITORS_RECORDED_PROTOCOL,
|
|
202
|
+
"pid": OS.get_process_id(),
|
|
203
|
+
"lsp_port": _serves(LSP_ASKED, LSP_SETTING),
|
|
204
|
+
"dap_port": _serves(DAP_ASKED, DAP_SETTING),
|
|
205
|
+
"written_at_ms": int(Time.get_unix_time_from_system() * 1000.0),
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
)
|
|
211
|
+
if not written:
|
|
212
|
+
push_warning("gdharness: could not write " + _ports_record())
|
|
213
|
+
|
|
214
|
+
|
|
223
215
|
## Moves to the server the project now names, when that is not the one this is talking to.
|
|
224
216
|
##
|
|
225
217
|
## A harness reconnect leaves the server it replaced running, still holding the port it bound and
|
|
@@ -371,7 +363,7 @@ func _handle_connect() -> void:
|
|
|
371
363
|
# as itself: the engine hands back none of the arguments it was given, so the arguments
|
|
372
364
|
# have to come from whoever wrote them. See `restart_editor` in play_tools.gd.
|
|
373
365
|
"opened_by_a_server": _opened_by_a_server() and not _started_by_an_editor,
|
|
374
|
-
"started_by_an_editor": _started_by_an_editor
|
|
366
|
+
"started_by_an_editor": _started_by_an_editor,
|
|
375
367
|
}
|
|
376
368
|
)
|
|
377
369
|
|
|
@@ -556,7 +548,7 @@ func _handle_message(json_string: String) -> void:
|
|
|
556
548
|
|
|
557
549
|
match message.get("type", ""):
|
|
558
550
|
"ping":
|
|
559
|
-
_send_message({"type": "pong"})
|
|
551
|
+
_send_message({ "type": "pong" })
|
|
560
552
|
|
|
561
553
|
"tool_invoke":
|
|
562
554
|
var request_id: String = message.get("id", "")
|
|
@@ -577,13 +569,16 @@ func send_busy(request_id: String, tool_name: String) -> void:
|
|
|
577
569
|
"id": request_id,
|
|
578
570
|
"success": false,
|
|
579
571
|
"busy": true,
|
|
580
|
-
"error":
|
|
572
|
+
"error":
|
|
573
|
+
"The editor is scanning or importing its files, so %s was not run." % tool_name,
|
|
581
574
|
}
|
|
582
575
|
)
|
|
583
576
|
|
|
584
577
|
|
|
585
|
-
func send_tool_result(
|
|
586
|
-
|
|
578
|
+
func send_tool_result(
|
|
579
|
+
request_id: String, success: bool, result: Variant = null, error: String = ""
|
|
580
|
+
) -> void:
|
|
581
|
+
var response: Dictionary = { "type": "tool_result", "id": request_id, "success": success }
|
|
587
582
|
|
|
588
583
|
if success:
|
|
589
584
|
response["result"] = result
|