gdharness 1.3.42 → 1.3.44
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 +383 -243
- 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 +51 -17
- package/build/godot/addons/gdharness_editor/same_values.gd +4 -4
- package/build/godot/addons/gdharness_editor/scene_file.gd +127 -80
- 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 +65 -42
- 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 +51 -17
- 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 +190 -109
- package/build/godot/operations/editor_tools.gd +10 -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 +61 -32
- 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 +51 -17
- 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/build/keeper.js +43 -43
- package/package.json +1 -1
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
extends SceneTree
|
|
2
|
-
|
|
3
2
|
## gdUnit4's command-line runner over the suites a file lists, in the file's order. One `-a` a suite
|
|
4
3
|
## outgrows the command line once a game has a few hundred suites (Windows stops at 32767
|
|
5
4
|
## characters), so the suites come from the file named after `--` with [constant LISTED], and the
|
|
@@ -26,7 +25,8 @@ const ROWS: String = "--rows-in"
|
|
|
26
25
|
## row an engine found to tell one that no engine ran.
|
|
27
26
|
const LEFT_OUT: String = "gdharness left out"
|
|
28
27
|
## Printed before each row an entry of [constant SKIPPING] leaves out, as
|
|
29
|
-
## `res://suite.gd > test_case:row`, so the caller can tell an entry naming a row that does not
|
|
28
|
+
## `res://suite.gd > test_case:row`, so the caller can tell an entry naming a row that does not
|
|
29
|
+
## exist.
|
|
30
30
|
const IGNORED_ROW: String = "gdharness ignored"
|
|
31
31
|
## Printed after a case or a suite's hooks that gdUnit4 says left orphan nodes, as
|
|
32
32
|
## `res://suite.gd > test_case left 2: Node "Sandbox" of res://sandbox.gd, Timer "Timer"`, with
|
|
@@ -76,6 +76,249 @@ const WALK_HOLD: StringName = &"gdharness_scripts_held_past_the_shutdown_walk"
|
|
|
76
76
|
var _runner: GdhRunner = null
|
|
77
77
|
|
|
78
78
|
|
|
79
|
+
func _initialize() -> void:
|
|
80
|
+
_runner = started(OS.get_cmdline_user_args())
|
|
81
|
+
_runner.own_path = get_script().resource_path
|
|
82
|
+
root.add_child(_runner)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
## As gdUnit4's own `GdUnitCmdTool.gd` ends, which names in a verbose run the nodes left orphaned.
|
|
86
|
+
func _finalize() -> void:
|
|
87
|
+
queue_delete(_runner)
|
|
88
|
+
if OS.is_stdout_verbose():
|
|
89
|
+
print("Orphan nodes left as the run ends:")
|
|
90
|
+
Window.print_orphan_nodes()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
## The runner over the suites [param given] lists, to be added to the tree, which starts it. Shared
|
|
94
|
+
## by this script and the suites scene of the tests addon, which loads this file from where the
|
|
95
|
+
## server ships it and calls this, since the addon itself names no gdUnit4 class.
|
|
96
|
+
static func started(given: PackedStringArray) -> GdhRunner:
|
|
97
|
+
# First, so the marker is older than every script the run loads.
|
|
98
|
+
var oldest: Array[GDScript] = []
|
|
99
|
+
if GdhShutdownWalk.guarded():
|
|
100
|
+
oldest = GdhShutdownWalk.marker(WALK_ENDED)
|
|
101
|
+
OS.low_processor_usage_mode_sleep_usec = FRAME_SLEEP_USEC
|
|
102
|
+
var at: int = given.find(GDUNIT)
|
|
103
|
+
var asked: PackedStringArray = given if at < 0 else given.slice(0, at)
|
|
104
|
+
var unread: PackedStringArray = []
|
|
105
|
+
for flag: String in REPORTS_OFF:
|
|
106
|
+
if asked.has(flag):
|
|
107
|
+
var _unread: bool = unread.append(REPORTS_OFF[flag])
|
|
108
|
+
var hooks: GdUnitTestSessionHookService = GdUnitTestSessionHookService.instance()
|
|
109
|
+
var off: PackedStringArray = unread_off(hooks, unread)
|
|
110
|
+
for path: String in unread:
|
|
111
|
+
if not off.has(path):
|
|
112
|
+
push_error(
|
|
113
|
+
(
|
|
114
|
+
(
|
|
115
|
+
"gdharness test: gdUnit4 %s registers no session hook %s, so the report it "
|
|
116
|
+
+ "writes is not turned off; report it to gdharness with the version."
|
|
117
|
+
)
|
|
118
|
+
% [GdUnit4Version.current(), path]
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
var suites: PackedStringArray = listed(asked, LISTED)
|
|
122
|
+
var runner: GdhRunner = GdhRunner.new()
|
|
123
|
+
runner.walk_scripts = oldest
|
|
124
|
+
if at >= 0:
|
|
125
|
+
runner.given = given.slice(at + 1)
|
|
126
|
+
if asked.has(TOGETHER):
|
|
127
|
+
runner.together = compiled_together(suites)
|
|
128
|
+
var expected_out: String = argument_after(asked, EXPECTED_OUT)
|
|
129
|
+
if not expected_out.is_empty():
|
|
130
|
+
write_expected(expected_out, suites)
|
|
131
|
+
runner.add_test_suites(suites)
|
|
132
|
+
for entry: String in listed(asked, SKIPPING):
|
|
133
|
+
if is_one_row(entry):
|
|
134
|
+
runner.left_out_rows[entry] = true
|
|
135
|
+
elif is_one_case(entry):
|
|
136
|
+
runner.left_out[entry] = true
|
|
137
|
+
else:
|
|
138
|
+
runner.skip_test_suite(entry)
|
|
139
|
+
runner.row_shares = row_shares_of(listed(asked, ROWS))
|
|
140
|
+
# Added to the list itself rather than through `register`, which saves every hook it holds into
|
|
141
|
+
# `project.godot`.
|
|
142
|
+
hooks.enigne_hooks.append(GdhSessionCheck.new(runner))
|
|
143
|
+
return runner
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
## Turns off the hooks among [param hooks] whose scripts [param unread] names, answering the scripts
|
|
147
|
+
## of those it turned off. Through the mark gdUnit4's own `is_enabled` reads, rather than its
|
|
148
|
+
## `enable_hook`, which writes the setting into `project.godot`: a run changes nothing in the tree.
|
|
149
|
+
static func unread_off(
|
|
150
|
+
hooks: GdUnitTestSessionHookService, unread: PackedStringArray
|
|
151
|
+
) -> PackedStringArray:
|
|
152
|
+
var off: PackedStringArray = []
|
|
153
|
+
for hook: GdUnitTestSessionHook in hooks.enigne_hooks:
|
|
154
|
+
var path: String = hook.get_script().resource_path
|
|
155
|
+
if unread.has(path):
|
|
156
|
+
hook.set_meta("enabled", false)
|
|
157
|
+
var _added: bool = off.append(path)
|
|
158
|
+
return off
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
## [param suites] compiled inside one script, answered for the caller to hold so gdUnit4's own loads
|
|
162
|
+
## find them compiled. The engine keeps what it read of a class only while the compile that asked
|
|
163
|
+
## for it runs (`GDScriptCache`), so a suite compiled on its own reads again every class it reaches
|
|
164
|
+
## a member of, some seventy milliseconds a suite. As the preloads of one script naming every class
|
|
165
|
+
## of the game's own first, held from that script's compile, they share one reading of each: two
|
|
166
|
+
## hundred suites compiled in a third of the time they took one at a time. The names are public,
|
|
167
|
+
## since a private one unread is a warning and every warning an error, and each a variable, since a
|
|
168
|
+
## class is no constant expression. A suite that does not compile fails here and again when gdUnit4
|
|
169
|
+
## loads it, which reports it as it would have. A directory listed is left to gdUnit4 to walk.
|
|
170
|
+
static func compiled_together(suites: PackedStringArray) -> GDScript:
|
|
171
|
+
var lines: PackedStringArray = ["extends RefCounted"]
|
|
172
|
+
var at: int = 0
|
|
173
|
+
for named: String in held_classes():
|
|
174
|
+
var _held: bool = lines.append("var held_%d: Variant = %s" % [at, named])
|
|
175
|
+
at += 1
|
|
176
|
+
at = 0
|
|
177
|
+
for path: String in suites:
|
|
178
|
+
if not path.ends_with(".gd"):
|
|
179
|
+
continue
|
|
180
|
+
var _suite: bool = lines.append('var suite_%d: Variant = preload("%s")' % [at, path])
|
|
181
|
+
at += 1
|
|
182
|
+
var together: GDScript = GDScript.new()
|
|
183
|
+
together.source_code = "\n".join(lines) + "\n"
|
|
184
|
+
var _compiled: Error = together.reload()
|
|
185
|
+
return together
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
## The classes [method compiled_together] names before the suites: the game's own GDScript classes,
|
|
189
|
+
## none of an addon's or a suite's, then gdUnit4's suite. Only GDScript shares the engine's reading,
|
|
190
|
+
## and a sandboxed mod's SafeGDScript class would load the sandbox's compiler, some 290 MB and up to
|
|
191
|
+
## 2.5 s, for nothing.
|
|
192
|
+
static func held_classes(
|
|
193
|
+
classes: Array[Dictionary] = ProjectSettings.get_global_class_list()
|
|
194
|
+
) -> PackedStringArray:
|
|
195
|
+
var held: PackedStringArray = []
|
|
196
|
+
for known: Dictionary in classes:
|
|
197
|
+
var path: String = known["path"]
|
|
198
|
+
var own: bool = not path.begins_with("res://addons/") and not path.ends_with("_test.gd")
|
|
199
|
+
if own and known["language"] == &"GDScript":
|
|
200
|
+
var _own: bool = held.append(str(known["class"]))
|
|
201
|
+
var _suite: bool = held.append("GdUnitTestSuite")
|
|
202
|
+
return held
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
## What the file after [param flag] in [param args] names, one a line in order, or none when no file
|
|
206
|
+
## is named: gdUnit4 then says it found nothing, which a gate refuses.
|
|
207
|
+
static func listed(args: PackedStringArray, flag: String) -> PackedStringArray:
|
|
208
|
+
var path: String = argument_after(args, flag)
|
|
209
|
+
if path.is_empty():
|
|
210
|
+
return PackedStringArray()
|
|
211
|
+
var named: PackedStringArray = []
|
|
212
|
+
for line: String in FileAccess.get_file_as_string(path).split("\n", false):
|
|
213
|
+
if not line.strip_edges().is_empty():
|
|
214
|
+
var _named: bool = named.append(line.strip_edges())
|
|
215
|
+
return named
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
## Whether [param entry] of a [constant SKIPPING] file names one case of one suite file, as
|
|
219
|
+
## `res://suite.gd:test_case`, rather than a suite or a case by gdUnit4's looser names, which go to
|
|
220
|
+
## gdUnit4 as they are.
|
|
221
|
+
## Whether [param entry] names one row of a parameterised case, as `res://suite.gd:test_case:row`.
|
|
222
|
+
static func is_one_row(entry: String) -> bool:
|
|
223
|
+
var at: int = entry.rfind(":")
|
|
224
|
+
var row: String = entry.substr(at + 1)
|
|
225
|
+
return (
|
|
226
|
+
at > 0
|
|
227
|
+
and row.is_valid_int()
|
|
228
|
+
and not row.begins_with("-")
|
|
229
|
+
and is_one_case(entry.substr(0, at))
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
static func is_one_case(entry: String) -> bool:
|
|
234
|
+
var at: int = entry.rfind(":")
|
|
235
|
+
return (
|
|
236
|
+
entry.begins_with("res://")
|
|
237
|
+
and at > "res://".length()
|
|
238
|
+
and entry.substr(0, at).ends_with(".gd")
|
|
239
|
+
and entry.substr(at + 1).is_valid_ascii_identifier()
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
## The row shares [param lines] of a [constant ROWS] file give, by case.
|
|
244
|
+
static func row_shares_of(lines: PackedStringArray) -> Dictionary[String, GdhRowShare]:
|
|
245
|
+
var shares: Dictionary[String, GdhRowShare] = {}
|
|
246
|
+
for line: String in lines:
|
|
247
|
+
var parts: PackedStringArray = line.split("\t")
|
|
248
|
+
if parts.size() < 2:
|
|
249
|
+
continue
|
|
250
|
+
var share: GdhRowShare = GdhRowShare.new()
|
|
251
|
+
share.only = parts[1] == "only"
|
|
252
|
+
for row: String in parts.slice(2):
|
|
253
|
+
if row.is_valid_int():
|
|
254
|
+
var _added: bool = share.rows.append(row.to_int())
|
|
255
|
+
shares[parts[0]] = share
|
|
256
|
+
return shares
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
## The argument after [param flag] in [param args], or "" when there is none.
|
|
260
|
+
static func argument_after(args: PackedStringArray, flag: String) -> String:
|
|
261
|
+
var at: int = args.find(flag)
|
|
262
|
+
if at < 0 or at + 1 >= args.size():
|
|
263
|
+
return ""
|
|
264
|
+
return args[at + 1]
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
## What each of [param suites] declares in [param constant], by suite path and then case name, read
|
|
268
|
+
## off the compiled suite's constants so a declaration is whatever the engine made of it. A
|
|
269
|
+
## directory listed is walked for its scripts, as gdUnit4 walks it for suites. Only a script whose
|
|
270
|
+
## source names the constant is loaded: gdUnit4 stops on a suite that does not load only when its
|
|
271
|
+
## own load is the one that fails, so a broken suite loaded here first let the rest of its directory
|
|
272
|
+
## run.
|
|
273
|
+
static func expected_by_suite(suites: PackedStringArray, constant: String) -> Dictionary:
|
|
274
|
+
var expected: Dictionary = {}
|
|
275
|
+
for path: String in scripts_in(suites):
|
|
276
|
+
if (
|
|
277
|
+
not FileAccess.file_exists(path)
|
|
278
|
+
or not FileAccess.get_file_as_string(path).contains(constant)
|
|
279
|
+
):
|
|
280
|
+
continue
|
|
281
|
+
var suite: Script = load(path)
|
|
282
|
+
var declared: Variant = suite.get_script_constant_map().get(constant)
|
|
283
|
+
if declared is Dictionary:
|
|
284
|
+
expected[path] = declared
|
|
285
|
+
return expected
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
## The scripts [param paths] names: each script, and every script under each directory.
|
|
289
|
+
static func scripts_in(paths: PackedStringArray) -> PackedStringArray:
|
|
290
|
+
var found: PackedStringArray = []
|
|
291
|
+
var pending: PackedStringArray = []
|
|
292
|
+
for path: String in paths:
|
|
293
|
+
if path.ends_with(".gd"):
|
|
294
|
+
var _script: bool = found.append(path)
|
|
295
|
+
else:
|
|
296
|
+
var _directory: bool = pending.append(path.trim_suffix("/"))
|
|
297
|
+
while not pending.is_empty():
|
|
298
|
+
var directory: String = pending[pending.size() - 1]
|
|
299
|
+
pending.remove_at(pending.size() - 1)
|
|
300
|
+
for file: String in DirAccess.get_files_at(directory):
|
|
301
|
+
if file.ends_with(".gd"):
|
|
302
|
+
var _file: bool = found.append("%s/%s" % [directory, file])
|
|
303
|
+
for inner: String in DirAccess.get_directories_at(directory):
|
|
304
|
+
var _inner: bool = pending.append("%s/%s" % [directory, inner])
|
|
305
|
+
return found
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
static func write_expected(path: String, suites: PackedStringArray) -> void:
|
|
309
|
+
var file: FileAccess = FileAccess.open(path, FileAccess.WRITE)
|
|
310
|
+
if file == null:
|
|
311
|
+
push_error(
|
|
312
|
+
"Could not write the expected errors to %s: %s" % [path, FileAccess.get_open_error()]
|
|
313
|
+
)
|
|
314
|
+
return
|
|
315
|
+
var expected: Dictionary = {}
|
|
316
|
+
for kind: String in EXPECTED:
|
|
317
|
+
expected[kind] = expected_by_suite(suites, EXPECTED[kind])
|
|
318
|
+
var _stored: bool = file.store_string(JSON.stringify(expected))
|
|
319
|
+
file.close()
|
|
320
|
+
|
|
321
|
+
|
|
79
322
|
## gdUnit4's runner reading its options after this script's path rather than after its own tool's,
|
|
80
323
|
## which it looks for by name and otherwise finds none of them, printing its help.
|
|
81
324
|
class GdhRunner:
|
|
@@ -90,14 +333,16 @@ class GdhRunner:
|
|
|
90
333
|
var together: GDScript = null
|
|
91
334
|
## The cases dealt by their rows, by `res://suite.gd:test_case`.
|
|
92
335
|
var row_shares: Dictionary[String, GdhRowShare] = {}
|
|
93
|
-
## The cases to leave out, by `res://suite.gd:test_case` of the suite running them. gdUnit4's
|
|
94
|
-
## list is matched against the script declaring a case, which for a case a suite inherits is
|
|
95
|
-
## base, so an inherited case left out under its suite ran in every engine the suite was
|
|
96
|
-
## and a case of `res://tests/a_test.gd` was also left out of
|
|
336
|
+
## The cases to leave out, by `res://suite.gd:test_case` of the suite running them. gdUnit4's
|
|
337
|
+
## own list is matched against the script declaring a case, which for a case a suite inherits is
|
|
338
|
+
## its base, so an inherited case left out under its suite ran in every engine the suite was
|
|
339
|
+
## dealt to, and a case of `res://tests/a_test.gd` was also left out of
|
|
340
|
+
## `res://more/tests/a_test.gd`.
|
|
97
341
|
var left_out: Dictionary[String, bool] = {}
|
|
98
342
|
## The rows of parameterised cases to leave out, by `res://suite.gd:test_case:row`.
|
|
99
343
|
var left_out_rows: Dictionary[String, bool] = {}
|
|
100
|
-
## The cases gdUnit4 said it finished, which [GdhSessionCheck] holds the session's lookups
|
|
344
|
+
## The cases gdUnit4 said it finished, which [GdhSessionCheck] holds the session's lookups
|
|
345
|
+
## against.
|
|
101
346
|
var cases_ended: int = 0
|
|
102
347
|
## The marker of the shutdown walk's oldest end, made as the run starts.
|
|
103
348
|
var walk_scripts: Array[GDScript] = []
|
|
@@ -135,8 +380,8 @@ class GdhRunner:
|
|
|
135
380
|
return options_of(OS.get_cmdline_args(), own_path)
|
|
136
381
|
|
|
137
382
|
## gdUnit4's own discovery, with each case filed under the suite it was found in before it is
|
|
138
|
-
## asked whether to leave the case out. gdUnit4 files a case under the script declaring it,
|
|
139
|
-
## for a case a suite inherits is its base: its reports added such a case to no suite, so
|
|
383
|
+
## asked whether to leave the case out. gdUnit4 files a case under the script declaring it,
|
|
384
|
+
## which for a case a suite inherits is its base: its reports added such a case to no suite, so
|
|
140
385
|
## project_test counted none of them, and a parameterised one ran as a suite of the base's own,
|
|
141
386
|
## outside the suite inheriting it and its hooks, and was judged a case that suite never ran.
|
|
142
387
|
func discover_tests() -> Array[GdUnitTestCase]:
|
|
@@ -169,7 +414,9 @@ class GdhRunner:
|
|
|
169
414
|
func is_skipped(test: GdUnitTestCase) -> bool:
|
|
170
415
|
if left_out.has("%s:%s" % [test.suite_resource_path, test.test_name]) or super(test):
|
|
171
416
|
return true
|
|
172
|
-
var row: String =
|
|
417
|
+
var row: String = (
|
|
418
|
+
"%s:%s:%d" % [test.suite_resource_path, test.test_name, test.attribute_index]
|
|
419
|
+
)
|
|
173
420
|
if test.attribute_index >= 0 and left_out_rows.has(row):
|
|
174
421
|
print(
|
|
175
422
|
(
|
|
@@ -178,19 +425,27 @@ class GdhRunner:
|
|
|
178
425
|
)
|
|
179
426
|
)
|
|
180
427
|
return true
|
|
181
|
-
var share: GdhRowShare = row_shares.get(
|
|
428
|
+
var share: GdhRowShare = row_shares.get(
|
|
429
|
+
"%s:%s" % [test.suite_resource_path, test.test_name], null
|
|
430
|
+
)
|
|
182
431
|
if share == null or not share.leaves_out(test.attribute_index):
|
|
183
432
|
return false
|
|
184
|
-
print(
|
|
433
|
+
print(
|
|
434
|
+
(
|
|
435
|
+
"%s %s > %s:%d"
|
|
436
|
+
% [LEFT_OUT, test.suite_resource_path, test.test_name, test.attribute_index]
|
|
437
|
+
)
|
|
438
|
+
)
|
|
185
439
|
return true
|
|
186
440
|
|
|
187
441
|
## The session gdUnit4 has just made, swapped for a [GdhIndexedSession] of the same cases as it
|
|
188
|
-
## is announced: everything that reads the session, the hooks, the cases and the console
|
|
189
|
-
## every case, is handed it after this.
|
|
442
|
+
## is announced: everything that reads the session, the hooks, the cases and the console
|
|
443
|
+
## printing every case, is handed it after this.
|
|
190
444
|
func _on_gdunit_event(event: GdUnitEvent) -> void:
|
|
191
445
|
match event.type():
|
|
192
446
|
GdUnitEvent.SESSION_START:
|
|
193
|
-
|
|
447
|
+
if not GdhIndexedSession.indexes_itself(_test_session):
|
|
448
|
+
_test_session = GdhIndexedSession.of(_test_session)
|
|
194
449
|
GdUnitEvent.TESTSUITE_BEFORE:
|
|
195
450
|
suite_running = event.resource_path()
|
|
196
451
|
orphans_at_suite = orphan_ids()
|
|
@@ -216,8 +471,12 @@ class GdhRunner:
|
|
|
216
471
|
if not case_began:
|
|
217
472
|
say_left("before or after", left)
|
|
218
473
|
return
|
|
219
|
-
say_left(
|
|
220
|
-
|
|
474
|
+
say_left(
|
|
475
|
+
"before", left.filter(func(id: int) -> bool: return orphans_from_before.has(id))
|
|
476
|
+
)
|
|
477
|
+
say_left(
|
|
478
|
+
"after", left.filter(func(id: int) -> bool: return not orphans_from_before.has(id))
|
|
479
|
+
)
|
|
221
480
|
|
|
222
481
|
## The orphans now that were not there in [param before] and no line has named yet.
|
|
223
482
|
func new_orphans(before: Dictionary[int, bool]) -> Array[int]:
|
|
@@ -237,7 +496,9 @@ class GdhRunner:
|
|
|
237
496
|
orphans_named[id] = true
|
|
238
497
|
if named.size() < ORPHANS_NAMED:
|
|
239
498
|
var _named: bool = named.append(described(instance_from_id(id) as Node))
|
|
240
|
-
var more: String =
|
|
499
|
+
var more: String = (
|
|
500
|
+
"" if ids.size() <= named.size() else ", and %d more" % (ids.size() - named.size())
|
|
501
|
+
)
|
|
241
502
|
print(
|
|
242
503
|
(
|
|
243
504
|
"%s %s > %s left %d: %s%s"
|
|
@@ -268,8 +529,8 @@ class GdhRunner:
|
|
|
268
529
|
said += " of " + script.resource_path
|
|
269
530
|
return said
|
|
270
531
|
|
|
271
|
-
## [param args] with everything up to the script at [param own] given as gdUnit4's tool name.
|
|
272
|
-
## path is compared with its separators and, on Windows, its case folded, since the engine
|
|
532
|
+
## [param args] with everything up to the script at [param own] given as gdUnit4's tool name.
|
|
533
|
+
## The path is compared with its separators and, on Windows, its case folded, since the engine
|
|
273
534
|
## reports the script as `X:/a/listed.gd` when it was started as `X:\a\listed.gd`.
|
|
274
535
|
static func options_of(args: PackedStringArray, own: String) -> PackedStringArray:
|
|
275
536
|
var wanted: String = spelt(own)
|
|
@@ -309,13 +570,14 @@ class GdhRowShare:
|
|
|
309
570
|
class GdhIndexedSession:
|
|
310
571
|
extends GdUnitTestSession
|
|
311
572
|
|
|
312
|
-
## The cases found by id, which [GdhSessionCheck] reads to tell that the console used this
|
|
573
|
+
## The cases found by id, which [GdhSessionCheck] reads to tell that the console used this
|
|
574
|
+
## session.
|
|
313
575
|
var lookups: int = 0
|
|
314
576
|
var _by_id: Dictionary[String, GdUnitTestCase] = {}
|
|
315
577
|
|
|
316
578
|
func _init(cases: Array[GdUnitTestCase], reports_in: String) -> void:
|
|
317
579
|
super(cases, reports_in)
|
|
318
|
-
for test: GdUnitTestCase in
|
|
580
|
+
for test: GdUnitTestCase in cases:
|
|
319
581
|
_by_id[str(test.guid)] = test
|
|
320
582
|
|
|
321
583
|
func find_test_by_id(id: GdUnitGUID) -> GdUnitTestCase:
|
|
@@ -328,7 +590,20 @@ class GdhIndexedSession:
|
|
|
328
590
|
static func of(made: GdUnitTestSession) -> GdUnitTestSession:
|
|
329
591
|
if made is GdhIndexedSession:
|
|
330
592
|
return made
|
|
331
|
-
|
|
593
|
+
# Read by name, since a gdUnit4 whose session indexes its cases has no such member and a
|
|
594
|
+
# script naming it would not compile there.
|
|
595
|
+
var cases: Array[GdUnitTestCase] = []
|
|
596
|
+
var held: Variant = made.get(&"_test_cases")
|
|
597
|
+
if held is Array:
|
|
598
|
+
var listed: Array = held
|
|
599
|
+
cases.assign(listed)
|
|
600
|
+
return GdhIndexedSession.new(cases, made.report_path)
|
|
601
|
+
|
|
602
|
+
## Whether [param made] already finds a case by its id in a dictionary, as a gdUnit4 session
|
|
603
|
+
## with [code]test_count()[/code] does: gdUnit4 added both in one change, and took away the
|
|
604
|
+
## array of cases [method of] reads.
|
|
605
|
+
static func indexes_itself(made: GdUnitTestSession) -> bool:
|
|
606
|
+
return made.has_method(&"test_count")
|
|
332
607
|
|
|
333
608
|
|
|
334
609
|
## A session hook of the runner's own, failing the run when the [GdhIndexedSession] is not the
|
|
@@ -346,16 +621,20 @@ class GdhSessionCheck:
|
|
|
346
621
|
_runner = runner
|
|
347
622
|
|
|
348
623
|
func startup(session: GdUnitTestSession) -> GdUnitResult:
|
|
349
|
-
if session is GdhIndexedSession:
|
|
624
|
+
if session is GdhIndexedSession or GdhIndexedSession.indexes_itself(session):
|
|
350
625
|
return GdUnitResult.success()
|
|
351
|
-
return
|
|
352
|
-
|
|
626
|
+
return (
|
|
627
|
+
GdUnitResult
|
|
628
|
+
. error(
|
|
353
629
|
(
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
630
|
+
(
|
|
631
|
+
"gdharness test: gdUnit4 %s handed its session hooks the session before "
|
|
632
|
+
+ "announcing it, so the runner's indexed session is not the one they "
|
|
633
|
+
+ "read. This gdUnit4 starts a session differently from the ones gdharness "
|
|
634
|
+
+ "knows; report it to gdharness with the version."
|
|
635
|
+
)
|
|
636
|
+
% GdUnit4Version.current()
|
|
357
637
|
)
|
|
358
|
-
% GdUnit4Version.current()
|
|
359
638
|
)
|
|
360
639
|
)
|
|
361
640
|
|
|
@@ -363,14 +642,18 @@ class GdhSessionCheck:
|
|
|
363
642
|
var indexed: GdhIndexedSession = session as GdhIndexedSession
|
|
364
643
|
if indexed == null or _runner.cases_ended == 0 or indexed.lookups > 0:
|
|
365
644
|
return GdUnitResult.success()
|
|
366
|
-
return
|
|
367
|
-
|
|
645
|
+
return (
|
|
646
|
+
GdUnitResult
|
|
647
|
+
. error(
|
|
368
648
|
(
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
649
|
+
(
|
|
650
|
+
"gdharness test: gdUnit4 %s ran %d cases without looking one up in the "
|
|
651
|
+
+ "runner's indexed session, so its console reads another session. This "
|
|
652
|
+
+ "gdUnit4 starts a session differently from the ones gdharness knows; "
|
|
653
|
+
+ "report it to gdharness with the version."
|
|
654
|
+
)
|
|
655
|
+
% [GdUnit4Version.current(), _runner.cases_ended]
|
|
372
656
|
)
|
|
373
|
-
% [GdUnit4Version.current(), _runner.cases_ended]
|
|
374
657
|
)
|
|
375
658
|
)
|
|
376
659
|
|
|
@@ -396,8 +679,8 @@ class GdhShutdownWalk:
|
|
|
396
679
|
static func marker(said: String) -> Array[GDScript]:
|
|
397
680
|
var printing: GDScript = GDScript.new()
|
|
398
681
|
printing.source_code = (
|
|
399
|
-
'extends RefCounted\n\nvar said: String = ""\n\n\nfunc _notification(what: int) ->
|
|
400
|
-
+ "\tif what == NOTIFICATION_PREDELETE:\n\t\tprint(said)\n"
|
|
682
|
+
'extends RefCounted\n\nvar said: String = ""\n\n\nfunc _notification(what: int) -> '
|
|
683
|
+
+ "void:\n\tif what == NOTIFICATION_PREDELETE:\n\t\tprint(said)\n"
|
|
401
684
|
)
|
|
402
685
|
var marking: GDScript = GDScript.new()
|
|
403
686
|
marking.source_code = (
|
|
@@ -434,233 +717,3 @@ class GdhShutdownWalk:
|
|
|
434
717
|
if not inner.begins_with("."):
|
|
435
718
|
var _inner: bool = pending.append(directory.path_join(inner))
|
|
436
719
|
ThemeDB.set_meta(WALK_HOLD, held)
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
func _initialize() -> void:
|
|
440
|
-
_runner = started(OS.get_cmdline_user_args())
|
|
441
|
-
_runner.own_path = get_script().resource_path
|
|
442
|
-
root.add_child(_runner)
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
## As gdUnit4's own `GdUnitCmdTool.gd` ends, which names in a verbose run the nodes left orphaned.
|
|
446
|
-
func _finalize() -> void:
|
|
447
|
-
queue_delete(_runner)
|
|
448
|
-
if OS.is_stdout_verbose():
|
|
449
|
-
print("Orphan nodes left as the run ends:")
|
|
450
|
-
Window.print_orphan_nodes()
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
## The runner over the suites [param given] lists, to be added to the tree, which starts it. Shared
|
|
454
|
-
## by this script and the suites scene of the tests addon, which loads this file from where the
|
|
455
|
-
## server ships it and calls this, since the addon itself names no gdUnit4 class.
|
|
456
|
-
static func started(given: PackedStringArray) -> GdhRunner:
|
|
457
|
-
# First, so the marker is older than every script the run loads.
|
|
458
|
-
var oldest: Array[GDScript] = []
|
|
459
|
-
if GdhShutdownWalk.guarded():
|
|
460
|
-
oldest = GdhShutdownWalk.marker(WALK_ENDED)
|
|
461
|
-
OS.low_processor_usage_mode_sleep_usec = FRAME_SLEEP_USEC
|
|
462
|
-
var at: int = given.find(GDUNIT)
|
|
463
|
-
var asked: PackedStringArray = given if at < 0 else given.slice(0, at)
|
|
464
|
-
var unread: PackedStringArray = []
|
|
465
|
-
for flag: String in REPORTS_OFF:
|
|
466
|
-
if asked.has(flag):
|
|
467
|
-
var _unread: bool = unread.append(REPORTS_OFF[flag])
|
|
468
|
-
var hooks: GdUnitTestSessionHookService = GdUnitTestSessionHookService.instance()
|
|
469
|
-
var off: PackedStringArray = unread_off(hooks, unread)
|
|
470
|
-
for path: String in unread:
|
|
471
|
-
if not off.has(path):
|
|
472
|
-
push_error(
|
|
473
|
-
(
|
|
474
|
-
(
|
|
475
|
-
"gdharness test: gdUnit4 %s registers no session hook %s, so the report it writes is not"
|
|
476
|
-
+ " turned off; report it to gdharness with the version."
|
|
477
|
-
)
|
|
478
|
-
% [GdUnit4Version.current(), path]
|
|
479
|
-
)
|
|
480
|
-
)
|
|
481
|
-
var suites: PackedStringArray = listed(asked, LISTED)
|
|
482
|
-
var runner: GdhRunner = GdhRunner.new()
|
|
483
|
-
runner.walk_scripts = oldest
|
|
484
|
-
if at >= 0:
|
|
485
|
-
runner.given = given.slice(at + 1)
|
|
486
|
-
if asked.has(TOGETHER):
|
|
487
|
-
runner.together = compiled_together(suites)
|
|
488
|
-
var expected_out: String = argument_after(asked, EXPECTED_OUT)
|
|
489
|
-
if not expected_out.is_empty():
|
|
490
|
-
write_expected(expected_out, suites)
|
|
491
|
-
runner.add_test_suites(suites)
|
|
492
|
-
for entry: String in listed(asked, SKIPPING):
|
|
493
|
-
if is_one_row(entry):
|
|
494
|
-
runner.left_out_rows[entry] = true
|
|
495
|
-
elif is_one_case(entry):
|
|
496
|
-
runner.left_out[entry] = true
|
|
497
|
-
else:
|
|
498
|
-
runner.skip_test_suite(entry)
|
|
499
|
-
runner.row_shares = row_shares_of(listed(asked, ROWS))
|
|
500
|
-
# Added to the list itself rather than through `register`, which saves every hook it holds into
|
|
501
|
-
# `project.godot`.
|
|
502
|
-
hooks.enigne_hooks.append(GdhSessionCheck.new(runner))
|
|
503
|
-
return runner
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
## Turns off the hooks among [param hooks] whose scripts [param unread] names, answering the scripts
|
|
507
|
-
## of those it turned off. Through the mark gdUnit4's own `is_enabled` reads, rather than its
|
|
508
|
-
## `enable_hook`, which writes the setting into `project.godot`: a run changes nothing in the tree.
|
|
509
|
-
static func unread_off(hooks: GdUnitTestSessionHookService, unread: PackedStringArray) -> PackedStringArray:
|
|
510
|
-
var off: PackedStringArray = []
|
|
511
|
-
for hook: GdUnitTestSessionHook in hooks.enigne_hooks:
|
|
512
|
-
var path: String = hook.get_script().resource_path
|
|
513
|
-
if unread.has(path):
|
|
514
|
-
hook.set_meta("enabled", false)
|
|
515
|
-
var _added: bool = off.append(path)
|
|
516
|
-
return off
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
## [param suites] compiled inside one script, answered for the caller to hold so gdUnit4's own loads
|
|
520
|
-
## find them compiled. The engine keeps what it read of a class only while the compile that asked for
|
|
521
|
-
## it runs (`GDScriptCache`), so a suite compiled on its own reads again every class it reaches a
|
|
522
|
-
## member of, some seventy milliseconds a suite. As the preloads of one script naming every class of
|
|
523
|
-
## the game's own first, held from that script's compile, they share one reading of each: two hundred
|
|
524
|
-
## suites compiled in a third of the time they took one at a time. The names are public, since a
|
|
525
|
-
## private one unread is a warning and every warning an error, and each a variable, since a class is
|
|
526
|
-
## no constant expression. A suite that does not compile fails here and again when gdUnit4 loads it,
|
|
527
|
-
## which reports it as it would have. A directory listed is left to gdUnit4 to walk.
|
|
528
|
-
static func compiled_together(suites: PackedStringArray) -> GDScript:
|
|
529
|
-
var lines: PackedStringArray = ["extends RefCounted"]
|
|
530
|
-
var at: int = 0
|
|
531
|
-
for named: String in held_classes():
|
|
532
|
-
var _held: bool = lines.append("var held_%d: Variant = %s" % [at, named])
|
|
533
|
-
at += 1
|
|
534
|
-
at = 0
|
|
535
|
-
for path: String in suites:
|
|
536
|
-
if not path.ends_with(".gd"):
|
|
537
|
-
continue
|
|
538
|
-
var _suite: bool = lines.append('var suite_%d: Variant = preload("%s")' % [at, path])
|
|
539
|
-
at += 1
|
|
540
|
-
var together: GDScript = GDScript.new()
|
|
541
|
-
together.source_code = "\n".join(lines) + "\n"
|
|
542
|
-
var _compiled: Error = together.reload()
|
|
543
|
-
return together
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
## The classes [method compiled_together] names before the suites: the game's own GDScript classes,
|
|
547
|
-
## none of an addon's or a suite's, then gdUnit4's suite. Only GDScript shares the engine's reading,
|
|
548
|
-
## and a sandboxed mod's SafeGDScript class would load the sandbox's compiler, some 290 MB and up to
|
|
549
|
-
## 2.5 s, for nothing.
|
|
550
|
-
static func held_classes(
|
|
551
|
-
classes: Array[Dictionary] = ProjectSettings.get_global_class_list()
|
|
552
|
-
) -> PackedStringArray:
|
|
553
|
-
var held: PackedStringArray = []
|
|
554
|
-
for known: Dictionary in classes:
|
|
555
|
-
var path: String = known["path"]
|
|
556
|
-
var own: bool = not path.begins_with("res://addons/") and not path.ends_with("_test.gd")
|
|
557
|
-
if own and known["language"] == &"GDScript":
|
|
558
|
-
var _own: bool = held.append(str(known["class"]))
|
|
559
|
-
var _suite: bool = held.append("GdUnitTestSuite")
|
|
560
|
-
return held
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
## What the file after [param flag] in [param args] names, one a line in order, or none when no file
|
|
564
|
-
## is named: gdUnit4 then says it found nothing, which a gate refuses.
|
|
565
|
-
static func listed(args: PackedStringArray, flag: String) -> PackedStringArray:
|
|
566
|
-
var path: String = argument_after(args, flag)
|
|
567
|
-
if path.is_empty():
|
|
568
|
-
return PackedStringArray()
|
|
569
|
-
var named: PackedStringArray = []
|
|
570
|
-
for line: String in FileAccess.get_file_as_string(path).split("\n", false):
|
|
571
|
-
if not line.strip_edges().is_empty():
|
|
572
|
-
var _named: bool = named.append(line.strip_edges())
|
|
573
|
-
return named
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
## Whether [param entry] of a [constant SKIPPING] file names one case of one suite file, as
|
|
577
|
-
## `res://suite.gd:test_case`, rather than a suite or a case by gdUnit4's looser names, which go to
|
|
578
|
-
## gdUnit4 as they are.
|
|
579
|
-
## Whether [param entry] names one row of a parameterised case, as `res://suite.gd:test_case:row`.
|
|
580
|
-
static func is_one_row(entry: String) -> bool:
|
|
581
|
-
var at: int = entry.rfind(":")
|
|
582
|
-
var row: String = entry.substr(at + 1)
|
|
583
|
-
return at > 0 and row.is_valid_int() and not row.begins_with("-") and is_one_case(entry.substr(0, at))
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
static func is_one_case(entry: String) -> bool:
|
|
587
|
-
var at: int = entry.rfind(":")
|
|
588
|
-
return (
|
|
589
|
-
entry.begins_with("res://")
|
|
590
|
-
and at > "res://".length()
|
|
591
|
-
and entry.substr(0, at).ends_with(".gd")
|
|
592
|
-
and entry.substr(at + 1).is_valid_ascii_identifier()
|
|
593
|
-
)
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
## The row shares [param lines] of a [constant ROWS] file give, by case.
|
|
597
|
-
static func row_shares_of(lines: PackedStringArray) -> Dictionary[String, GdhRowShare]:
|
|
598
|
-
var shares: Dictionary[String, GdhRowShare] = {}
|
|
599
|
-
for line: String in lines:
|
|
600
|
-
var parts: PackedStringArray = line.split("\t")
|
|
601
|
-
if parts.size() < 2:
|
|
602
|
-
continue
|
|
603
|
-
var share: GdhRowShare = GdhRowShare.new()
|
|
604
|
-
share.only = parts[1] == "only"
|
|
605
|
-
for row: String in parts.slice(2):
|
|
606
|
-
if row.is_valid_int():
|
|
607
|
-
var _added: bool = share.rows.append(row.to_int())
|
|
608
|
-
shares[parts[0]] = share
|
|
609
|
-
return shares
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
## The argument after [param flag] in [param args], or "" when there is none.
|
|
613
|
-
static func argument_after(args: PackedStringArray, flag: String) -> String:
|
|
614
|
-
var at: int = args.find(flag)
|
|
615
|
-
if at < 0 or at + 1 >= args.size():
|
|
616
|
-
return ""
|
|
617
|
-
return args[at + 1]
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
## What each of [param suites] declares in [param constant], by suite path and then case name, read
|
|
621
|
-
## off the compiled suite's constants so a declaration is whatever the engine made of it. A directory
|
|
622
|
-
## listed is walked for its scripts, as gdUnit4 walks it for suites. Only a script whose source names
|
|
623
|
-
## the constant is loaded: gdUnit4 stops on a suite that does not load only when its own load is the
|
|
624
|
-
## one that fails, so a broken suite loaded here first let the rest of its directory run.
|
|
625
|
-
static func expected_by_suite(suites: PackedStringArray, constant: String) -> Dictionary:
|
|
626
|
-
var expected: Dictionary = {}
|
|
627
|
-
for path: String in scripts_in(suites):
|
|
628
|
-
if not FileAccess.file_exists(path) or not FileAccess.get_file_as_string(path).contains(constant):
|
|
629
|
-
continue
|
|
630
|
-
var suite: Script = load(path)
|
|
631
|
-
var declared: Variant = suite.get_script_constant_map().get(constant)
|
|
632
|
-
if declared is Dictionary:
|
|
633
|
-
expected[path] = declared
|
|
634
|
-
return expected
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
## The scripts [param paths] names: each script, and every script under each directory.
|
|
638
|
-
static func scripts_in(paths: PackedStringArray) -> PackedStringArray:
|
|
639
|
-
var found: PackedStringArray = []
|
|
640
|
-
var pending: PackedStringArray = []
|
|
641
|
-
for path: String in paths:
|
|
642
|
-
if path.ends_with(".gd"):
|
|
643
|
-
var _script: bool = found.append(path)
|
|
644
|
-
else:
|
|
645
|
-
var _directory: bool = pending.append(path.trim_suffix("/"))
|
|
646
|
-
while not pending.is_empty():
|
|
647
|
-
var directory: String = pending[pending.size() - 1]
|
|
648
|
-
pending.remove_at(pending.size() - 1)
|
|
649
|
-
for file: String in DirAccess.get_files_at(directory):
|
|
650
|
-
if file.ends_with(".gd"):
|
|
651
|
-
var _file: bool = found.append("%s/%s" % [directory, file])
|
|
652
|
-
for inner: String in DirAccess.get_directories_at(directory):
|
|
653
|
-
var _inner: bool = pending.append("%s/%s" % [directory, inner])
|
|
654
|
-
return found
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
static func write_expected(path: String, suites: PackedStringArray) -> void:
|
|
658
|
-
var file: FileAccess = FileAccess.open(path, FileAccess.WRITE)
|
|
659
|
-
if file == null:
|
|
660
|
-
push_error("Could not write the expected errors to %s: %s" % [path, FileAccess.get_open_error()])
|
|
661
|
-
return
|
|
662
|
-
var expected: Dictionary = {}
|
|
663
|
-
for kind: String in EXPECTED:
|
|
664
|
-
expected[kind] = expected_by_suite(suites, EXPECTED[kind])
|
|
665
|
-
var _stored: bool = file.store_string(JSON.stringify(expected))
|
|
666
|
-
file.close()
|