diff --git a/trimsock.gd/addons/vest/_generated-mixins/1-8182042.gd b/trimsock.gd/addons/vest/_generated-mixins/1-8182042.gd
index 4759f81..7f4e80b 100644
--- a/trimsock.gd/addons/vest/_generated-mixins/1-8182042.gd
+++ b/trimsock.gd/addons/vest/_generated-mixins/1-8182042.gd
@@ -61,9 +61,18 @@ func _get_suite() -> VestDefs.Suite:
await call(method["name"])
for method in case_methods:
- test(method["name"].trim_prefix("test").capitalize(), func(): await call(method["name"]))
+ var method_name := method["name"] as String
+ var test_name := method_name.trim_prefix("test").trim_suffix("__only").capitalize()
+ var callback := func(): await call(method["name"])
+ var is_only := _is_only(method_name)
+
+ test(test_name, callback, is_only, method_name)
for method in parametric_methods:
+ var method_name := method["name"] as String
+ var test_name := method_name.trim_prefix("test").trim_suffix("__only").capitalize()
+ var is_only := _is_only(method_name)
+
var param_provider_name := method["default_args"][0] as String
if not has_method(param_provider_name):
push_warning(
@@ -80,7 +89,12 @@ func _get_suite() -> VestDefs.Suite:
for i in range(params.size()):
test(
- "%s#%d %s" % [method["name"].trim_prefix("test").capitalize(), i+1, params[i]],
- func(): await callv(method["name"], params[i])
+ "%s#%d %s" % [test_name, i+1, params[i]],
+ func(): await callv(method["name"], params[i]),
+ is_only,
+ method_name
)
)
+
+func _is_only(name: String) -> bool:
+ return name.ends_with("__only")
diff --git a/trimsock.gd/addons/vest/_generated-mixins/1-8182042.gd.uid b/trimsock.gd/addons/vest/_generated-mixins/1-8182042.gd.uid
index 727a363..b300e57 100644
--- a/trimsock.gd/addons/vest/_generated-mixins/1-8182042.gd.uid
+++ b/trimsock.gd/addons/vest/_generated-mixins/1-8182042.gd.uid
@@ -1 +1 @@
-uid://cg53q8t5aibti
+uid://mm20mdmsc5ii
diff --git a/trimsock.gd/addons/vest/_generated-mixins/2-ffc90559.gd.uid b/trimsock.gd/addons/vest/_generated-mixins/2-ffc90559.gd.uid
index bba802c..97d7766 100644
--- a/trimsock.gd/addons/vest/_generated-mixins/2-ffc90559.gd.uid
+++ b/trimsock.gd/addons/vest/_generated-mixins/2-ffc90559.gd.uid
@@ -1 +1 @@
-uid://d168ndvsqupun
+uid://caf7owv25i7op
diff --git a/trimsock.gd/addons/vest/_generated-mixins/3-27f38ba0.gd.uid b/trimsock.gd/addons/vest/_generated-mixins/3-27f38ba0.gd.uid
index ba129d9..c099974 100644
--- a/trimsock.gd/addons/vest/_generated-mixins/3-27f38ba0.gd.uid
+++ b/trimsock.gd/addons/vest/_generated-mixins/3-27f38ba0.gd.uid
@@ -1 +1 @@
-uid://8ojcq7eq3de3
+uid://d3mmot6r5ycm6
diff --git a/trimsock.gd/addons/vest/_generated-mixins/4-795b469a.gd.uid b/trimsock.gd/addons/vest/_generated-mixins/4-795b469a.gd.uid
index fc69f9a..9021b7a 100644
--- a/trimsock.gd/addons/vest/_generated-mixins/4-795b469a.gd.uid
+++ b/trimsock.gd/addons/vest/_generated-mixins/4-795b469a.gd.uid
@@ -1 +1 @@
-uid://hlpirirrosh8
+uid://v3npn3goh4em
diff --git a/trimsock.gd/addons/vest/_generated-mixins/5-96ae432f.gd.uid b/trimsock.gd/addons/vest/_generated-mixins/5-96ae432f.gd.uid
index 14678fe..7dcf75d 100644
--- a/trimsock.gd/addons/vest/_generated-mixins/5-96ae432f.gd.uid
+++ b/trimsock.gd/addons/vest/_generated-mixins/5-96ae432f.gd.uid
@@ -1 +1 @@
-uid://hgh0houeje1w
+uid://c8oa5ooaqwi11
diff --git a/trimsock.gd/addons/vest/_generated-mixins/6-3c4a4dd7.gd.uid b/trimsock.gd/addons/vest/_generated-mixins/6-3c4a4dd7.gd.uid
index cb88ed1..a3ca195 100644
--- a/trimsock.gd/addons/vest/_generated-mixins/6-3c4a4dd7.gd.uid
+++ b/trimsock.gd/addons/vest/_generated-mixins/6-3c4a4dd7.gd.uid
@@ -1 +1 @@
-uid://bki88xn3pwurs
+uid://dqqt1ecooeqdd
diff --git a/trimsock.gd/addons/vest/cli/vest-cli-runner.gd b/trimsock.gd/addons/vest/cli/vest-cli-runner.gd
new file mode 100644
index 0000000..c15fdb1
--- /dev/null
+++ b/trimsock.gd/addons/vest/cli/vest-cli-runner.gd
@@ -0,0 +1,93 @@
+extends RefCounted
+class_name VestCLIRunner
+
+## Implements functionality to run tests
+
+var _peer: StreamPeerTCP = null
+
+## Run tests with [Params]
+func run(params: VestCLI.Params) -> int:
+ var validation_errors := params.validate()
+ if not validation_errors.is_empty():
+ for error in validation_errors:
+ OS.alert(error)
+ push_error(error)
+ return 1
+
+ await _connect(params)
+
+ var results := await _run_tests(params)
+ _report(params, results)
+ _send_results_over_network(params, results)
+
+ _disconnect()
+
+ if results.get_aggregate_status() == VestResult.TEST_PASS:
+ print_rich("All tests [color=green]passed[/color]!")
+ return 0
+ else:
+ print_rich("There are test [color=red]failures[/color]!")
+ return 1
+
+func _run_tests(params: VestCLI.Params) -> VestResult.Suite:
+ var runner := VestLocalRunner.new()
+ runner.on_partial_result.connect(func(result: VestResult.Suite):
+ if _peer != null:
+ if result != null:
+ _peer.put_var(result._to_wire(), true)
+ else:
+ _peer.put_var(result, true)
+ )
+
+ var results: VestResult.Suite
+ if params.run_file:
+ results = await runner.run_script_at(params.run_file, params.only_mode)
+ elif params.run_glob:
+ results = await runner.run_glob(params.run_glob, params.only_mode)
+
+ return results
+
+func _report(params: VestCLI.Params, results: VestResult.Suite):
+ var report := TAPReporter.report(results)
+
+ if params.report_format:
+ if params.report_file in ["", "-"]:
+ print(report)
+ else:
+ var fa := FileAccess.open(params.report_file, FileAccess.WRITE)
+ fa.store_string(report)
+ fa.close()
+
+func _connect(params: VestCLI.Params):
+ if not params.host and params.port == -1:
+ return
+
+ var host := params.host
+ var port := params.port
+
+ if not host: host = "0.0.0.0"
+ if port == -1: port = 54932
+
+ var peer := StreamPeerTCP.new()
+ var err := peer.connect_to_host(host, port)
+ if err != OK:
+ push_warning("Couldn't connect on port %d! %s" % [port, error_string(err)])
+ return
+
+ await Vest.until(func(): peer.poll(); return peer.get_status() != StreamPeerTCP.STATUS_CONNECTING)
+ if peer.get_status() != StreamPeerTCP.STATUS_CONNECTED:
+ push_warning("Connection failed! Socket status: %d" % [peer.get_status()])
+ return
+
+ peer.set_no_delay(true)
+ _peer = peer
+
+func _disconnect():
+ if _peer != null:
+ _peer.disconnect_from_host()
+
+func _send_results_over_network(params: VestCLI.Params, results: VestResult.Suite):
+ if not _peer:
+ return
+
+ _peer.put_var(results._to_wire(), true)
diff --git a/trimsock.gd/addons/vest/cli/vest-cli-runner.gd.uid b/trimsock.gd/addons/vest/cli/vest-cli-runner.gd.uid
new file mode 100644
index 0000000..bad75d7
--- /dev/null
+++ b/trimsock.gd/addons/vest/cli/vest-cli-runner.gd.uid
@@ -0,0 +1 @@
+uid://bw0ajf5t50wi0
diff --git a/trimsock.gd/addons/vest/cli/vest-cli-scene.tscn b/trimsock.gd/addons/vest/cli/vest-cli-scene.tscn
index 49e1f79..6bb7a49 100644
--- a/trimsock.gd/addons/vest/cli/vest-cli-scene.tscn
+++ b/trimsock.gd/addons/vest/cli/vest-cli-scene.tscn
@@ -3,13 +3,15 @@
[sub_resource type="GDScript" id="GDScript_de1pc"]
script/source = "extends Node
+# Used to run tests in debug mode
+
func _ready():
Vest._register_scene_tree(get_tree())
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_MINIMIZED)
var params := Vest.__.LocalSettings.run_params
- var runner := VestCLI.Runner.new()
+ var runner := VestCLIRunner.new()
var exit_code := await runner.run(params)
get_tree().quit(exit_code)
diff --git a/trimsock.gd/addons/vest/cli/vest-cli.gd b/trimsock.gd/addons/vest/cli/vest-cli.gd
index 98c55d5..6b4ac5b 100644
--- a/trimsock.gd/addons/vest/cli/vest-cli.gd
+++ b/trimsock.gd/addons/vest/cli/vest-cli.gd
@@ -19,6 +19,9 @@ class Params:
## Path for saving the report
var report_file: String = ""
+ ## How to handle tests marked as `only`
+ var only_mode: int = Vest.__.ONLY_DISABLED
+
## Host to connect to for sending results
var host: String = ""
@@ -50,6 +53,11 @@ class Params:
if host: result.append_array(["--vest-host", host])
if port != -1: result.append_array(["--vest-port", str(port)])
+ match only_mode:
+ Vest.__.ONLY_DISABLED: result.append("--no-only")
+ Vest.__.ONLY_AUTO: result.append("--auto-only")
+ Vest.__.ONLY_ENABLED: result.append("--only")
+
return result
## Parse an array of CLI parameters.
@@ -68,78 +76,12 @@ class Params:
elif arg == "--vest-report-format": result.report_format = val
elif arg == "--vest-port": result.port = val.to_int()
elif arg == "--vest-host": result.host = val
+ elif arg == "--no-only": result.only_mode = Vest.__.ONLY_DISABLED
+ elif arg == "--only": result.only_mode = Vest.__.ONLY_ENABLED
+ elif arg == "--auto-only": result.only_mode = Vest.__.ONLY_AUTO
return result
-## Implements functionality to run tests
-class Runner:
- ## Run tests with [Params]
- func run(params: Params) -> int:
- var validation_errors := params.validate()
- if not validation_errors.is_empty():
- for error in validation_errors:
- OS.alert(error)
- push_error(error)
- return 1
-
- var results := await _run_tests(params)
- _report(params, results)
- await _send_results_over_network(params, results)
-
- if results.get_aggregate_status() == VestResult.TEST_PASS:
- print_rich("All tests [color=green]passed[/color]!")
- return 0
- else:
- print_rich("There are test [color=red]failures[/color]!")
- return 1
-
- func _run_tests(params: Params) -> VestResult.Suite:
- var runner := VestLocalRunner.new()
-
- var results: VestResult.Suite
- if params.run_file:
- results = await runner.run_script_at(params.run_file)
- elif params.run_glob:
- results = await runner.run_glob(params.run_glob)
-
- return results
-
- func _report(params: Params, results: VestResult.Suite):
- var report := TAPReporter.report(results)
-
- if params.report_format:
- if params.report_file in ["", "-"]:
- print(report)
- else:
- var fa := FileAccess.open(params.report_file, FileAccess.WRITE)
- fa.store_string(report)
- fa.close()
-
- func _send_results_over_network(params: Params, results: VestResult.Suite):
- if not params.host and params.port == -1:
- return
-
- var host := params.host
- var port := params.port
-
- if not host: host = "0.0.0.0"
- if port == -1: port = 54932
-
- var peer := StreamPeerTCP.new()
- var err := peer.connect_to_host(host, port)
- if err != OK:
- push_warning("Couldn't connect on port %d! %s" % [port, error_string(err)])
- return
-
- await Vest.until(func(): peer.poll(); return peer.get_status() != StreamPeerTCP.STATUS_CONNECTING)
- if peer.get_status() != StreamPeerTCP.STATUS_CONNECTED:
- push_warning("Connection failed! Socket status: %d" % [peer.get_status()])
- return
-
- peer.put_var(results._to_wire(), true)
- peer.disconnect_from_host()
-
-
## Run vest CLI with parameters.
## [br][br]
## Returns the spawned process' ID.
@@ -161,7 +103,7 @@ func _init():
await process_frame
var params := Params.parse(OS.get_cmdline_args())
- var runner := Runner.new()
+ var runner := VestCLIRunner.new()
var exit_code := await runner.run(params)
diff --git a/trimsock.gd/addons/vest/commands/run-test-command.gd b/trimsock.gd/addons/vest/commands/run-test-command.gd
index 4389224..13ed67d 100644
--- a/trimsock.gd/addons/vest/commands/run-test-command.gd
+++ b/trimsock.gd/addons/vest/commands/run-test-command.gd
@@ -1,24 +1,13 @@
@tool
extends Node
-static var _instance = null
-
-static func find():
- return _instance
-
-static func execute() -> void:
- if _instance:
- _instance.create_test()
- else:
- push_warning("No instance of Create Test command found!")
-
func run_test():
- _run(false)
+ _run(false, Vest.__.ONLY_AUTO)
func debug_test():
- _run(true)
+ _run(true, Vest.__.ONLY_AUTO)
-func _run(is_debug: bool) -> void:
+func _run(is_debug: bool, only_mode: int) -> void:
var editor_interface := Vest._get_editor_interface()
var script_editor := editor_interface.get_script_editor() as ScriptEditor
@@ -34,7 +23,7 @@ func _run(is_debug: bool) -> void:
print_verbose("Running test \"%s\"" % [edited_script.resource_path])
var vest_ui := VestUI._get_ui()
- vest_ui.run_script(edited_script, is_debug)
+ vest_ui.run_script(edited_script, is_debug, only_mode)
func _is_ancestor_of(base_script: Script, script: Script) -> bool:
for i in range(128): # Prevent runaway loops
@@ -45,7 +34,6 @@ func _is_ancestor_of(base_script: Script, script: Script) -> bool:
return false
func _ready():
- _instance = self
var editor := Vest._get_editor_interface()
editor.get_command_palette().add_command("Run test", "vest/run-test", run_test, "F7")
editor.get_command_palette().add_command("Debug test", "vest/debug-test", debug_test, "Ctrl+F7")
diff --git a/trimsock.gd/addons/vest/icons/benchmark-fail.svg.import b/trimsock.gd/addons/vest/icons/benchmark-fail.svg.import
index 122eb8c..5056f26 100644
--- a/trimsock.gd/addons/vest/icons/benchmark-fail.svg.import
+++ b/trimsock.gd/addons/vest/icons/benchmark-fail.svg.import
@@ -5,6 +5,7 @@ type="CompressedTexture2D"
uid="uid://c1cdr1khuiy2f"
path="res://.godot/imported/benchmark-fail.svg-b20a3c22c391ea7bcebd7f59f83ca9d5.ctex"
metadata={
+"has_editor_variant": true,
"vram_texture": false
}
@@ -33,5 +34,5 @@ process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=2.0
-editor/scale_with_editor_scale=false
-editor/convert_colors_with_editor_theme=false
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/trimsock.gd/addons/vest/icons/benchmark-pass.svg.import b/trimsock.gd/addons/vest/icons/benchmark-pass.svg.import
index d790d50..9ca506f 100644
--- a/trimsock.gd/addons/vest/icons/benchmark-pass.svg.import
+++ b/trimsock.gd/addons/vest/icons/benchmark-pass.svg.import
@@ -5,6 +5,7 @@ type="CompressedTexture2D"
uid="uid://bt8iem8l0pq4s"
path="res://.godot/imported/benchmark-pass.svg-7479ef55a95006e5fb1a614e003877eb.ctex"
metadata={
+"has_editor_variant": true,
"vram_texture": false
}
@@ -33,5 +34,5 @@ process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=2.0
-editor/scale_with_editor_scale=false
-editor/convert_colors_with_editor_theme=false
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/trimsock.gd/addons/vest/icons/benchmark.svg.import b/trimsock.gd/addons/vest/icons/benchmark.svg.import
index 19e8983..7b9229d 100644
--- a/trimsock.gd/addons/vest/icons/benchmark.svg.import
+++ b/trimsock.gd/addons/vest/icons/benchmark.svg.import
@@ -5,6 +5,7 @@ type="CompressedTexture2D"
uid="uid://b6yh8aylojfgt"
path="res://.godot/imported/benchmark.svg-b9f24369bdd89bc588a152cef6f09c00.ctex"
metadata={
+"has_editor_variant": true,
"vram_texture": false
}
@@ -33,5 +34,5 @@ process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=2.0
-editor/scale_with_editor_scale=false
-editor/convert_colors_with_editor_theme=false
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/trimsock.gd/addons/vest/icons/clear.svg.import b/trimsock.gd/addons/vest/icons/clear.svg.import
index b7fc175..195375a 100644
--- a/trimsock.gd/addons/vest/icons/clear.svg.import
+++ b/trimsock.gd/addons/vest/icons/clear.svg.import
@@ -5,6 +5,7 @@ type="CompressedTexture2D"
uid="uid://ctdipwc8sklwo"
path="res://.godot/imported/clear.svg-c74aed6190d9cab6b3b5d7c8ad724085.ctex"
metadata={
+"has_editor_variant": true,
"vram_texture": false
}
@@ -33,5 +34,5 @@ process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=2.0
-editor/scale_with_editor_scale=false
-editor/convert_colors_with_editor_theme=false
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/trimsock.gd/addons/vest/icons/collapse.svg b/trimsock.gd/addons/vest/icons/collapse.svg
new file mode 100644
index 0000000..b92a25a
--- /dev/null
+++ b/trimsock.gd/addons/vest/icons/collapse.svg
@@ -0,0 +1,68 @@
+
+
+
+
diff --git a/trimsock.gd/addons/vest/icons/collapse.svg.import b/trimsock.gd/addons/vest/icons/collapse.svg.import
new file mode 100644
index 0000000..83d5e88
--- /dev/null
+++ b/trimsock.gd/addons/vest/icons/collapse.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cv8va2smpv1lf"
+path="res://.godot/imported/collapse.svg-9b497d9ee40a464a0f26821fb8538f0b.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/vest/icons/collapse.svg"
+dest_files=["res://.godot/imported/collapse.svg-9b497d9ee40a464a0f26821fb8538f0b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=2.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/trimsock.gd/addons/vest/icons/debug.svg.import b/trimsock.gd/addons/vest/icons/debug.svg.import
index 007a7ed..2a33651 100644
--- a/trimsock.gd/addons/vest/icons/debug.svg.import
+++ b/trimsock.gd/addons/vest/icons/debug.svg.import
@@ -5,6 +5,7 @@ type="CompressedTexture2D"
uid="uid://dwhlyf5eyiect"
path="res://.godot/imported/debug.svg-10ec6677d3bd3a64aea2006222e97a3c.ctex"
metadata={
+"has_editor_variant": true,
"vram_texture": false
}
@@ -33,5 +34,5 @@ process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=2.0
-editor/scale_with_editor_scale=false
-editor/convert_colors_with_editor_theme=false
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/trimsock.gd/addons/vest/icons/expand.svg b/trimsock.gd/addons/vest/icons/expand.svg
new file mode 100644
index 0000000..fdb09d7
--- /dev/null
+++ b/trimsock.gd/addons/vest/icons/expand.svg
@@ -0,0 +1,68 @@
+
+
+
+
diff --git a/trimsock.gd/addons/vest/icons/expand.svg.import b/trimsock.gd/addons/vest/icons/expand.svg.import
new file mode 100644
index 0000000..34df78c
--- /dev/null
+++ b/trimsock.gd/addons/vest/icons/expand.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://sx0233b14cll"
+path="res://.godot/imported/expand.svg-6ad1f463c41f73d2aadcc7559e7670ff.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/vest/icons/expand.svg"
+dest_files=["res://.godot/imported/expand.svg-6ad1f463c41f73d2aadcc7559e7670ff.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=2.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/trimsock.gd/addons/vest/icons/fail.svg.import b/trimsock.gd/addons/vest/icons/fail.svg.import
index 94520aa..fd55afe 100644
--- a/trimsock.gd/addons/vest/icons/fail.svg.import
+++ b/trimsock.gd/addons/vest/icons/fail.svg.import
@@ -5,6 +5,7 @@ type="CompressedTexture2D"
uid="uid://de5ileacgwme1"
path="res://.godot/imported/fail.svg-fb1453c71ffec14739e9280d33cb8120.ctex"
metadata={
+"has_editor_variant": true,
"vram_texture": false
}
@@ -33,5 +34,5 @@ process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=2.0
-editor/scale_with_editor_scale=false
-editor/convert_colors_with_editor_theme=false
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/trimsock.gd/addons/vest/icons/hidden.svg b/trimsock.gd/addons/vest/icons/hidden.svg
new file mode 100644
index 0000000..8abbe79
--- /dev/null
+++ b/trimsock.gd/addons/vest/icons/hidden.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/trimsock.gd/addons/vest/icons/hidden.svg.import b/trimsock.gd/addons/vest/icons/hidden.svg.import
new file mode 100644
index 0000000..b87b8be
--- /dev/null
+++ b/trimsock.gd/addons/vest/icons/hidden.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://buuq6qptg3vll"
+path="res://.godot/imported/hidden.svg-58af1b7fb82759e99e9f427f659b9a32.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/vest/icons/hidden.svg"
+dest_files=["res://.godot/imported/hidden.svg-58af1b7fb82759e99e9f427f659b9a32.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=2.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/trimsock.gd/addons/vest/icons/jump-to.svg.import b/trimsock.gd/addons/vest/icons/jump-to.svg.import
index eba8300..049431c 100644
--- a/trimsock.gd/addons/vest/icons/jump-to.svg.import
+++ b/trimsock.gd/addons/vest/icons/jump-to.svg.import
@@ -5,6 +5,7 @@ type="CompressedTexture2D"
uid="uid://beoxvhp204wuv"
path="res://.godot/imported/jump-to.svg-c9add894b2d5164c6f4e6837154c4f30.ctex"
metadata={
+"has_editor_variant": true,
"vram_texture": false
}
@@ -33,5 +34,5 @@ process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=2.0
-editor/scale_with_editor_scale=false
-editor/convert_colors_with_editor_theme=false
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/trimsock.gd/addons/vest/icons/lightbulb.svg.import b/trimsock.gd/addons/vest/icons/lightbulb.svg.import
index 83b1998..707e301 100644
--- a/trimsock.gd/addons/vest/icons/lightbulb.svg.import
+++ b/trimsock.gd/addons/vest/icons/lightbulb.svg.import
@@ -5,6 +5,7 @@ type="CompressedTexture2D"
uid="uid://ckxkehgmjtjcx"
path="res://.godot/imported/lightbulb.svg-f4f05ab673622fe0cb66314541b2eac4.ctex"
metadata={
+"has_editor_variant": true,
"vram_texture": false
}
@@ -33,5 +34,5 @@ process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=2.0
-editor/scale_with_editor_scale=false
-editor/convert_colors_with_editor_theme=false
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/trimsock.gd/addons/vest/icons/pass.svg.import b/trimsock.gd/addons/vest/icons/pass.svg.import
index 03fc7b3..6acccf3 100644
--- a/trimsock.gd/addons/vest/icons/pass.svg.import
+++ b/trimsock.gd/addons/vest/icons/pass.svg.import
@@ -5,6 +5,7 @@ type="CompressedTexture2D"
uid="uid://bmqjme87oq3xx"
path="res://.godot/imported/pass.svg-527f701477d1793b7212aa92a3d53194.ctex"
metadata={
+"has_editor_variant": true,
"vram_texture": false
}
@@ -33,5 +34,5 @@ process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=2.0
-editor/scale_with_editor_scale=false
-editor/convert_colors_with_editor_theme=false
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/trimsock.gd/addons/vest/icons/refresh.svg.import b/trimsock.gd/addons/vest/icons/refresh.svg.import
index 4e8b6b3..bf5bf4e 100644
--- a/trimsock.gd/addons/vest/icons/refresh.svg.import
+++ b/trimsock.gd/addons/vest/icons/refresh.svg.import
@@ -5,6 +5,7 @@ type="CompressedTexture2D"
uid="uid://dt75n55vr4kq0"
path="res://.godot/imported/refresh.svg-08b8d1fd09107d0faa0c33acbadb6352.ctex"
metadata={
+"has_editor_variant": true,
"vram_texture": false
}
@@ -33,5 +34,5 @@ process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=2.0
-editor/scale_with_editor_scale=false
-editor/convert_colors_with_editor_theme=false
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/trimsock.gd/addons/vest/icons/run-save.svg.import b/trimsock.gd/addons/vest/icons/run-save.svg.import
index ca548dc..4d55d54 100644
--- a/trimsock.gd/addons/vest/icons/run-save.svg.import
+++ b/trimsock.gd/addons/vest/icons/run-save.svg.import
@@ -5,6 +5,7 @@ type="CompressedTexture2D"
uid="uid://c711af0y1s2ct"
path="res://.godot/imported/run-save.svg-284dc56c3fcbb3decae07a3263265580.ctex"
metadata={
+"has_editor_variant": true,
"vram_texture": false
}
@@ -33,5 +34,5 @@ process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=2.0
-editor/scale_with_editor_scale=false
-editor/convert_colors_with_editor_theme=false
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/trimsock.gd/addons/vest/icons/run.svg.import b/trimsock.gd/addons/vest/icons/run.svg.import
index 93ad9ff..ec7b6d6 100644
--- a/trimsock.gd/addons/vest/icons/run.svg.import
+++ b/trimsock.gd/addons/vest/icons/run.svg.import
@@ -5,6 +5,7 @@ type="CompressedTexture2D"
uid="uid://r4y6ihamgino"
path="res://.godot/imported/run.svg-b6532e07a4db7efc49be28be04360e08.ctex"
metadata={
+"has_editor_variant": true,
"vram_texture": false
}
@@ -33,5 +34,5 @@ process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=2.0
-editor/scale_with_editor_scale=false
-editor/convert_colors_with_editor_theme=false
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/trimsock.gd/addons/vest/icons/search.svg b/trimsock.gd/addons/vest/icons/search.svg
new file mode 100644
index 0000000..110a512
--- /dev/null
+++ b/trimsock.gd/addons/vest/icons/search.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/trimsock.gd/addons/vest/icons/search.svg.import b/trimsock.gd/addons/vest/icons/search.svg.import
new file mode 100644
index 0000000..99d1c51
--- /dev/null
+++ b/trimsock.gd/addons/vest/icons/search.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cl6im4a1uj6nq"
+path="res://.godot/imported/search.svg-f595f7d0e550dfff682417fb006673bc.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/vest/icons/search.svg"
+dest_files=["res://.godot/imported/search.svg-f595f7d0e550dfff682417fb006673bc.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=2.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/trimsock.gd/addons/vest/icons/skip.svg.import b/trimsock.gd/addons/vest/icons/skip.svg.import
index 1c928fd..972820e 100644
--- a/trimsock.gd/addons/vest/icons/skip.svg.import
+++ b/trimsock.gd/addons/vest/icons/skip.svg.import
@@ -5,6 +5,7 @@ type="CompressedTexture2D"
uid="uid://cx4htkmtgdk5l"
path="res://.godot/imported/skip.svg-05ed8ec36ac4803550bae54821df0a30.ctex"
metadata={
+"has_editor_variant": true,
"vram_texture": false
}
@@ -33,5 +34,5 @@ process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=2.0
-editor/scale_with_editor_scale=false
-editor/convert_colors_with_editor_theme=false
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/trimsock.gd/addons/vest/icons/spinner.svg b/trimsock.gd/addons/vest/icons/spinner.svg
new file mode 100644
index 0000000..f6f3d39
--- /dev/null
+++ b/trimsock.gd/addons/vest/icons/spinner.svg
@@ -0,0 +1,125 @@
+
+
+
+
diff --git a/trimsock.gd/addons/vest/icons/spinner.svg.import b/trimsock.gd/addons/vest/icons/spinner.svg.import
new file mode 100644
index 0000000..a0cdc23
--- /dev/null
+++ b/trimsock.gd/addons/vest/icons/spinner.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://csfyamlc15vp0"
+path="res://.godot/imported/spinner.svg-2fba1cd10beb909105907ca61911cf40.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/vest/icons/spinner.svg"
+dest_files=["res://.godot/imported/spinner.svg-2fba1cd10beb909105907ca61911cf40.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=2.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/trimsock.gd/addons/vest/icons/todo.svg.import b/trimsock.gd/addons/vest/icons/todo.svg.import
index 1d3aaae..38cd422 100644
--- a/trimsock.gd/addons/vest/icons/todo.svg.import
+++ b/trimsock.gd/addons/vest/icons/todo.svg.import
@@ -5,6 +5,7 @@ type="CompressedTexture2D"
uid="uid://meh1sny670to"
path="res://.godot/imported/todo.svg-2340992753f3f9d2c971234e5e126d95.ctex"
metadata={
+"has_editor_variant": true,
"vram_texture": false
}
@@ -33,5 +34,5 @@ process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=2.0
-editor/scale_with_editor_scale=false
-editor/convert_colors_with_editor_theme=false
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/trimsock.gd/addons/vest/icons/vest-icons.gd b/trimsock.gd/addons/vest/icons/vest-icons.gd
index fb4058e..a2ec9c8 100644
--- a/trimsock.gd/addons/vest/icons/vest-icons.gd
+++ b/trimsock.gd/addons/vest/icons/vest-icons.gd
@@ -20,3 +20,8 @@ const clear: Texture2D = preload("res://addons/vest/icons/clear.svg")
const jump_to: Texture2D = preload("res://addons/vest/icons/jump-to.svg")
const refresh: Texture2D = preload("res://addons/vest/icons/refresh.svg")
const lightbulb: Texture2D = preload("res://addons/vest/icons/lightbulb.svg")
+
+const visible: Texture2D = preload("res://addons/vest/icons/visibility.svg")
+const hidden: Texture2D = preload("res://addons/vest/icons/hidden.svg")
+const expand: Texture2D = preload("res://addons/vest/icons/expand.svg")
+const collapse: Texture2D = preload("res://addons/vest/icons/collapse.svg")
diff --git a/trimsock.gd/addons/vest/icons/visibility.svg b/trimsock.gd/addons/vest/icons/visibility.svg
new file mode 100644
index 0000000..768d6ee
--- /dev/null
+++ b/trimsock.gd/addons/vest/icons/visibility.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/trimsock.gd/addons/vest/icons/visibility.svg.import b/trimsock.gd/addons/vest/icons/visibility.svg.import
new file mode 100644
index 0000000..5977a86
--- /dev/null
+++ b/trimsock.gd/addons/vest/icons/visibility.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bpyua1ic4p47h"
+path="res://.godot/imported/visibility.svg-2226fcfdff15944d56e68e7df764b325.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/vest/icons/visibility.svg"
+dest_files=["res://.godot/imported/visibility.svg-2226fcfdff15944d56e68e7df764b325.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=2.0
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/trimsock.gd/addons/vest/icons/void.svg.import b/trimsock.gd/addons/vest/icons/void.svg.import
index a0c3658..59c1efb 100644
--- a/trimsock.gd/addons/vest/icons/void.svg.import
+++ b/trimsock.gd/addons/vest/icons/void.svg.import
@@ -5,6 +5,7 @@ type="CompressedTexture2D"
uid="uid://ci8hqm6meom3h"
path="res://.godot/imported/void.svg-dedfa2bc2d4e3cbf7106ebb34fd16279.ctex"
metadata={
+"has_editor_variant": true,
"vram_texture": false
}
@@ -33,5 +34,5 @@ process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=2.0
-editor/scale_with_editor_scale=false
-editor/convert_colors_with_editor_theme=false
+editor/scale_with_editor_scale=true
+editor/convert_colors_with_editor_theme=true
diff --git a/trimsock.gd/addons/vest/mocks/vest-mock-defs.gd b/trimsock.gd/addons/vest/mocks/vest-mock-defs.gd
index 791b6bb..c6d921a 100644
--- a/trimsock.gd/addons/vest/mocks/vest-mock-defs.gd
+++ b/trimsock.gd/addons/vest/mocks/vest-mock-defs.gd
@@ -35,7 +35,6 @@ class Answer:
if args.size() != expected_args.size():
return false
if args == expected_args:
- print("Arg match!")
return true
# Do a lenient check, so users don't trip on unexpected diffs, like
diff --git a/trimsock.gd/addons/vest/mocks/vest-mock-generator.gd b/trimsock.gd/addons/vest/mocks/vest-mock-generator.gd
index 11caa81..2f0a133 100644
--- a/trimsock.gd/addons/vest/mocks/vest-mock-generator.gd
+++ b/trimsock.gd/addons/vest/mocks/vest-mock-generator.gd
@@ -41,7 +41,7 @@ func generate_mock_source(script: Script) -> String:
mock_source.append(
("func %s(%s):\n" +
- "\treturn __vest_mock_handler._handle(%s, [%s])\n\n") %
+ "\treturn __vest_mock_handler._handle(self.%s, [%s])\n\n") %
[method_name, arg_def_string, method_name, arg_def_string]
)
diff --git a/trimsock.gd/addons/vest/plugin.cfg b/trimsock.gd/addons/vest/plugin.cfg
index 493868c..fae928a 100644
--- a/trimsock.gd/addons/vest/plugin.cfg
+++ b/trimsock.gd/addons/vest/plugin.cfg
@@ -3,5 +3,5 @@
name="vest"
description="A unit testing library for Godot"
author="Tamás Gálffy"
-version="1.3.3"
+version="1.10.2"
script="plugin.gd"
diff --git a/trimsock.gd/addons/vest/runner/vest-base-runner.gd b/trimsock.gd/addons/vest/runner/vest-base-runner.gd
index e9a7b7e..9bea773 100644
--- a/trimsock.gd/addons/vest/runner/vest-base-runner.gd
+++ b/trimsock.gd/addons/vest/runner/vest-base-runner.gd
@@ -1,19 +1,19 @@
extends RefCounted
-# TODO: Eventually support re-running single tests
+signal on_partial_result(result: VestResult.Suite)
-func run_script(_script: Script) -> VestResult.Suite:
+func run_script(_script: Script, _only_mode: int = Vest.__.ONLY_DEFAULT) -> VestResult.Suite:
# OVERRIDE
return VestResult.Suite.new()
-func run_glob(_p_glob: String) -> VestResult.Suite:
+func run_glob(_p_glob: String, _only_mode: int = Vest.__.ONLY_DEFAULT) -> VestResult.Suite:
# OVERRIDE
return VestResult.Suite.new()
-func run_script_at(path: String) -> VestResult.Suite:
+func run_script_at(path: String, only_mode: int = Vest.__.ONLY_DEFAULT) -> VestResult.Suite:
var test_script := load(path)
if not test_script or not test_script is Script:
return null
- return await run_script(test_script)
+ return await run_script(test_script, only_mode)
diff --git a/trimsock.gd/addons/vest/runner/vest-daemon-runner.gd b/trimsock.gd/addons/vest/runner/vest-daemon-runner.gd
index a3a2964..2f86160 100644
--- a/trimsock.gd/addons/vest/runner/vest-daemon-runner.gd
+++ b/trimsock.gd/addons/vest/runner/vest-daemon-runner.gd
@@ -15,18 +15,20 @@ func with_debug() -> VestDaemonRunner:
return self
## Run a test script
-func run_script(script: Script) -> VestResult.Suite:
+func run_script(script: Script, only_mode: int = Vest.__.ONLY_DEFAULT) -> VestResult.Suite:
var params := VestCLI.Params.new()
params.run_file = script.resource_path
+ params.only_mode = only_mode
return await _run_with_params(params)
## Run test scripts matching glob
## [br][br]
## See [method String.match]
-func run_glob(glob: String) -> VestResult.Suite:
+func run_glob(glob: String, only_mode: int = Vest.__.ONLY_DEFAULT) -> VestResult.Suite:
var params := VestCLI.Params.new()
params.run_glob = glob
+ params.only_mode = only_mode
return await _run_with_params(params)
@@ -53,14 +55,24 @@ func _run_with_params(params: VestCLI.Params) -> VestResult.Suite:
return null
_peer = _server.take_connection()
+ var results = null
- # Take results
- if await timeout.until(func(): return _peer.get_available_bytes() > 0) != OK:
- push_error("Didn't receive results in time! Available bytes: %d" % [_peer.get_available_bytes()])
- _stop()
- return null
+ while true:
+ await Vest.sleep()
+
+ _peer.poll()
+ if _peer.get_status() != StreamPeerTCP.STATUS_CONNECTED:
+ break
+
+ if _peer.get_available_bytes() <= 0:
+ # No data, wait some more
+ continue
+
+ var message = _peer.get_var(true)
+ if message is Dictionary:
+ results = message
+ on_partial_result.emit(VestResult.Suite._from_wire(results))
- var results = _peer.get_var(true)
_stop()
if results == null:
diff --git a/trimsock.gd/addons/vest/runner/vest-local-runner.gd b/trimsock.gd/addons/vest/runner/vest-local-runner.gd
index 40833e7..c827a44 100644
--- a/trimsock.gd/addons/vest/runner/vest-local-runner.gd
+++ b/trimsock.gd/addons/vest/runner/vest-local-runner.gd
@@ -1,58 +1,121 @@
extends "res://addons/vest/runner/vest-base-runner.gd"
class_name VestLocalRunner
+var _result_buffer: VestResult.Suite
+
## Run a test script
-func run_script(script: Script) -> VestResult.Suite:
+func run_script(script: Script, only_mode: int = Vest.__.ONLY_DEFAULT) -> VestResult.Suite:
+ var _result_buffer = VestResult.Suite.new()
if not script:
return null
var test_instance = script.new()
+ if not test_instance is VestTest:
+ test_instance.free()
+ return null
+ var test := test_instance as VestTest
+
+ var suite = await test._get_suite()
- var results: VestResult.Suite = null
- if test_instance is VestTest:
- await test_instance._begin(test_instance)
- var suite = await test_instance._get_suite()
- results = await _run_suite(suite, test_instance)
- await test_instance._finish(test_instance)
- test_instance.free()
+ var run_only := false
+ match only_mode:
+ Vest.__.ONLY_DISABLED: run_only = false
+ Vest.__.ONLY_AUTO: run_only = suite.has_only()
+ Vest.__.ONLY_ENABLED: run_only = true
- return results
+ await test._begin(test_instance)
+ _result_buffer = await _run_suite(_result_buffer, suite, test, run_only)
+ await test._finish(test)
+
+ test.free()
+
+ return _result_buffer
## Run test scripts matching glob
## [br][br]
## See [method String.match]
-func run_glob(glob: String) -> VestResult.Suite:
- var result := VestResult.Suite.new()
- result.suite = VestDefs.Suite.new()
- result.suite.name = "Glob suite \"%s\"" % [glob]
+func run_glob(glob: String, only_mode: int = Vest.__.ONLY_DEFAULT) -> VestResult.Suite:
+ _result_buffer = VestResult.Suite.new()
+ _result_buffer.suite = VestDefs.Suite.new()
+ _result_buffer.suite.name = "Glob suite \"%s\"" % [glob]
+
+ # Gather suites
+ var suites := [] as Array[VestDefs.Suite]
+ var test_instances := [] as Array[VestTest]
+ var has_only = false
for test_file in Vest.glob(glob):
- var suite_result := await run_script_at(test_file)
- if suite_result:
- result.subsuites.append(suite_result)
+ var test := _load_test(test_file)
+ if not test: continue
- return result
+ var subsuite := await test._get_suite()
+ test_instances.append(test)
+ suites.append(subsuite)
+ has_only = has_only or subsuite.has_only()
+
+ # Figure out only mode
+ var run_only := false
+ match only_mode:
+ Vest.__.ONLY_DISABLED: run_only = false
+ Vest.__.ONLY_AUTO: run_only = has_only
+ Vest.__.ONLY_ENABLED: run_only = true
+
+ # Run suites
+ for i in range(suites.size()):
+ var suite_result := VestResult.Suite.new()
+ _result_buffer.subsuites.append(suite_result)
+
+ var response := await _run_suite(suite_result, suites[i], test_instances[i], run_only)
+ if not response:
+ _result_buffer.subsuites.erase(suite_result)
+
+ # Cleanup
+ for test in test_instances:
+ test.free()
+
+ return _result_buffer
+
+func _load_test(path: String) -> VestTest:
+ var script := load(path)
+ if not script or not script is Script:
+ return null
+
+ var test_instance = (script as Script).new()
+ if not test_instance is VestTest:
+ return null
+
+ return test_instance as VestTest
+
+func _run_case(case: VestDefs.Case, test_instance: VestTest, run_only: bool, is_parent_only: bool = false) -> VestResult.Case:
+ if run_only and not case.is_only and not is_parent_only:
+ return null
-func _run_case(case: VestDefs.Case, test_instance: VestTest) -> VestResult.Case:
await test_instance._begin(case)
await case.callback.call()
await test_instance._finish(case)
+ on_partial_result.emit(_result_buffer)
+
return test_instance._get_result()
-func _run_suite(suite: VestDefs.Suite, test_instance: VestTest) -> VestResult.Suite:
- var result := VestResult.Suite.new()
+func _run_suite(result: VestResult.Suite, suite: VestDefs.Suite, test_instance: VestTest, run_only: bool, is_parent_only: bool = false) -> VestResult.Suite:
+ if run_only and not suite.has_only() and not is_parent_only:
+ return null
+
result.suite = suite
await test_instance._begin(suite)
for subsuite in suite.suites:
- var suite_result := await _run_suite(subsuite, test_instance)
- result.subsuites.append(suite_result)
+ var suite_result := VestResult.Suite.new()
+ suite_result = await _run_suite(suite_result, subsuite, test_instance, run_only, is_parent_only or suite.is_only)
+ if suite_result != null:
+ result.subsuites.append(suite_result)
for case in suite.cases:
- var case_result := await _run_case(case, test_instance)
- result.cases.append(case_result)
+ var case_result := await _run_case(case, test_instance, run_only, is_parent_only or suite.is_only)
+ if case_result != null:
+ result.cases.append(case_result)
await test_instance._finish(suite)
diff --git a/trimsock.gd/addons/vest/tap-reporter.gd b/trimsock.gd/addons/vest/tap-reporter.gd
index d7c281d..059ef69 100644
--- a/trimsock.gd/addons/vest/tap-reporter.gd
+++ b/trimsock.gd/addons/vest/tap-reporter.gd
@@ -19,7 +19,7 @@ static func report(suite: VestResult.Suite) -> String:
static func _report_suite(suite: VestResult.Suite, lines: PackedStringArray, indent: int = 0):
var indent_prefix := " ".repeat(indent)
- var test_count := suite.size()
+ var test_count := suite.plan_size()
var test_id := 1
lines.append(indent_prefix + "1..%d" % [test_count])
@@ -55,7 +55,10 @@ static func _report_case(test_id: int, case: VestResult.Case, lines: PackedStrin
yaml_data["severity"] = "fail"
yaml_data["assert_source"] = case.assert_file
yaml_data["assert_line"] = case.assert_line
- if case.message: yaml_data["message"] = case.message
+ if not case.messages.is_empty():
+ yaml_data["message"] = case.messages[0]
+ if case.messages.size() > 1:
+ yaml_data["messages"] = case.messages
if case.data: yaml_data["data"] = case.data
if not yaml_data.is_empty():
diff --git a/trimsock.gd/addons/vest/test/mixins/gather-suite-mixin.gd b/trimsock.gd/addons/vest/test/mixins/gather-suite-mixin.gd
index cfc1440..528e8e7 100644
--- a/trimsock.gd/addons/vest/test/mixins/gather-suite-mixin.gd
+++ b/trimsock.gd/addons/vest/test/mixins/gather-suite-mixin.gd
@@ -56,9 +56,18 @@ func _get_suite() -> VestDefs.Suite:
await call(method["name"])
for method in case_methods:
- test(method["name"].trim_prefix("test").capitalize(), func(): await call(method["name"]))
+ var method_name := method["name"] as String
+ var test_name := method_name.trim_prefix("test").trim_suffix("__only").capitalize()
+ var callback := func(): await call(method["name"])
+ var is_only := _is_only(method_name)
+
+ test(test_name, callback, is_only, method_name)
for method in parametric_methods:
+ var method_name := method["name"] as String
+ var test_name := method_name.trim_prefix("test").trim_suffix("__only").capitalize()
+ var is_only := _is_only(method_name)
+
var param_provider_name := method["default_args"][0] as String
if not has_method(param_provider_name):
push_warning(
@@ -75,7 +84,12 @@ func _get_suite() -> VestDefs.Suite:
for i in range(params.size()):
test(
- "%s#%d %s" % [method["name"].trim_prefix("test").capitalize(), i+1, params[i]],
- func(): await callv(method["name"], params[i])
+ "%s#%d %s" % [test_name, i+1, params[i]],
+ func(): await callv(method["name"], params[i]),
+ is_only,
+ method_name
)
)
+
+func _is_only(name: String) -> bool:
+ return name.ends_with("__only")
diff --git a/trimsock.gd/addons/vest/test/vest-test-base.gd b/trimsock.gd/addons/vest/test/vest-test-base.gd
index 9ee784a..0445a7b 100644
--- a/trimsock.gd/addons/vest/test/vest-test-base.gd
+++ b/trimsock.gd/addons/vest/test/vest-test-base.gd
@@ -3,6 +3,9 @@ extends Object
var _define_stack: Array[VestDefs.Suite] = []
var _result: VestResult.Case
+var _expected_count := 0
+var _actual_count := 0
+
signal on_begin()
signal on_suite_begin(suite: VestDefs.Suite)
signal on_case_begin(case: VestDefs.Case)
@@ -10,9 +13,10 @@ signal on_case_finish(case: VestDefs.Case)
signal on_suite_finish(case: VestDefs.Case)
signal on_finish()
-func define(name: String, callback: Callable) -> VestDefs.Suite:
+func define(name: String, callback: Callable, is_only: bool = false) -> VestDefs.Suite:
var suite = VestDefs.Suite.new()
suite.name = name
+ suite.is_only = is_only
_define_stack.push_back(suite)
var userland_loc := _find_userland_stack_location()
@@ -27,10 +31,15 @@ func define(name: String, callback: Callable) -> VestDefs.Suite:
return suite
-func test(description: String, callback: Callable) -> void:
+func define_only(name: String, callback: Callable) -> VestDefs.Suite:
+ return await define(name, callback, true)
+
+func test(description: String, callback: Callable, is_only: bool = false, method_name: String = "") -> void:
var case_def := VestDefs.Case.new()
case_def.description = description
+ case_def.is_only = is_only
case_def.callback = callback
+ case_def.method_name = method_name
var userland_loc := _find_userland_stack_location()
case_def.definition_file = userland_loc[0]
@@ -38,6 +47,9 @@ func test(description: String, callback: Callable) -> void:
_define_stack.back().cases.push_back(case_def)
+func test_only(description: String, callback: Callable, method_name: String = "") -> void:
+ await test(description, callback, true, method_name)
+
func benchmark(name: String, callback: Callable) -> VestDefs.Benchmark:
var result := VestDefs.Benchmark.new()
result.name = name
@@ -79,13 +91,39 @@ func _init():
pass
func _with_result(status: int, message: String, data: Dictionary):
+ if message:
+ _result.messages.append(message)
+
+ # Smartly gather "got" and "expected" data
+ # - If there's just one assert that sends these, have the values as-is
+ # - If multiple asserts send them, gather them into arrays for the user to check
+ if data.has("got"):
+ if _actual_count == 0:
+ _result.data["got"] = data["got"]
+ elif _actual_count == 1:
+ _result.data["got"] = [_result.data["got"], data["got"]]
+ elif _actual_count > 1:
+ _result.data["got"].append(data["got"])
+ data.erase("got")
+ _actual_count += 1
+
+ if data.has("expect"):
+ if _expected_count == 0:
+ _result.data["expect"] = data["expect"]
+ elif _expected_count == 1:
+ _result.data["expect"] = [_result.data["expect"], data["expect"]]
+ elif _expected_count > 1:
+ _result.data["expect"].append(data["expect"])
+ data.erase("expect")
+ _expected_count += 1
+
+ _result.data.merge(data, true)
+
if _result.status != VestResult.TEST_VOID and status == VestResult.TEST_PASS:
# Test already failed, don't override with PASS
return
_result.status = status
- _result.message = message
- _result.data.merge(data, true)
var userland_loc := _find_userland_stack_location()
_result.assert_file = userland_loc[0]
@@ -114,6 +152,9 @@ func _finish(what: Object):
await _emit_async(on_suite_finish, [what])
await after_suite(what)
elif what is VestDefs.Case:
+ _actual_count = 0
+ _expected_count = 0
+
await _emit_async(on_case_finish, [what])
await after_case(what)
else:
diff --git a/trimsock.gd/addons/vest/test/vest-test.gd b/trimsock.gd/addons/vest/test/vest-test.gd
index e8b1913..9abc9d0 100644
--- a/trimsock.gd/addons/vest/test/vest-test.gd
+++ b/trimsock.gd/addons/vest/test/vest-test.gd
@@ -6,4 +6,4 @@ extends "res://addons/vest/_generated-mixins/6-3c4a4dd7.gd"
class_name VestTest
func __get_vest_mixins() -> Array:
- return []
\ No newline at end of file
+ return []
diff --git a/trimsock.gd/addons/vest/ui/results-panel.gd b/trimsock.gd/addons/vest/ui/results-panel.gd
new file mode 100644
index 0000000..dfc0736
--- /dev/null
+++ b/trimsock.gd/addons/vest/ui/results-panel.gd
@@ -0,0 +1,292 @@
+@tool
+extends Panel
+
+var visibility_popup: VestUI.VisibilityPopup
+
+@onready var _tree := %Tree as Tree
+@onready var _spinner := %Spinner as Control
+@onready var _spinner_icon := %"Spinner Icon" as TextureRect
+@onready var _spinner_label := %"Spinner Label" as Label
+@onready var _animation_player := $PanelContainer/Spinner/AnimationPlayer as AnimationPlayer
+
+var _results: VestResult.Suite = null
+var _render_results: VestResult.Suite = null
+var _search_string: String = ""
+
+signal on_collapse_changed()
+
+func get_results() -> VestResult.Suite:
+ return _results
+
+func set_results(results: VestResult.Suite) -> void:
+ if _results != results:
+ _results = results
+ _render()
+
+func get_search_string() -> String:
+ return _search_string
+
+func set_search_string(search_string: String) -> void:
+ if _search_string != search_string:
+ _search_string = search_string
+ _render()
+
+func clear() -> void:
+ set_results(null)
+
+func set_spinner(text: String, icon: Texture2D = null, animated: bool = true) -> void:
+ clear()
+ _spinner_icon.texture = icon
+ _spinner_label.text = text
+ _spinner.show()
+ if animated:
+ _animation_player.play("spin")
+ else:
+ _animation_player.stop()
+
+func collapse() -> void:
+ var root := _tree.get_root()
+ if not root: return
+
+ for item in root.get_children():
+ item.set_collapsed_recursive(true)
+
+func expand() -> void:
+ var root := _tree.get_root()
+ if not root: return
+
+ for item in root.get_children():
+ item.set_collapsed_recursive(false)
+
+func toggle_collapsed() -> void:
+ if is_any_collapsed():
+ expand()
+ else:
+ collapse()
+
+func is_any_collapsed() -> bool:
+ var at := _tree.get_root()
+ var queue := [] as Array[TreeItem]
+
+ while at:
+ queue.append_array(at.get_children())
+
+ if at.collapsed:
+ return true
+ at = queue.pop_back()
+
+ return false
+
+func _ready():
+ _tree.item_collapsed.connect(func(_item):
+ on_collapse_changed.emit()
+ )
+
+func _clear():
+ _tree.clear()
+ for connection in _tree.item_activated.get_connections():
+ connection["signal"].disconnect(connection["callable"])
+ _spinner.hide()
+
+func _render():
+ _clear()
+ if _results != null:
+ _render_results = VestResult.Suite._from_wire(_results._to_wire()) # HACK: Duplicate
+ _filter_visibility(_render_results)
+ _filter_search(_render_results, _search_string)
+ _render_result(_render_results, _tree)
+
+func _filter_visibility(results: VestResult.Suite) -> void:
+ var cases_to_remove := []
+ var suites_to_remove := []
+
+ for case in results.cases:
+ if not _check_visibility(case):
+ cases_to_remove.append(case)
+
+ for subsuite in results.subsuites:
+ if not _check_visibility(subsuite):
+ suites_to_remove.append(subsuite)
+ else:
+ _filter_visibility(subsuite)
+
+ for case in cases_to_remove:
+ results.cases.erase(case)
+ for subsuite in suites_to_remove:
+ results.subsuites.erase(subsuite)
+
+func _filter_search(results: VestResult.Suite, needle: String) -> void:
+ if not needle:
+ # Search string empty, do nothing
+ return
+
+ var scores := {}
+ var parents := {}
+ var at := results
+ var queue: Array[VestResult.Suite] = []
+ var leaves: Array[VestResult.Suite] = []
+
+ # Calculate scores
+ while at:
+ for subsuite in at.subsuites:
+ queue.append(subsuite)
+ parents[subsuite] = at
+
+ if at.subsuites.is_empty():
+ leaves.append(at)
+
+ scores[at] = VestUI.fuzzy_score(needle, at.suite.name)
+ for case in at.cases:
+ scores[case] = maxf(
+ VestUI.fuzzy_score(needle, case.case.description),
+ VestUI.fuzzy_score(needle, case.case.method_name + "()")
+ )
+
+ at = queue.pop_back() as VestResult.Suite
+
+ # Propagate best scores from leaves
+ for leaf in leaves:
+ at = leaf
+
+ # Calculate best score for leaf
+ var best_score := scores[at] as float
+ for case in at.cases:
+ best_score = maxf(best_score, scores[case])
+
+ # Propagate upwards in tree
+ while at:
+ scores[at] = maxf(scores[at], best_score)
+ best_score = maxf(best_score, scores[at])
+ at = parents.get(at, null)
+
+ # Remove results that don't match the search string
+ at = results
+ queue.clear()
+ while at:
+ at.cases.sort_custom(func(a, b): return scores[a] > scores[b])
+ at.subsuites.sort_custom(func(a, b): return scores[a] > scores[b])
+
+ queue.append_array(at.subsuites)
+ at = queue.pop_back()
+
+func _check_visibility(what: Variant) -> bool:
+ if what is VestResult.Case:
+ var case := what as VestResult.Case
+ return visibility_popup.get_visibility_for(case.status)
+ elif what is VestResult.Suite:
+ var suite := what as VestResult.Suite
+ for status in suite.get_unique_statuses():
+ if visibility_popup.get_visibility_for(status):
+ return true
+ return false
+ else:
+ push_warning("Checking visibility for unknown item: %s" % [what])
+ return true
+
+func _render_result(what: Object, tree: Tree, parent: TreeItem = null):
+ if what is VestResult.Suite:
+ var item := tree.create_item(parent)
+ item.set_text(0, what.suite.name)
+ item.set_text(1, what.get_aggregate_status_string().capitalize())
+
+ item.set_icon(0, VestUI.get_status_icon(what))
+ item.set_icon_max_width(0, VestUI.get_icon_size())
+
+ tree.item_activated.connect(func():
+ if tree.get_selected() == item:
+ _navigate(what.suite.definition_file, what.suite.definition_line)
+ )
+
+ for subsuite in what.subsuites:
+ _render_result(subsuite, tree, item)
+ for case in what.cases:
+ _render_result(case, tree, item)
+ elif what is VestResult.Case:
+ var item := tree.create_item(parent)
+ item.set_text(0, what.case.description)
+ item.set_text(1, what.get_status_string().capitalize())
+ item.collapsed = what.status == VestResult.TEST_PASS
+
+ item.set_icon(0, VestUI.get_status_icon(what))
+ item.set_icon_max_width(0, VestUI.get_icon_size())
+
+ _render_data(what, tree, item)
+
+ tree.item_activated.connect(func():
+ if tree.get_selected() == item:
+ _navigate(what.case.definition_file, what.case.definition_line)
+ )
+ else:
+ push_error("Rendering unknown object: %s" % [what])
+
+func _render_data(case: VestResult.Case, tree: Tree, parent: TreeItem):
+ var data := case.data.duplicate()
+
+ for message in case.messages:
+ var item := tree.create_item(parent)
+ item.set_text(0, message)
+
+ tree.item_activated.connect(func():
+ if tree.get_selected() == item:
+ # TODO: popup_dialog()
+ add_child(VestMessagePopup.of(message).window)
+ )
+
+ if data == null or data.is_empty():
+ return
+
+ if data.has("messages"):
+ var header_item := tree.create_item(parent)
+ header_item.set_text(0, "Messages")
+
+ for message in data["messages"]:
+ tree.create_item(header_item).set_text(0, message)
+
+ data.erase("messages")
+
+ if data.has("benchmarks"):
+ var header_item := tree.create_item(parent)
+ header_item.set_text(0, "Benchmarks")
+
+ for benchmark in data["benchmarks"]:
+ var benchmark_item = tree.create_item(header_item)
+ benchmark_item.set_text(0, benchmark["name"])
+ if benchmark.has("duration"): benchmark_item.set_text(1, benchmark["duration"])
+
+ for measurement in benchmark.keys():
+ if measurement == "name": continue
+
+ var measurement_item := tree.create_item(benchmark_item)
+ measurement_item.set_text(0, str(measurement).capitalize())
+ measurement_item.set_text(1, str(benchmark[measurement]))
+
+ data.erase("benchmarks")
+
+ if data.has("expect") and data.has("got"):
+ var header_item := tree.create_item(parent)
+ header_item.set_text(0, "Got:")
+ header_item.set_text(1, "Expected:")
+
+ var got_string := JSON.stringify(data["got"], " ")
+ var expect_string := JSON.stringify(data["expect"], " ")
+
+ var comparison_item := tree.create_item(header_item)
+ comparison_item.set_text(0, got_string)
+ comparison_item.set_text(1, expect_string)
+
+ tree.item_activated.connect(func():
+ # TODO: popup_dialog()
+ if tree.get_selected() in [header_item, comparison_item]:
+ add_child(VestComparisonPopup.of(expect_string, got_string).window)
+ )
+
+ data.erase("got")
+ data.erase("expect")
+
+ for key in data:
+ var item := tree.create_item(parent)
+ item.set_text(0, var_to_str(key))
+ item.set_text(1, var_to_str(data[key]))
+
+func _navigate(file: String, line: int):
+ Vest._get_editor_interface().edit_script(load(file), line)
diff --git a/trimsock.gd/addons/vest/ui/results-panel.gd.uid b/trimsock.gd/addons/vest/ui/results-panel.gd.uid
new file mode 100644
index 0000000..08ab771
--- /dev/null
+++ b/trimsock.gd/addons/vest/ui/results-panel.gd.uid
@@ -0,0 +1 @@
+uid://brlr0bv7le1uq
diff --git a/trimsock.gd/addons/vest/ui/results-panel.tscn b/trimsock.gd/addons/vest/ui/results-panel.tscn
new file mode 100644
index 0000000..464f302
--- /dev/null
+++ b/trimsock.gd/addons/vest/ui/results-panel.tscn
@@ -0,0 +1,76 @@
+[gd_scene load_steps=5 format=3 uid="uid://lqudrgj64q3x"]
+
+[ext_resource type="Script" path="res://addons/vest/ui/results-panel.gd" id="1_ujqyc"]
+[ext_resource type="Texture2D" uid="uid://dwhlyf5eyiect" path="res://addons/vest/icons/debug.svg" id="2_1y2jb"]
+
+[sub_resource type="Animation" id="Animation_wkibd"]
+resource_name = "spin"
+loop_mode = 1
+step = 0.0416667
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Spinner Icon:modulate")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.5, 1),
+"transitions": PackedFloat32Array(-2, -2, -2),
+"update": 0,
+"values": [Color(1, 1, 1, 1), Color(1, 1, 1, 0.501961), Color(1, 1, 1, 1)]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_ne2nn"]
+_data = {
+"spin": SubResource("Animation_wkibd")
+}
+
+[node name="Results Panel" type="Panel"]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_vertical = 3
+script = ExtResource("1_ujqyc")
+
+[node name="PanelContainer" type="PanelContainer" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="Tree" type="Tree" parent="PanelContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+columns = 2
+hide_root = true
+
+[node name="Spinner" type="HBoxContainer" parent="PanelContainer"]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+size_flags_horizontal = 4
+size_flags_vertical = 4
+
+[node name="Spinner Icon" type="TextureRect" parent="PanelContainer/Spinner"]
+unique_name_in_owner = true
+layout_mode = 2
+texture = ExtResource("2_1y2jb")
+expand_mode = 3
+stretch_mode = 5
+
+[node name="Spinner Label" type="Label" parent="PanelContainer/Spinner"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "Waiting for results..."
+
+[node name="AnimationPlayer" type="AnimationPlayer" parent="PanelContainer/Spinner"]
+autoplay = "spin"
+libraries = {
+"": SubResource("AnimationLibrary_ne2nn")
+}
diff --git a/trimsock.gd/addons/vest/ui/vest-ui.gd b/trimsock.gd/addons/vest/ui/vest-ui.gd
index edaf251..60263a7 100644
--- a/trimsock.gd/addons/vest/ui/vest-ui.gd
+++ b/trimsock.gd/addons/vest/ui/vest-ui.gd
@@ -2,17 +2,31 @@
extends Control
class_name VestUI
+const VisibilityPopup := preload("res://addons/vest/ui/visibility-popup.gd")
+const ResultsPanel := preload("res://addons/vest/ui/results-panel.gd")
+
@onready var run_all_button := %"Run All Button" as Button
@onready var debug_button := %"Debug Button" as Button
@onready var run_on_save_button := %"Run on Save Button" as Button
+@onready var filter_results_button := %"Filter Results Button" as Button
+@onready var visibility_popup := %"Visibility Popup" as VisibilityPopup
@onready var clear_button := %"Clear Button" as Button
+@onready var expand_toggle_button := %"Expand Toggle Button" as Button
+@onready var search_button := %"Search Button" as Button
+@onready var search_input := %"Search Input" as LineEdit
+
@onready var refresh_mixins_button := %"Refresh Mixins Button" as Button
-@onready var results_tree := %"Results Tree" as Tree
+@onready var results_panel := %"Results Panel" as ResultsPanel
@onready var summary_label := %"Tests Summary Label" as Label
@onready var summary_icon := %"Test Summary Icon" as TextureRect
@onready var glob_line_edit := %"Glob LineEdit" as LineEdit
+@onready var run_summary := %"Run Summary" as Control
+@onready var progress_indicator := %"Progress Indicator" as Control
+@onready var progress_animator := $"VBoxContainer/Bottom Line/Progress Indicator/Control/AnimationPlayer" as AnimationPlayer
+
var _run_on_save: bool = false
+var _results: VestResult.Suite = null
static var _icon_size := 16
static var _instance: VestUI
@@ -33,13 +47,18 @@ func handle_resource_saved(resource: Resource):
func run_all(is_debug: bool = false):
Vest._register_scene_tree(get_tree())
var runner := VestDaemonRunner.new()
+ runner.on_partial_result.connect(func(results):
+ results_panel.set_results(results)
+ )
var test_glob := glob_line_edit.text
Vest.__.LocalSettings.test_glob = test_glob
Vest.__.LocalSettings.flush()
- clear_results()
- _set_placeholder_text("Waiting for results...")
+ results_panel.set_spinner("Waiting for results...", Vest.Icons.debug)
+ progress_indicator.show()
+ progress_animator.play("spin")
+ run_summary.hide()
var test_start := Vest.time()
var results: VestResult.Suite
@@ -53,19 +72,23 @@ func run_all(is_debug: bool = false):
# Render individual results
ingest_results(results, test_duration)
-func run_script(script: Script, is_debug: bool = false) -> void:
+func run_script(script: Script, is_debug: bool = false, only_mode: int = Vest.__.ONLY_AUTO) -> void:
+ if not get_tree():
+ push_warning("UI has no tree!")
Vest._register_scene_tree(get_tree())
var runner := VestDaemonRunner.new()
- clear_results()
- _set_placeholder_text("Waiting for results...")
+ results_panel.set_spinner("Waiting for results...", Vest.Icons.debug)
+ progress_indicator.show()
+ progress_animator.play("spin")
+ run_summary.hide()
var test_start := Vest.time()
var results: VestResult.Suite
if not is_debug:
- results = await runner.run_script(script)
+ results = await runner.run_script(script, only_mode)
else:
- results = await runner.with_debug().run_script(script)
+ results = await runner.with_debug().run_script(script, only_mode)
var test_duration := Vest.time() - test_start
@@ -74,21 +97,23 @@ func run_script(script: Script, is_debug: bool = false) -> void:
func ingest_results(results: VestResult.Suite, duration: float = -1.) -> void:
clear_results()
+ _results = results
+
if results:
- _render_result(results, results_tree)
+ results_panel.set_results(results)
_render_summary(results, duration)
else:
- _set_placeholder_text("Test run failed!")
+ results_panel.set_spinner("Test run failed!", Vest.Icons.result_fail, false)
func clear_results():
- results_tree.clear()
- for connection in results_tree.item_activated.get_connections():
- connection["signal"].disconnect(connection["callable"])
-
+ results_panel.clear()
summary_label.text = ""
summary_icon.visible = false
func _ready():
+ _icon_size = int(16. * Vest._get_editor_scale())
+ results_panel.visibility_popup = visibility_popup
+
run_all_button.pressed.connect(run_all)
run_on_save_button.toggled.connect(func(toggled):
_run_on_save = toggled
@@ -103,134 +128,63 @@ func _ready():
debug_button.pressed.connect(func(): run_all(true))
- _icon_size = int(16. * Vest._get_editor_interface().get_editor_scale())
- _instance = self
+ filter_results_button.pressed.connect(func():
+ visibility_popup.position = filter_results_button.get_screen_position() + Vector2.RIGHT * filter_results_button.size.x
+ visibility_popup.show()
+ )
-func _render_result(what: Object, tree: Tree, parent: TreeItem = null):
- if what is VestResult.Suite:
- var item := tree.create_item(parent)
- item.set_text(0, what.suite.name)
- item.set_text(1, what.get_aggregate_status_string().capitalize())
-
- item.set_icon(0, _get_status_icon(what))
- item.set_icon_max_width(0, VestUI.get_icon_size())
-
- tree.item_activated.connect(func():
- if tree.get_selected() == item:
- _navigate(what.suite.definition_file, what.suite.definition_line)
- )
-
- for subsuite in what.subsuites:
- _render_result(subsuite, tree, item)
- for case in what.cases:
- _render_result(case, tree, item)
- elif what is VestResult.Case:
- var item := tree.create_item(parent)
- item.set_text(0, what.case.description)
- item.set_text(1, what.get_status_string().capitalize())
- item.collapsed = what.status == VestResult.TEST_PASS
+ visibility_popup.on_change.connect(func():
+ clear_results()
+ results_panel.set_results(_results)
- item.set_icon(0, _get_status_icon(what))
- item.set_icon_max_width(0, VestUI.get_icon_size())
+ if visibility_popup.is_empty():
+ filter_results_button.icon = Vest.Icons.hidden
+ else:
+ filter_results_button.icon = Vest.Icons.visible
+ )
- _render_data(what, tree, item)
+ results_panel.on_collapse_changed.connect(func():
+ if (results_panel.is_any_collapsed()):
+ expand_toggle_button.icon = Vest.Icons.expand
+ else:
+ expand_toggle_button.icon = Vest.Icons.collapse
+ )
- tree.item_activated.connect(func():
- if tree.get_selected() == item:
- _navigate(what.case.definition_file, what.case.definition_line)
- )
- else:
- push_error("Rendering unknown object: %s" % [what])
+ expand_toggle_button.pressed.connect(func():
+ results_panel.toggle_collapsed()
+ )
+
+ search_button.pressed.connect(func():
+ search_input.show()
+ search_input.grab_focus()
+ )
+
+ search_input.focus_exited.connect(func():
+ if not search_input.text and get_viewport().gui_get_focus_owner() != search_button:
+ search_input.hide()
+ , CONNECT_DEFERRED)
+
+ search_input.text_changed.connect(func(text: String):
+ results_panel.set_search_string(text)
+ )
+
+ _instance = self
func _render_summary(results: VestResult.Suite, test_duration: float):
+ progress_indicator.hide()
+ run_summary.show()
+
if test_duration > 0:
- summary_label.text = "Ran %d tests in %.2fms" % [results.size(), test_duration * 1000.]
+ summary_label.text = "Ran %d tests in %s" % [results.size(), VestUI.format_duration(test_duration)]
else:
summary_label.text = "Ran %d tests" % [results.size()]
summary_icon.visible = true
- summary_icon.texture = _get_status_icon(results)
+ summary_icon.texture = VestUI.get_status_icon(results)
summary_icon.custom_minimum_size = Vector2i.ONE * VestUI.get_icon_size() # TODO: Check
-func _render_data(case: VestResult.Case, tree: Tree, parent: TreeItem):
- var data := case.data.duplicate()
-
- if case.message:
- var item := tree.create_item(parent)
- item.set_text(0, case.message)
-
- tree.item_activated.connect(func():
- if tree.get_selected() == item:
- # TODO: popup_dialog()
- add_child(VestMessagePopup.of(case.message).window)
- )
-
- if data == null or data.is_empty():
- return
-
- if data.has("messages"):
- var header_item := tree.create_item(parent)
- header_item.set_text(0, "Messages")
-
- for message in data["messages"]:
- tree.create_item(header_item).set_text(0, message)
-
- data.erase("messages")
-
- if data.has("benchmarks"):
- var header_item := tree.create_item(parent)
- header_item.set_text(0, "Benchmarks")
-
- for benchmark in data["benchmarks"]:
- var benchmark_item = tree.create_item(header_item)
- benchmark_item.set_text(0, benchmark["name"])
- if benchmark.has("duration"): benchmark_item.set_text(1, benchmark["duration"])
-
- for measurement in benchmark.keys():
- if measurement == "name": continue
-
- var measurement_item := tree.create_item(benchmark_item)
- measurement_item.set_text(0, str(measurement).capitalize())
- measurement_item.set_text(1, str(benchmark[measurement]))
-
- data.erase("benchmarks")
-
- if data.has("expect") and data.has("got"):
- var header_item := tree.create_item(parent)
- header_item.set_text(0, "Got:")
- header_item.set_text(1, "Expected:")
-
- var got_string := JSON.stringify(data["got"], " ")
- var expect_string := JSON.stringify(data["expect"], " ")
-
- var comparison_item := tree.create_item(header_item)
- comparison_item.set_text(0, got_string)
- comparison_item.set_text(1, expect_string)
-
- tree.item_activated.connect(func():
- # TODO: popup_dialog()
- if tree.get_selected() in [header_item, comparison_item]:
- add_child(VestComparisonPopup.of(expect_string, got_string).window)
- )
-
- data.erase("got")
- data.erase("expect")
-
- for key in data:
- var item := tree.create_item(parent)
- item.set_text(0, var_to_str(key))
- item.set_text(1, var_to_str(data[key]))
-
-func _set_placeholder_text(text: String):
- results_tree.clear()
- var placeholder_root := results_tree.create_item()
- results_tree.create_item(placeholder_root).set_text(0, text)
-
-func _navigate(file: String, line: int):
- Vest._get_editor_interface().edit_script(load(file), line)
-
-func _get_status_icon(what: Variant) -> Texture2D:
+static func get_status_icon(what: Variant) -> Texture2D:
if what is VestResult.Suite:
- return _get_status_icon(what.get_aggregate_status())
+ return get_status_icon(what.get_aggregate_status())
elif what is VestResult.Case:
if what.data.has("benchmarks"):
if what.status == VestResult.TEST_FAIL:
@@ -238,7 +192,7 @@ func _get_status_icon(what: Variant) -> Texture2D:
else:
return Vest.Icons.benchmark
else:
- return _get_status_icon(what.status)
+ return get_status_icon(what.status)
elif what is int:
match(what):
VestResult.TEST_VOID: return Vest.Icons.result_void
@@ -247,3 +201,25 @@ func _get_status_icon(what: Variant) -> Texture2D:
VestResult.TEST_FAIL: return Vest.Icons.result_fail
VestResult.TEST_PASS: return Vest.Icons.result_pass
return null
+
+static func format_duration(duration: float) -> String:
+ if duration > 60.:
+ return "%.2fmin" % duration
+ elif duration > 1.:
+ return "%.2fs" % duration
+ elif duration > 0.001:
+ return "%.2fms" % [duration * 1000.]
+ else:
+ return "%.2fµs" % [duration * 1000_000.0]
+
+static func fuzzy_score(needle: String, haystack: String) -> float:
+ var ineedle := needle.to_lower()
+ var ihaystack := haystack.to_lower()
+ return ineedle.similarity(ihaystack) + float(ineedle.is_subsequence_of(ihaystack))
+
+static func fuzzy_match(needle: String, haystack: String) -> bool:
+ return fuzzy_score(needle, haystack) > 0.0
+
+static func fuzzy_sorter(needle: String) -> Callable:
+ return func(a, b):
+ return fuzzy_score(needle, a) < fuzzy_score(needle, b)
diff --git a/trimsock.gd/addons/vest/ui/vest-ui.tscn b/trimsock.gd/addons/vest/ui/vest-ui.tscn
index 7b1ee96..c9247c2 100644
--- a/trimsock.gd/addons/vest/ui/vest-ui.tscn
+++ b/trimsock.gd/addons/vest/ui/vest-ui.tscn
@@ -1,12 +1,40 @@
-[gd_scene load_steps=8 format=3 uid="uid://bp8g7j7774mwi"]
+[gd_scene load_steps=16 format=3 uid="uid://bp8g7j7774mwi"]
-[ext_resource type="Script" uid="uid://i36by1fk3oss" path="res://addons/vest/ui/vest-ui.gd" id="1_cct3x"]
+[ext_resource type="Script" path="res://addons/vest/ui/vest-ui.gd" id="1_cct3x"]
[ext_resource type="Texture2D" uid="uid://r4y6ihamgino" path="res://addons/vest/icons/run.svg" id="2_q3lni"]
+[ext_resource type="PackedScene" uid="uid://lqudrgj64q3x" path="res://addons/vest/ui/results-panel.tscn" id="2_r41u0"]
[ext_resource type="Texture2D" uid="uid://dwhlyf5eyiect" path="res://addons/vest/icons/debug.svg" id="3_mcbjt"]
[ext_resource type="Texture2D" uid="uid://ctdipwc8sklwo" path="res://addons/vest/icons/clear.svg" id="4_rjham"]
[ext_resource type="Texture2D" uid="uid://dt75n55vr4kq0" path="res://addons/vest/icons/refresh.svg" id="5_k7hxl"]
[ext_resource type="Texture2D" uid="uid://bmqjme87oq3xx" path="res://addons/vest/icons/pass.svg" id="6_mef67"]
[ext_resource type="Texture2D" uid="uid://c711af0y1s2ct" path="res://addons/vest/icons/run-save.svg" id="6_wy3v7"]
+[ext_resource type="Texture2D" uid="uid://bpyua1ic4p47h" path="res://addons/vest/icons/visibility.svg" id="6_xkbs5"]
+[ext_resource type="Texture2D" uid="uid://sx0233b14cll" path="res://addons/vest/icons/expand.svg" id="9_hg4sy"]
+[ext_resource type="Texture2D" uid="uid://cl6im4a1uj6nq" path="res://addons/vest/icons/search.svg" id="10_2uogy"]
+[ext_resource type="Texture2D" uid="uid://csfyamlc15vp0" path="res://addons/vest/icons/spinner.svg" id="10_7lh4k"]
+[ext_resource type="PackedScene" uid="uid://dbgbcglqh42ya" path="res://addons/vest/ui/visibility-popup.tscn" id="13_72bup"]
+
+[sub_resource type="Animation" id="Animation_cyqg3"]
+resource_name = "spin"
+loop_mode = 1
+step = 0.0416667
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Spinner:rotation")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 1),
+"transitions": PackedFloat32Array(0.5, 1),
+"update": 0,
+"values": [0.0, 6.28319]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_ed1ow"]
+_data = {
+"spin": SubResource("Animation_cyqg3")
+}
[node name="Vest UI" type="Control"]
custom_minimum_size = Vector2(0, 144)
@@ -27,30 +55,17 @@ grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
-[node name="Results Panel" type="Panel" parent="VBoxContainer"]
-layout_mode = 2
-size_flags_vertical = 3
-
-[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer/Results Panel"]
-layout_mode = 1
-anchors_preset = 15
-anchor_right = 1.0
-anchor_bottom = 1.0
-grow_horizontal = 2
-grow_vertical = 2
-
-[node name="Results Tree" type="Tree" parent="VBoxContainer/Results Panel/PanelContainer"]
+[node name="Results Panel" parent="VBoxContainer" instance=ExtResource("2_r41u0")]
unique_name_in_owner = true
layout_mode = 2
-size_flags_horizontal = 3
-size_flags_vertical = 3
-columns = 2
-hide_root = true
[node name="Bottom Line" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
-[node name="Run All Button" type="Button" parent="VBoxContainer/Bottom Line"]
+[node name="Buttons" type="HBoxContainer" parent="VBoxContainer/Bottom Line"]
+layout_mode = 2
+
+[node name="Run All Button" type="Button" parent="VBoxContainer/Bottom Line/Buttons"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Run tests"
@@ -59,7 +74,7 @@ icon = ExtResource("2_q3lni")
icon_alignment = 1
expand_icon = true
-[node name="Debug Button" type="Button" parent="VBoxContainer/Bottom Line"]
+[node name="Debug Button" type="Button" parent="VBoxContainer/Bottom Line/Buttons"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Debug tests"
@@ -68,7 +83,7 @@ icon = ExtResource("3_mcbjt")
icon_alignment = 1
expand_icon = true
-[node name="Clear Button" type="Button" parent="VBoxContainer/Bottom Line"]
+[node name="Clear Button" type="Button" parent="VBoxContainer/Bottom Line/Buttons"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Clear results"
@@ -77,7 +92,7 @@ icon = ExtResource("4_rjham")
icon_alignment = 1
expand_icon = true
-[node name="Run on Save Button" type="Button" parent="VBoxContainer/Bottom Line"]
+[node name="Run on Save Button" type="Button" parent="VBoxContainer/Bottom Line/Buttons"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Run on Save"
@@ -87,36 +102,122 @@ icon = ExtResource("6_wy3v7")
icon_alignment = 1
expand_icon = true
+[node name="Filter Results Button" type="Button" parent="VBoxContainer/Bottom Line/Buttons"]
+unique_name_in_owner = true
+layout_mode = 2
+tooltip_text = "Filter results"
+text = " "
+icon = ExtResource("6_xkbs5")
+icon_alignment = 1
+expand_icon = true
+
+[node name="Visibility Popup" parent="VBoxContainer/Bottom Line/Buttons/Filter Results Button" instance=ExtResource("13_72bup")]
+unique_name_in_owner = true
+visible = false
+
+[node name="Search Button" type="Button" parent="VBoxContainer/Bottom Line/Buttons"]
+unique_name_in_owner = true
+layout_mode = 2
+text = " "
+icon = ExtResource("10_2uogy")
+icon_alignment = 1
+expand_icon = true
+
+[node name="Search Input" type="LineEdit" parent="VBoxContainer/Bottom Line/Buttons"]
+unique_name_in_owner = true
+visible = false
+custom_minimum_size = Vector2(192, 0)
+layout_mode = 2
+placeholder_text = "Search..."
+
+[node name="Expand Toggle Button" type="Button" parent="VBoxContainer/Bottom Line/Buttons"]
+unique_name_in_owner = true
+layout_mode = 2
+text = " "
+icon = ExtResource("9_hg4sy")
+icon_alignment = 1
+expand_icon = true
+
[node name="VSeparator3" type="VSeparator" parent="VBoxContainer/Bottom Line"]
layout_mode = 2
-[node name="Test Summary Icon" type="TextureRect" parent="VBoxContainer/Bottom Line"]
+[node name="Run Summary" type="HBoxContainer" parent="VBoxContainer/Bottom Line"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="Test Summary Icon" type="TextureRect" parent="VBoxContainer/Bottom Line/Run Summary"]
unique_name_in_owner = true
visible = false
+custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_vertical = 4
texture = ExtResource("6_mef67")
expand_mode = 5
stretch_mode = 4
-[node name="Tests Summary Label" type="Label" parent="VBoxContainer/Bottom Line"]
+[node name="Tests Summary Label" type="Label" parent="VBoxContainer/Bottom Line/Run Summary"]
unique_name_in_owner = true
custom_minimum_size = Vector2(220, 0)
layout_mode = 2
-text = "Ready"
+
+[node name="Progress Indicator" type="HBoxContainer" parent="VBoxContainer/Bottom Line"]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="Control" type="Control" parent="VBoxContainer/Bottom Line/Progress Indicator"]
+custom_minimum_size = Vector2(32, 0)
+layout_mode = 2
+
+[node name="Spinner" type="TextureRect" parent="VBoxContainer/Bottom Line/Progress Indicator/Control"]
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -16.0
+offset_top = -15.5
+offset_right = 16.0
+offset_bottom = 16.5
+grow_horizontal = 2
+grow_vertical = 2
+rotation = 2.9185
+pivot_offset = Vector2(16, 16)
+texture = ExtResource("10_7lh4k")
+expand_mode = 5
+stretch_mode = 5
+flip_h = true
+
+[node name="AnimationPlayer" type="AnimationPlayer" parent="VBoxContainer/Bottom Line/Progress Indicator/Control"]
+autoplay = "spin"
+libraries = {
+"": SubResource("AnimationLibrary_ed1ow")
+}
+
+[node name="Label" type="Label" parent="VBoxContainer/Bottom Line/Progress Indicator"]
+layout_mode = 2
+text = "Running tests..."
[node name="VSeparator2" type="VSeparator" parent="VBoxContainer/Bottom Line"]
layout_mode = 2
-[node name="Glob Label" type="Label" parent="VBoxContainer/Bottom Line"]
+[node name="Glob" type="HBoxContainer" parent="VBoxContainer/Bottom Line"]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 3.0
+
+[node name="Glob Label" type="Label" parent="VBoxContainer/Bottom Line/Glob"]
layout_mode = 2
text = "Test glob:"
-[node name="Glob LineEdit" type="LineEdit" parent="VBoxContainer/Bottom Line"]
+[node name="Glob LineEdit" type="LineEdit" parent="VBoxContainer/Bottom Line/Glob"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
-text = "res://*.test.gd"
+text = "res://tests/*.test.gd"
[node name="VSeparator4" type="VSeparator" parent="VBoxContainer/Bottom Line"]
layout_mode = 2
diff --git a/trimsock.gd/addons/vest/ui/visibility-popup.gd b/trimsock.gd/addons/vest/ui/visibility-popup.gd
new file mode 100644
index 0000000..65f3c42
--- /dev/null
+++ b/trimsock.gd/addons/vest/ui/visibility-popup.gd
@@ -0,0 +1,70 @@
+@tool
+extends PopupPanel
+
+@onready var _container := %"Statuses Container" as Control
+@onready var _toggle_button := %"Toggle Button" as Button
+
+var _visibilities: Dictionary = {}
+
+signal on_change()
+
+func get_visibility_for(status: int) -> bool:
+ return _visibilities.get(status, true)
+
+func is_empty() -> bool:
+ # Return true if all visibilites are set to false
+ for status in _visibilities:
+ if _visibilities[status]:
+ return false
+ return true
+
+func _init() -> void:
+ # Default visibility to true
+ for status in range(VestResult.TEST_MAX):
+ _visibilities[status] = true
+
+func _ready() -> void:
+ _render()
+ _toggle_button.pressed.connect(_toggle_all)
+
+func _toggle_all() -> void:
+ var visibility := is_empty()
+ for status in _visibilities.keys():
+ _visibilities[status] = visibility
+ _render()
+ on_change.emit()
+
+func _render() -> void:
+ if _container.get_child_count() != _visibilities.size():
+ # Remove children
+ for child in _container.get_children():
+ child.queue_free()
+
+ for status in _visibilities.keys():
+ var checkbox := CheckBox.new()
+ checkbox.toggle_mode = true
+ checkbox.text = VestResult.get_status_string(status).capitalize()
+ checkbox.icon = VestUI.get_status_icon(status)
+ checkbox.expand_icon = true
+
+ checkbox.pressed.connect(func():
+ _visibilities[status] = not _visibilities[status]
+ _render()
+ on_change.emit()
+ )
+
+ _container.add_child(checkbox)
+
+ # Update checkbox statuses
+ for idx in range(_container.get_child_count()):
+ var status := _visibilities.keys()[idx] as int
+ var checkbox := _container.get_child(idx) as CheckBox
+ checkbox.set_pressed_no_signal(_visibilities[status])
+
+ # Update toggle button
+ if is_empty():
+ _toggle_button.text = "All"
+ _toggle_button.icon = Vest.Icons.visible
+ else:
+ _toggle_button.text = "None"
+ _toggle_button.icon = Vest.Icons.hidden
diff --git a/trimsock.gd/addons/vest/ui/visibility-popup.gd.uid b/trimsock.gd/addons/vest/ui/visibility-popup.gd.uid
new file mode 100644
index 0000000..07f495d
--- /dev/null
+++ b/trimsock.gd/addons/vest/ui/visibility-popup.gd.uid
@@ -0,0 +1 @@
+uid://hnnddmijt1mg
diff --git a/trimsock.gd/addons/vest/ui/visibility-popup.tscn b/trimsock.gd/addons/vest/ui/visibility-popup.tscn
new file mode 100644
index 0000000..fe3bc99
--- /dev/null
+++ b/trimsock.gd/addons/vest/ui/visibility-popup.tscn
@@ -0,0 +1,31 @@
+[gd_scene load_steps=4 format=3 uid="uid://dbgbcglqh42ya"]
+
+[ext_resource type="Script" path="res://addons/vest/ui/visibility-popup.gd" id="1_21m7j"]
+[ext_resource type="Texture2D" uid="uid://buuq6qptg3vll" path="res://addons/vest/icons/hidden.svg" id="2_urx7m"]
+
+[sub_resource type="Theme" id="Theme_l01ra"]
+Button/colors/icon_normal_color = Color(1, 1, 1, 0.501961)
+Button/colors/icon_pressed_color = Color(1, 1, 1, 1)
+
+[node name="Visibility Popup" type="PopupPanel"]
+size = Vector2i(128, 214)
+visible = true
+theme = SubResource("Theme_l01ra")
+script = ExtResource("1_21m7j")
+
+[node name="Buttons Container" type="VBoxContainer" parent="."]
+offset_left = 4.0
+offset_top = 4.0
+offset_right = 124.0
+offset_bottom = 210.0
+
+[node name="Statuses Container" type="VBoxContainer" parent="Buttons Container"]
+unique_name_in_owner = true
+layout_mode = 2
+
+[node name="Toggle Button" type="Button" parent="Buttons Container"]
+unique_name_in_owner = true
+layout_mode = 2
+text = "None"
+icon = ExtResource("2_urx7m")
+expand_icon = true
diff --git a/trimsock.gd/addons/vest/vest-defs.gd b/trimsock.gd/addons/vest/vest-defs.gd
index d042c34..fc62ff1 100644
--- a/trimsock.gd/addons/vest/vest-defs.gd
+++ b/trimsock.gd/addons/vest/vest-defs.gd
@@ -23,6 +23,9 @@ class Suite:
## Nested test suites contained in the suite
var suites: Array[VestDefs.Suite] = []
+ ## If true, only this group of tests should run
+ var is_only: bool = false
+
## The resource path to the script that defined the suite
var definition_file: String = ""
@@ -30,6 +33,15 @@ class Suite:
## Set to -1 for undetermined.
var definition_line: int = -1
+ ## Return true if either this suite, one of its cases, or one of its
+ ## subsuites is marked as only.
+ func has_only() -> bool:
+ if is_only:
+ return true
+
+ return cases.any(func(it): return it.is_only) or\
+ suites.any(func(it): return it.has_only())
+
## Get the number of test cases in the suite.[br]
## Includes the number of test cases in the suite, and recursively sums up
## the test cases in any of the nested suites.
@@ -54,6 +66,12 @@ class Case:
## Test case description, displayed in reports
var description: String = ""
+ ## The method defining the test case - empty if defined via [method VestTest.test]
+ var method_name: String = ""
+
+ ## If true, only this test should run
+ var is_only: bool = false
+
## The method called to run the test case
var callback: Callable
@@ -70,6 +88,7 @@ class Case:
func _to_wire() -> Dictionary:
return {
"description": description,
+ "method_name": method_name,
"definition_file": definition_file,
"definition_line": definition_line
}
@@ -78,6 +97,7 @@ class Case:
var result := Case.new()
result.description = data["description"]
+ result.method_name = data["method_name"]
result.definition_file = data["definition_file"]
result.definition_line = data["definition_line"]
@@ -231,10 +251,10 @@ class Benchmark:
var batch_threshold := 0.001 # 1ms
if avg_batch_time <= batch_threshold and _enable_builtin_measures:
Vest.message((
- "Benchmark \"%s\" has run with an average of %.6fms per batch. " +
+ "Benchmark \"%s\" has run with an average of %s per batch. " +
"This is probably faster than what can be reliably measured. " +
"To avoid this warning, increase the batch size to at least %s.") %
- [name, avg_batch_time * 1000., ceil(_batch_size * batch_threshold / avg_batch_time * 1.1)]
+ [name, VestUI.format_duration(avg_batch_time), ceil(_batch_size * batch_threshold / avg_batch_time * 1.1)]
)
return self
@@ -306,9 +326,9 @@ class Benchmark:
# Add builtin measures
if _enable_builtin_measures:
result["iterations"] = _iterations
- result["duration"] = "%.4fms" % [_duration * 1000.0]
+ result["duration"] = VestUI.format_duration(_duration)
result["iters/sec"] = get_iters_per_sec()
- result["average iteration time"] = "%.4fms" % [get_avg_iteration_time() * 1000.0]
+ result["average iteration time"] = VestUI.format_duration(get_avg_iteration_time())
# Add benchmark data
result["name"] = name
diff --git a/trimsock.gd/addons/vest/vest-internals.gd b/trimsock.gd/addons/vest/vest-internals.gd
index 2f1b2ae..f2751d3 100644
--- a/trimsock.gd/addons/vest/vest-internals.gd
+++ b/trimsock.gd/addons/vest/vest-internals.gd
@@ -9,6 +9,11 @@ const GoToTestCommand := preload("res://addons/vest/commands/go-to-test-command.
const CreateTestCommand := preload("res://addons/vest/commands/create-test-command.gd")
const RunTestCommand := preload("res://addons/vest/commands/run-test-command.gd")
+const ONLY_DISABLED := 0
+const ONLY_AUTO := 1
+const ONLY_ENABLED := 2
+const ONLY_DEFAULT := ONLY_AUTO
+
static func create_commands() -> Array[Node]:
# TODO: Don't recreate if exists
var commands := [
diff --git a/trimsock.gd/addons/vest/vest-result.gd b/trimsock.gd/addons/vest/vest-result.gd
index eb4cf3d..d3a7c6e 100644
--- a/trimsock.gd/addons/vest/vest-result.gd
+++ b/trimsock.gd/addons/vest/vest-result.gd
@@ -10,7 +10,8 @@ enum { ## Result status enum
TEST_TODO, ## Test is not implemented yet
TEST_FAIL, ## Test has failed
TEST_SKIP, ## Test was skipped
- TEST_PASS ## Test passed
+ TEST_PASS, ## Test passed
+ TEST_MAX ## Represents the size of the result status enum
}
## Test suite results.
@@ -26,10 +27,18 @@ class Suite:
## Get the number of test cases in the suite.[br]
## Includes the number of test cases in the suite, and recursively sums up
- ## the test cases in any of the nested suites.
+ ## the test cases in any of the nested suites.[br]
+ ## To count only the direct descendants, see [method plan_size].
func size() -> int:
return cases.size() + subsuites.reduce(func(acc, it): return acc + it.size(), 0)
+ ## Return the number of items in the test plan.[br]
+ ## Includes the number of test cases and subsuites in the suite. As opposed
+ ## to [method size], this method doesn't include the test cases of the
+ ## subsuites.
+ func plan_size() -> int:
+ return cases.size() + subsuites.size()
+
## Get the aggregate result of the test cases and suites contained in the
## suite.
func get_aggregate_status() -> int:
@@ -44,6 +53,18 @@ class Suite:
func get_aggregate_status_string() -> String:
return VestResult.get_status_string(get_aggregate_status())
+ ## Get an array of result statuses contained in the suite.
+ func get_unique_statuses() -> Array[int]:
+ var result := [] as Array[int]
+ for kase in cases:
+ if not result.has(kase.status):
+ result.push_back(kase.status)
+ for subsuite in subsuites:
+ for status in subsuite.get_unique_statuses():
+ if not result.has(status):
+ result.push_back(status)
+ return result
+
## Get the count of test cases and nested suites with the given result
## status.
## [br][br]
@@ -78,8 +99,8 @@ class Case:
## The resulting status of the test run.
var status: int = TEST_VOID
- ## The message attached to this result.
- var message: String = ""
+ # The messages attached to this result
+ var messages: Array[String] = []
## Custom data attached to this result.
var data: Dictionary = {}
@@ -99,7 +120,7 @@ class Case:
return {
"case": case._to_wire(),
"status": status,
- "message": message,
+ "messages": messages.duplicate(),
"data": Vest.__.Serializer.serialize(data),
"assert_file": assert_file,
"assert_line": assert_line
@@ -110,7 +131,7 @@ class Case:
result.case = VestDefs.Case._from_wire(p_data["case"])
result.status = p_data["status"]
- result.message = p_data["message"]
+ result.messages.assign(p_data["messages"])
result.data = p_data["data"]
result.assert_file = p_data["assert_file"]
result.assert_line = p_data["assert_line"]
diff --git a/trimsock.gd/addons/vest/vest-singleton.gd b/trimsock.gd/addons/vest/vest-singleton.gd
index 0f3644e..f15030c 100644
--- a/trimsock.gd/addons/vest/vest-singleton.gd
+++ b/trimsock.gd/addons/vest/vest-singleton.gd
@@ -86,7 +86,7 @@ static func sleep(duration: float = 0.) -> Error:
## If the test runner doesn't finish running the tests in this time, it's
## considered stuck. This value is read from the project settings.
## [br][br]
-## For more info, see the
+## For more info, see the
## [url=https://foxssake.github.io/vest/latest/user-guide/project-settings/#runner-timeout]user
## guide[/url].
static func get_runner_timeout() -> float:
@@ -98,7 +98,7 @@ static func get_runner_timeout() -> float:
## for deriving paths to test suites. This value is read from the project
## settings.
## [br][br]
-## For more info, see the
+## For more info, see the
## [url=https://foxssake.github.io/vest/latest/user-guide/project-settings/#sources-root]user
## guide[/url].
static func get_sources_root() -> String:
@@ -109,7 +109,7 @@ static func get_sources_root() -> String:
## This is directory is assumed root for all the test suites, and is used
## for deriving paths. This value is read from the project settings.
## [br][br]
-## For more info, see the
+## For more info, see the
## [url=https://foxssake.github.io/vest/latest/user-guide/project-settings/#tests-root]user
## guide[/url].
static func get_tests_root() -> String:
@@ -120,7 +120,7 @@ static func get_tests_root() -> String:
## These are used to recognize and generate filenames for test suites. This
## value is read from the project settings.
## [br][br]
-## For more info, see the
+## For more info, see the
## [url=https://foxssake.github.io/vest/latest/user-guide/project-settings/#test-name-patterns]user
## guide[/url].
static func get_test_name_patterns() -> Array[FilenamePattern]:
@@ -135,7 +135,7 @@ static func get_test_name_patterns() -> Array[FilenamePattern]:
##
## This setting determines what path to suggest when creating new tests.
## [br][br]
-## For more info, see the
+## For more info, see the
## [url=https://foxssake.github.io/vest/latest/user-guide/project-settings/#new-test-location]
## user guide[/url].
static func get_new_test_location_preference() -> int:
@@ -198,6 +198,11 @@ static func glob(pattern: String, max_iters: int = 131072) -> Array[String]:
return results
+## Returns a [SceneTree]. May be used for tests requiring more complex node
+## setups.
+static func get_tree() -> SceneTree:
+ return _scene_tree
+
static func _clear_messages():
_messages.clear()
@@ -209,12 +214,21 @@ static func _register_scene_tree(scene_tree: SceneTree):
# HACK: Godot script compiler fails in 4.2+ if the return type is an engine singleton
# The best we can do is return an object and assume the methods exist.
-# Casting is out of the question as well, also freaks out the script compiler.
+# Casting isn't possible either, also freaks out the script compiler.
static func _get_editor_interface() -> Object:
if Engine.get_version_info().hex >= 0x040200:
return Engine.get_singleton("EditorInterface")
- else:
+ elif _editor_interface_provider.is_valid():
return _editor_interface_provider.call()
+ else:
+ return null
+
+static func _get_editor_scale() -> float:
+ var interface := _get_editor_interface()
+ if interface and interface.has_method("get_editor_scale"):
+ return interface.get_editor_scale()
+ else:
+ return 1.0
static func _register_editor_interface_provider(provider: Callable):
_editor_interface_provider = provider
diff --git a/trimsock.gd/tests/incremental_id_generator.perf.gd b/trimsock.gd/tests/incremental_id_generator.perf.gd
index c4fcb7c..7387cd6 100644
--- a/trimsock.gd/tests/incremental_id_generator.perf.gd
+++ b/trimsock.gd/tests/incremental_id_generator.perf.gd
@@ -7,7 +7,7 @@ func test_sequence():
var generator := IncrementalTrimsockIDGenerator.new()
var min_seq := 1_000_000
var ids := {}
-
+
benchmark("Generator", func(__):
var id := generator.get_id()
ids[id] = true
diff --git a/trimsock.gd/tests/random_id_generator.perf.gd b/trimsock.gd/tests/random_id_generator.perf.gd
index aa80a0f..9730049 100644
--- a/trimsock.gd/tests/random_id_generator.perf.gd
+++ b/trimsock.gd/tests/random_id_generator.perf.gd
@@ -7,7 +7,7 @@ func test_sequence():
var generator := RandomTrimsockIDGenerator.new()
var min_seq := 1_000_000
var ids := {}
-
+
benchmark("Generator", func(__):
var id := generator.get_id()
ids[id] = true