gdharness 1.3.16 → 1.3.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/cli.js CHANGED
@@ -16373,7 +16373,7 @@ var init_tool_definitions = __esm(() => {
16373
16373
  title: "Run tests",
16374
16374
  effect: "additive",
16375
16375
  description: "Runs the project's gdUnit4 tests 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, its reports 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. Identical reports fold into one with times, at most twenty different ones are kept (reportsLeftOut counts the rest), and past about a megabyte the largest cases keep only their first message (failedWithoutDetail). 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, not listed, and an engine message keeps the frames above gdUnit4, not the twenty inside it; identical engine messages come back once under engineEntries, with times saying how often they were printed. The class list is rebuilt first, so a suite written a moment ago is found, and the rebuild's answer is under classes. What changed on disk since it was imported is then imported before any suite runs, and named under importedFirst. On Windows and Linux the run gets a user:// of its own, away from a player's saves; on macOS the engine reads user:// off HOME, so it writes where the player keeps theirs, and savesNote says so. The run is headless unless a suite in it needs a window as gdharness test reads it, and then has one, on a desktop of its own on Windows. A run that found nothing to run is never called a pass, 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. An autoload that does not compile stops the run at once, its errors listed the same way. A client sending a progress token is told as each suite finishes, and at least every twenty seconds. 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 orphan nodes were left 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 is not listed there, and none are when the project turns off gdUnit4's report/godot/script_error setting; a suite declares them all the same. Needs gdUnit4 under addons/gdUnit4.",
16376
- answer: "passed is the verdict to read. exitCode is the engine's own exit, gdUnit4's code for what it saw, and is 0 beside passed false when the run is failed for something gdUnit4 does not judge, such as an engine error no suite declared. time is gdUnit4's own sum of the seconds its cases took, from its report. tookMs is the whole call in milliseconds, from the class rebuild to the engine's exit, which is what a caller waits for. The run is judged as gdharness test judges it: undeclaredErrors names each engine error that no suite declared in its GDHARNESS_EXPECTED_ERRORS constant, and each engine warning a suite printed and did not declare in its GDHARNESS_EXPECTED_WARNINGS, marked warning true, with the suite and the case that printed it, and declaredNotPrinted each declared error or warning its case did not print. A declared text must be the whole line after ERROR: or WARNING: and its space, so a longer one its case printed holding the text is named under printedInstead. Either fails the run, and each suite holding one is listed with its count. leakedAtExit holds the engine's lines saying it exited still holding objects, resources or memory pages, which fail the run too, and the note names any script of the project Godot itself never frees. engineErrors counts every engine error the run printed but those a suite declared. orphans totals the orphan nodes gdUnit4 counted, and each suite that left some has its count, and a warning saying how to free them, under warnings. suites lists only the suites with something to say (a case that failed, erred or was skipped, a hook that failed, orphans, or an error undeclared or declared and not printed), and suitesPassed counts the rest. Under classes, the classes each addon declares are counted under addedInAddons and changedInAddons rather than named, and a class the rebuild 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. A suite needs a window when it declares const GDHARNESS_NEEDS_WINDOW: bool = true, or calls scene_runner and declares nothing; a run holding one has a window, at the size project.godot gives its viewport and with no sound, and windowed is true and windowNote names those suites and any saying it runs headless that shared the window. On a machine with no display such a run is headless, windowed is false, and windowNote says so. " + IMPORTED_FIRST,
16376
+ answer: "passed is the verdict to read. exitCode is the engine's own exit, gdUnit4's code for what it saw, and is 0 beside passed false when the run is failed for something gdUnit4 does not judge, such as an engine error no suite declared. time is gdUnit4's own sum of the seconds its cases took, from its report. tookMs is the whole call in milliseconds, from the class rebuild to the engine's exit, which is what a caller waits for, and tookMsBy splits it into the class rebuild (classes), the import after it (import) and the engine running the suites (engine), each with any wait for an engine slot or the editor's scan in it. The run is judged as gdharness test judges it: undeclaredErrors names each engine error that no suite declared in its GDHARNESS_EXPECTED_ERRORS constant, and each engine warning a suite printed and did not declare in its GDHARNESS_EXPECTED_WARNINGS, marked warning true, with the suite and the case that printed it, and declaredNotPrinted each declared error or warning its case did not print. A declared text must be the whole line after ERROR: or WARNING: and its space, so a longer one its case printed holding the text is named under printedInstead. Either fails the run, and each suite holding one is listed with its count. leakedAtExit holds the engine's lines saying it exited still holding objects, resources or memory pages, which fail the run too, and the note names any script of the project Godot itself never frees. engineErrors counts every engine error the run printed but those a suite declared. orphans totals the orphan nodes gdUnit4 counted, and each suite that left some has its count, and a warning saying how to free them, under warnings. suites lists only the suites with something to say (a case that failed, erred or was skipped, a hook that failed, orphans, or an error undeclared or declared and not printed), and suitesPassed counts the rest. Under classes, the classes each addon declares are counted under addedInAddons and changedInAddons rather than named, and a class the rebuild 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. A suite needs a window when it declares const GDHARNESS_NEEDS_WINDOW: bool = true, or calls scene_runner and declares nothing; a run holding one has a window, at the size project.godot gives its viewport and with no sound, and windowed is true and windowNote names those suites and any saying it runs headless that shared the window. On a machine with no display such a run is headless, windowed is false, and windowNote says so. " + IMPORTED_FIRST,
16377
16377
  parameters: {
16378
16378
  projectPath: PROJECT_PATH,
16379
16379
  path: {
@@ -17517,7 +17517,7 @@ var init_tool_definitions = __esm(() => {
17517
17517
  type: ["boolean", "array"],
17518
17518
  items: { type: "string" },
17519
17519
  ops: ["call"],
17520
- description: `call: read the object the call answered with in the same answer, under result_properties: true for every variable its script declares, or a list of names, each a colon path walked from that object the way property paths are ("dead_allowed", "terms:0:name"). For a list or a dictionary the call answered with, they are read off each object in it: result_properties is then a list, or a dictionary under the same keys, with null for an element that is no object. A name that is not there is listed under result_properties_missing with why, led by the element's index or key for a list or dictionary, and a result with no object in it answers properties_note instead. Without it an object comes back as its class, its script_class or script, and id, which is the object's instance id (get_instance_id()) and never a property of its own named id: read that one through properties.`
17520
+ description: `call: read the object the call answered with in the same answer, under result_properties: true for every variable its script declares, or a list of names, each a colon path walked from that object the way property paths are ("dead_allowed", "terms:0:name"). For a list or a dictionary the call answered with, they are read off each object in it: result_properties is then a list, or a dictionary in the shape result gives it (under the same keys when every key is text, otherwise as entries, each a key and what was read), with null for an element that is no object. A name that is not there is listed under result_properties_missing with why, led by the element's index or key for a list or dictionary, a key that is not text shown as result shows it, and a result with no object in it answers properties_note instead. Without it an object comes back as its class, its script_class or script, and id, which is the object's instance id (get_instance_id()) and never a property of its own named id: read that one through properties.`
17521
17521
  },
17522
17522
  timeoutMs: {
17523
17523
  type: "integer",
@@ -52179,7 +52179,9 @@ ${platform}`;
52179
52179
  return this.answer(classes);
52180
52180
  }
52181
52181
  const rebuildWaited = scanWaitIn(classes.payload);
52182
+ const rebuiltAt = Date.now();
52182
52183
  const importWaited = scanWaitAnswer(await this.afterTheImport(project.value.path, { waitedMs: 0, stillScanning: false }, await this.importWhatChanged(project.value.path)));
52184
+ const importedAt = Date.now();
52183
52185
  const waitedMs = (rebuildWaited.waitedForEditorScanMs ?? 0) + (importWaited.waitedForEditorScanMs ?? 0);
52184
52186
  const scanNote = importWaited.scanNote ?? rebuildWaited.scanNote;
52185
52187
  const scanned = {
@@ -52293,7 +52295,13 @@ ${platform}`;
52293
52295
  discard(join43(project.value.path, ".godot", ...ours.split("/")), userData);
52294
52296
  return this.createErrorResponse(`No tests ran: ${named(needing)} ${needing.length === 1 ? "needs" : "need"} a window, and the engine could not be started on a desktop of its own: ${helperFailed}`);
52295
52297
  }
52296
- const tookMs = Date.now() - began;
52298
+ const endedAt = Date.now();
52299
+ const tookMs = endedAt - began;
52300
+ const tookMsBy = {
52301
+ classes: rebuiltAt - began,
52302
+ import: importedAt - rebuiltAt,
52303
+ engine: endedAt - importedAt
52304
+ };
52297
52305
  const notCompiled = stopped.for ?? run.log.all.map((entry) => autoloadNotCompiled(entry, autoloaded)).find((found) => found !== null) ?? null;
52298
52306
  const reportsDir = join43(project.value.path, ".godot", ...ours.split("/"));
52299
52307
  let report = null;
@@ -52386,6 +52394,7 @@ ${platform}`;
52386
52394
  verdict,
52387
52395
  tests: 0,
52388
52396
  tookMs,
52397
+ tookMsBy,
52389
52398
  exitCode,
52390
52399
  exitSignal: run.exitSignal ?? undefined,
52391
52400
  hung,
@@ -52516,6 +52525,7 @@ ${error.text}`;
52516
52525
  skipped: report.skipped,
52517
52526
  time: report.time,
52518
52527
  tookMs,
52528
+ tookMsBy,
52519
52529
  failed,
52520
52530
  failedWithoutDetail: briefed > 0 ? briefed : undefined,
52521
52531
  hookFailures: hookFailures.length > 0 ? hookFailures : undefined,
@@ -711,10 +711,23 @@ func _read_off_result(result: Variant, asked: Variant) -> Dictionary:
711
711
  return _read_off_many(read_each, list, "Array", missing)
712
712
  if result is Dictionary:
713
713
  var map: Dictionary = result
714
+ # In the shape result gives the dictionary: keyed by text only when every key is text, since
715
+ # a key 7 and a key "7" are one key once both are text, and one of them was lost.
716
+ var texts: bool = map.keys().all(
717
+ func(key: Variant) -> bool: return key is String or key is StringName
718
+ )
714
719
  var read_by_key: Dictionary = {}
720
+ var entries: Array = []
715
721
  for key: Variant in map:
716
- read_by_key[str(key)] = _read_off_element(map[key], "[%s]" % str(key), asked, missing)
717
- return _read_off_many(read_by_key, map.values(), "Dictionary", missing)
722
+ var shown: Variant = _values.serialize(key)
723
+ var named: String = "[%s]" % (str(key) if texts else JSON.stringify(shown, "", false))
724
+ var got: Variant = _read_off_element(map[key], named, asked, missing)
725
+ if texts:
726
+ read_by_key[str(key)] = got
727
+ else:
728
+ entries.append([shown, got])
729
+ var shaped: Variant = read_by_key if texts else {"_type": "Dictionary", "entries": entries}
730
+ return _read_off_many(shaped, map.values(), "Dictionary", missing)
718
731
  if result is Object and not is_instance_valid(result):
719
732
  return {
720
733
  "properties_note": "the call answered an object that has since been freed, so nothing was read"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "1.3.16",
3
+ "version": "1.3.18",
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",