Setup tests

- Install Gut using gd-plug.
- Add some basic tests.
- Update workflow to run tests.
- Add test and gd-plug install/uninstall recipes to Justfile.
- Re-enable debug builds as these are used by editor when testing.
This commit is contained in:
Leroy Hopson 2024-02-18 11:50:38 +13:00
parent e234b14867
commit db3564f0cc
No known key found for this signature in database
GPG key ID: D2747312A6DB51AA
128 changed files with 1412 additions and 16452 deletions

4
.gitattributes vendored
View file

@ -15,7 +15,7 @@
*.wav binary *.wav binary
# Files to exclude from asset-lib download. # Files to exclude from asset-lib download.
/addons/gut export-ignore /addons/gd-plug export-ignore
/CHANGELOG.md export-ignore /CHANGELOG.md export-ignore
/default_env.tres export-ignore /default_env.tres export-ignore
/docs export-ignore /docs export-ignore
@ -32,6 +32,8 @@
/Justfile export-ignore /Justfile export-ignore
/LICENSE.md export-ignore /LICENSE.md export-ignore
/misc export-ignore /misc export-ignore
/plug.gd export-ignore
/.plugged export-ignore
/.pre-commit-config.yaml export-ignore /.pre-commit-config.yaml export-ignore
/project.godot export-ignore /project.godot export-ignore
/README.md export-ignore /README.md export-ignore

View file

@ -9,6 +9,11 @@ runs:
uses: lihop/setup-godot@v2 uses: lihop/setup-godot@v2
with: with:
version: ${{ inputs.godot-version }} version: ${{ inputs.godot-version }}
- name: Install just
uses: taiki-e/install-action@just
- name: Install dev dependencies
shell: bash
run: just install
- name: Import assets - name: Import assets
shell: bash shell: bash
run: | run: |

View file

@ -39,7 +39,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
arch: [x86_64, x86_32] arch: [x86_64, x86_32]
target: [release] target: [release, debug]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
@ -80,7 +80,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
platform: [web, macos, windows] platform: [web, macos, windows]
target: [release] target: [release, debug]
bits: [64, 32] bits: [64, 32]
exclude: exclude:
- platform: web - platform: web
@ -246,22 +246,17 @@ jobs:
path: docs/demo path: docs/demo
test: test:
name: Test (${{ matrix.platform }}, ${{ matrix.arch }}, release) name: Test (${{ matrix.platform }}, ${{ matrix.arch }}, debug)
needs: [check-archive, check-pre-commit] needs: [check-archive, check-pre-commit]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
platform: [linux, macos, windows] platform: [linux, windows] # Testing not currently supported on macOS.
bits: [64, 32] bits: [64, 32]
exclude:
- platform: macos
bits: 32
include: include:
- platform: linux - platform: linux
os: ubuntu-22.04 os: ubuntu-22.04
- platform: macos
os: macos-12
- platform: windows - platform: windows
os: windows-2022 os: windows-2022
- platform: linux - platform: linux
@ -270,8 +265,6 @@ jobs:
- platform: linux - platform: linux
bits: 32 bits: 32
arch: x86_32 arch: x86_32
- platform: macos
arch: universal
- platform: windows - platform: windows
bits: 64 bits: 64
arch: x86_64 arch: x86_64
@ -284,6 +277,8 @@ jobs:
uses: lihop/setup-godot@v2 uses: lihop/setup-godot@v2
with: with:
version: "4.2.1-stable" version: "4.2.1-stable"
- uses: taiki-e/install-action@just
- run: just install
- name: Wait for asset import - name: Wait for asset import
uses: fountainhead/action-wait-for-check@v1.1.0 uses: fountainhead/action-wait-for-check@v1.1.0
with: with:
@ -299,13 +294,15 @@ jobs:
uses: fountainhead/action-wait-for-check@v1.1.0 uses: fountainhead/action-wait-for-check@v1.1.0
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
checkName: Build (${{ matrix.platform }}, ${{ matrix.arch }}, release) checkName: Build (${{ matrix.platform }}, ${{ matrix.arch }}, debug)
ref: ${{ github.event.pull_request.head.sha || github.sha }} ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Install binary build artifacts - name: Install binary build artifacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
with: with:
name: libgodot-xterm-release name: libgodot-xterm-debug
path: addons/godot_xterm/native/bin path: addons/godot_xterm/native/bin
- name: Test
run: just test
delete-asset-imports: delete-asset-imports:
name: Delete Asset Imports name: Delete Asset Imports

4
.gitignore vendored
View file

@ -33,6 +33,10 @@ test/test_metadata.json
# GodotXterm-specific ignores # GodotXterm-specific ignores
.gdxterm .gdxterm
.plugged/
addons/*
!addons/gd-plug/
!addons/godot_xterm/
docs/demo docs/demo
docs/logs docs/logs

0
.plugged/.gdignore Normal file
View file

View file

@ -7,3 +7,12 @@ godot := `echo "${GODOT:-godot}"`
build: build:
cd addons/godot_xterm/native && scons cd addons/godot_xterm/native && scons
install:
{{godot}} --headless -s plug.gd install
test:
{{godot}} --headless -s addons/gut/gut_cmdln.gd -gdir=res://test -gexit
uninstall:
{{godot}} --headless -s plug.gd uninstall

1336
addons/gd-plug/plug.gd Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,133 +0,0 @@
extends Node2D
# ##############################################################################
# This is a wrapper around the normal and compact gui controls and serves as
# the interface between gut.gd and the gui. The GutRunner creates an instance
# of this and then this takes care of managing the different GUI controls.
# ##############################################################################
@onready var _normal_gui = $Normal
@onready var _compact_gui = $Compact
var gut = null:
set(val):
gut = val
_set_gut(val)
func _ready():
_normal_gui.switch_modes.connect(use_compact_mode.bind(true))
_compact_gui.switch_modes.connect(use_compact_mode.bind(false))
_normal_gui.set_title("GUT")
_compact_gui.set_title("GUT")
_normal_gui.align_right()
_compact_gui.to_bottom_right()
use_compact_mode(false)
if get_parent() == get_tree().root:
_test_running_setup()
func _test_running_setup():
set_font_size(100)
_normal_gui.get_textbox().text = "hello world, how are you doing?"
# ------------------------
# Private
# ------------------------
func _set_gut(val):
if _normal_gui.get_gut() == val:
return
_normal_gui.set_gut(val)
_compact_gui.set_gut(val)
val.start_run.connect(_on_gut_start_run)
val.end_run.connect(_on_gut_end_run)
val.start_pause_before_teardown.connect(_on_gut_pause)
val.end_pause_before_teardown.connect(_on_pause_end)
func _set_both_titles(text):
_normal_gui.set_title(text)
_compact_gui.set_title(text)
# ------------------------
# Events
# ------------------------
func _on_gut_start_run():
_set_both_titles("Running")
func _on_gut_end_run():
_set_both_titles("Finished")
func _on_gut_pause():
_set_both_titles("-- Paused --")
func _on_pause_end():
_set_both_titles("Running")
# ------------------------
# Public
# ------------------------
func get_textbox():
return _normal_gui.get_textbox()
func set_font_size(new_size):
var rtl = _normal_gui.get_textbox()
rtl.set("theme_override_font_sizes/bold_italics_font_size", new_size)
rtl.set("theme_override_font_sizes/bold_font_size", new_size)
rtl.set("theme_override_font_sizes/italics_font_size", new_size)
rtl.set("theme_override_font_sizes/normal_font_size", new_size)
func set_font(font_name):
_set_all_fonts_in_rtl(_normal_gui.get_textbox(), font_name)
func _set_font(rtl, font_name, custom_name):
if font_name == null:
rtl.add_theme_font_override(custom_name, null)
else:
var dyn_font = FontFile.new()
dyn_font.load_dynamic_font("res://addons/gut/fonts/" + font_name + ".ttf")
rtl.add_theme_font_override(custom_name, dyn_font)
func _set_all_fonts_in_rtl(rtl, base_name):
if base_name == "Default":
_set_font(rtl, null, "normal_font")
_set_font(rtl, null, "bold_font")
_set_font(rtl, null, "italics_font")
_set_font(rtl, null, "bold_italics_font")
else:
_set_font(rtl, base_name + "-Regular", "normal_font")
_set_font(rtl, base_name + "-Bold", "bold_font")
_set_font(rtl, base_name + "-Italic", "italics_font")
_set_font(rtl, base_name + "-BoldItalic", "bold_italics_font")
func set_default_font_color(color):
_normal_gui.get_textbox().set("custom_colors/default_color", color)
func set_background_color(color):
_normal_gui.set_bg_color(color)
func use_compact_mode(should = true):
_compact_gui.visible = should
_normal_gui.visible = !should
func set_opacity(val):
_normal_gui.modulate.a = val
_compact_gui.modulate.a = val

View file

@ -1,16 +0,0 @@
[gd_scene load_steps=4 format=3 uid="uid://m28heqtswbuq"]
[ext_resource type="Script" path="res://addons/gut/GutScene.gd" id="1_b4m8y"]
[ext_resource type="PackedScene" uid="uid://duxblir3vu8x7" path="res://addons/gut/gui/NormalGui.tscn" id="2_j6ywb"]
[ext_resource type="PackedScene" uid="uid://cnqqdfsn80ise" path="res://addons/gut/gui/MinGui.tscn" id="3_3glw1"]
[node name="GutScene" type="Node2D"]
script = ExtResource("1_b4m8y")
[node name="Normal" parent="." instance=ExtResource("2_j6ywb")]
[node name="Compact" parent="." instance=ExtResource("3_3glw1")]
offset_left = 5.0
offset_top = 273.0
offset_right = 265.0
offset_bottom = 403.0

View file

@ -1,22 +0,0 @@
The MIT License (MIT)
=====================
Copyright (c) 2018 Tom "Butch" Wesley
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -1,64 +0,0 @@
extends Window
@onready var rtl = $TextDisplay/RichTextLabel
func _get_file_as_text(path):
var to_return = null
var f = FileAccess.open(path, FileAccess.READ)
if f != null:
to_return = f.get_as_text()
else:
to_return = str("ERROR: Could not open file. Error code ", FileAccess.get_open_error())
return to_return
func _ready():
rtl.clear()
func _on_OpenFile_pressed():
$FileDialog.popup_centered()
func _on_FileDialog_file_selected(path):
show_file(path)
func _on_Close_pressed():
self.hide()
func show_file(path):
var text = _get_file_as_text(path)
if text == "":
text = "<Empty File>"
rtl.set_text(text)
self.window_title = path
func show_open():
self.popup_centered()
$FileDialog.popup_centered()
func get_rich_text_label():
return $TextDisplay/RichTextLabel
func _on_Home_pressed():
rtl.scroll_to_line(0)
func _on_End_pressed():
rtl.scroll_to_line(rtl.get_line_count() - 1)
func _on_Copy_pressed():
return
# OS.clipboard = rtl.text
func _on_file_dialog_visibility_changed():
if rtl.text.length() == 0 and not $FileDialog.visible:
self.hide()

View file

@ -1,92 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://bsm7wtt1gie4v"]
[ext_resource type="Script" path="res://addons/gut/UserFileViewer.gd" id="1"]
[node name="UserFileViewer" type="Window"]
exclusive = true
script = ExtResource("1")
[node name="FileDialog" type="FileDialog" parent="."]
access = 1
show_hidden_files = true
__meta__ = {
"_edit_use_anchors_": false
}
[node name="TextDisplay" type="ColorRect" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 8.0
offset_right = -10.0
offset_bottom = -65.0
color = Color(0.2, 0.188235, 0.188235, 1)
[node name="RichTextLabel" type="RichTextLabel" parent="TextDisplay"]
anchor_right = 1.0
anchor_bottom = 1.0
focus_mode = 2
text = "In publishing and graphic design, Lorem ipsum is a placeholder text commonly used to demonstrate the visual form of a document or a typeface without relying on meaningful content. Lorem ipsum may be used before final copy is available, but it may also be used to temporarily replace copy in a process called greeking, which allows designers to consider form without the meaning of the text influencing the design.
Lorem ipsum is typically a corrupted version of De finibus bonorum et malorum, a first-century BCE text by the Roman statesman and philosopher Cicero, with words altered, added, and removed to make it nonsensical, improper Latin.
Versions of the Lorem ipsum text have been used in typesetting at least since the 1960s, when it was popularized by advertisements for Letraset transfer sheets. Lorem ipsum was introduced to the digital world in the mid-1980s when Aldus employed it in graphic and word-processing templates for its desktop publishing program PageMaker. Other popular word processors including Pages and Microsoft Word have since adopted Lorem ipsum as well."
selection_enabled = true
[node name="OpenFile" type="Button" parent="."]
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -158.0
offset_top = -50.0
offset_right = -84.0
offset_bottom = -30.0
text = "Open File"
[node name="Home" type="Button" parent="."]
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -478.0
offset_top = -50.0
offset_right = -404.0
offset_bottom = -30.0
text = "Home"
[node name="Copy" type="Button" parent="."]
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 160.0
offset_top = -50.0
offset_right = 234.0
offset_bottom = -30.0
text = "Copy"
[node name="End" type="Button" parent="."]
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -318.0
offset_top = -50.0
offset_right = -244.0
offset_bottom = -30.0
text = "End"
[node name="Close" type="Button" parent="."]
anchor_top = 1.0
anchor_bottom = 1.0
offset_left = 10.0
offset_top = -50.0
offset_right = 80.0
offset_bottom = -30.0
text = "Close"
[connection signal="file_selected" from="FileDialog" to="." method="_on_FileDialog_file_selected"]
[connection signal="visibility_changed" from="FileDialog" to="." method="_on_file_dialog_visibility_changed"]
[connection signal="pressed" from="OpenFile" to="." method="_on_OpenFile_pressed"]
[connection signal="pressed" from="Home" to="." method="_on_Home_pressed"]
[connection signal="pressed" from="Copy" to="." method="_on_Copy_pressed"]
[connection signal="pressed" from="End" to="." method="_on_End_pressed"]
[connection signal="pressed" from="Close" to="." method="_on_Close_pressed"]

View file

@ -1,62 +0,0 @@
# ##############################################################################
#(G)odot (U)nit (T)est class
#
# ##############################################################################
# The MIT License (MIT)
# =====================
#
# Copyright (c) 2020 Tom "Butch" Wesley
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ##############################################################################
# Class used to keep track of objects to be freed and utilities to free them.
# ##############################################################################
var _to_free = []
var _to_queue_free = []
func add_free(thing):
if typeof(thing) == TYPE_OBJECT:
if !thing is RefCounted:
_to_free.append(thing)
func add_queue_free(thing):
_to_queue_free.append(thing)
func get_queue_free_count():
return _to_queue_free.size()
func get_free_count():
return _to_free.size()
func free_all():
for i in range(_to_free.size()):
if is_instance_valid(_to_free[i]):
_to_free[i].free()
_to_free.clear()
for i in range(_to_queue_free.size()):
if is_instance_valid(_to_queue_free[i]):
_to_queue_free[i].queue_free()
_to_queue_free.clear()

View file

@ -1,77 +0,0 @@
extends Node
signal timeout
signal wait_started
var _wait_time = 0.0
var _wait_frames = 0
var _signal_to_wait_on = null
var _elapsed_time = 0.0
var _elapsed_frames = 0
func _physics_process(delta):
if _wait_time != 0.0:
_elapsed_time += delta
if _elapsed_time >= _wait_time:
_end_wait()
if _wait_frames != 0:
_elapsed_frames += 1
if _elapsed_frames >= _wait_frames:
_end_wait()
func _end_wait():
if _signal_to_wait_on != null and _signal_to_wait_on.is_connected(_signal_callback):
_signal_to_wait_on.disconnect(_signal_callback)
_wait_time = 0.0
_wait_frames = 0
_signal_to_wait_on = null
_elapsed_time = 0.0
_elapsed_frames = 0
timeout.emit()
const ARG_NOT_SET = "_*_argument_*_is_*_not_set_*_"
func _signal_callback(
arg1 = ARG_NOT_SET,
arg2 = ARG_NOT_SET,
arg3 = ARG_NOT_SET,
arg4 = ARG_NOT_SET,
arg5 = ARG_NOT_SET,
arg6 = ARG_NOT_SET,
arg7 = ARG_NOT_SET,
arg8 = ARG_NOT_SET,
arg9 = ARG_NOT_SET
):
_signal_to_wait_on.disconnect(_signal_callback)
# DO NOT _end_wait here. For other parts of the test to get the signal that
# was waited on, we have to wait for a couple more frames. For example, the
# signal_watcher doesn't get the signal in time if we don't do this.
_wait_frames = 2
func wait_for(x):
_wait_time = x
wait_started.emit()
func wait_frames(x):
_wait_frames = x
wait_started.emit()
func wait_for_signal(the_signal, x):
the_signal.connect(_signal_callback)
_signal_to_wait_on = the_signal
_wait_time = x
wait_started.emit()
func is_waiting():
return _wait_time != 0.0 || _wait_frames != 0

View file

@ -1,214 +0,0 @@
# ------------------------------------------------------------------------------
# This holds all the meta information for a test script. It contains the
# name of the inner class and an array of CollectedTests. This does not parse
# anything, it just holds the data about parsed scripts and tests. The
# TestCollector is responsible for populating this object.
#
# This class also facilitates all the exporting and importing of tests.
# ------------------------------------------------------------------------------
var CollectedTest = load("res://addons/gut/collected_test.gd")
var _utils = null
var _lgr = null
# One entry per test found in the script. Added externally by TestCollector
var tests = []
# One entry for before_all and after_all (maybe add before_each and after_each).
# These are added by Gut when running before_all and after_all for the script.
var setup_teardown_tests = []
var inner_class_name: StringName
var path: String
# Set externally by test_collector after it can verify that the script was
# actually loaded. This could probably be changed to just hold the GutTest
# script that was loaded, cutting down on complexity elsewhere.
var is_loaded = false
# Set by Gut when it decides that a script should be skipped.
# Right now this is whenever the script has the variable skip_script declared.
# the value of skip_script is put into skip_reason.
var was_skipped = false
var skip_reason = ""
var was_run = false
var name = "":
get:
return path
set(val):
pass
func _init(utils = null, logger = null):
_utils = utils
_lgr = logger
func get_new():
return load_script().new()
func load_script():
var to_return = load(path)
if inner_class_name != null and inner_class_name != "":
# If we wanted to do inner classes in inner classses
# then this would have to become some kind of loop or recursive
# call to go all the way down the chain or this class would
# have to change to hold onto the loaded class instead of
# just path information.
to_return = to_return.get(inner_class_name)
return to_return
# script.gd.InnerClass
func get_filename_and_inner():
var to_return = get_filename()
if inner_class_name != "":
to_return += "." + String(inner_class_name)
return to_return
# res://foo/bar.gd.FooBar
func get_full_name():
var to_return = path
if inner_class_name != "":
to_return += "." + String(inner_class_name)
return to_return
func get_filename():
return path.get_file()
func has_inner_class():
return inner_class_name != ""
# Note: although this no longer needs to export the inner_class names since
# they are pulled from metadata now, it is easier to leave that in
# so we don't have to cut the export down to unique script names.
func export_to(config_file, section):
config_file.set_value(section, "path", path)
config_file.set_value(section, "inner_class", inner_class_name)
var names = []
for i in range(tests.size()):
names.append(tests[i].name)
config_file.set_value(section, "tests", names)
func _remap_path(source_path):
var to_return = source_path
if !_utils.file_exists(source_path):
_lgr.debug("Checking for remap for: " + source_path)
var remap_path = source_path.get_basename() + ".gd.remap"
if _utils.file_exists(remap_path):
var cf = ConfigFile.new()
cf.load(remap_path)
to_return = cf.get_value("remap", "path")
else:
_lgr.warn("Could not find remap file " + remap_path)
return to_return
func import_from(config_file, section):
path = config_file.get_value(section, "path")
path = _remap_path(path)
# Null is an acceptable value, but you can't pass null as a default to
# get_value since it thinks you didn't send a default...then it spits
# out red text. This works around that.
var inner_name = config_file.get_value(section, "inner_class", "Placeholder")
if inner_name != "Placeholder":
inner_class_name = inner_name
else: # just being explicit
inner_class_name = StringName("")
func get_test_named(name):
return _utils.search_array(tests, "name", name)
func mark_tests_to_skip_with_suffix(suffix):
for single_test in tests:
single_test.should_skip = single_test.name.ends_with(suffix)
func get_ran_test_count():
var count = 0
for t in tests:
if t.was_run:
count += 1
return count
func get_assert_count():
var count = 0
for t in tests:
count += t.pass_texts.size()
count += t.fail_texts.size()
for t in setup_teardown_tests:
count += t.pass_texts.size()
count += t.fail_texts.size()
return count
func get_pass_count():
var count = 0
for t in tests:
count += t.pass_texts.size()
for t in setup_teardown_tests:
count += t.pass_texts.size()
return count
func get_fail_count():
var count = 0
for t in tests:
count += t.fail_texts.size()
for t in setup_teardown_tests:
count += t.fail_texts.size()
return count
func get_pending_count():
var count = 0
for t in tests:
count += t.pending_texts.size()
return count
func get_passing_test_count():
var count = 0
for t in tests:
if t.is_passing():
count += 1
return count
func get_failing_test_count():
var count = 0
for t in tests:
if t.is_failing():
count += 1
return count
func get_risky_count():
var count = 0
if was_skipped:
count = 1
else:
for t in tests:
if t.is_risky():
count += 1
return count
func to_s():
var to_return = path
if inner_class_name != null:
to_return += str(".", inner_class_name)
to_return += "\n"
for i in range(tests.size()):
to_return += str(" ", tests[i].to_s())
return to_return

View file

@ -1,117 +0,0 @@
# ------------------------------------------------------------------------------
# Used to keep track of info about each test ran.
# ------------------------------------------------------------------------------
# the name of the function
var name = ""
# flag to know if the name has been printed yet. Used by the logger.
var has_printed_name = false
# the number of arguments the method has
var arg_count = 0
# The number of asserts in the test. Converted to a property for backwards
# compatibility. This now reflects the text sizes instead of being a value
# that can be altered externally.
var assert_count = 0:
get:
return pass_texts.size() + fail_texts.size()
set(val):
pass
# Converted to propety for backwards compatibility. This now cannot be set
# externally
var pending = false:
get:
return is_pending()
set(val):
pass
# the line number when the test fails
var line_number = -1
# Set internally by Gut using whatever reason Gut wants to use to set this.
# Gut will skip these marked true and the test will be listed as risky.
var should_skip = false
var pass_texts = []
var fail_texts = []
var pending_texts = []
var orphans = 0
var was_run = false
func did_pass():
return is_passing()
func add_fail(fail_text):
fail_texts.append(fail_text)
func add_pending(pending_text):
pending_texts.append(pending_text)
func add_pass(passing_text):
pass_texts.append(passing_text)
# must have passed an assert and not have any other status to be passing
func is_passing():
return pass_texts.size() > 0 and fail_texts.size() == 0 and pending_texts.size() == 0
# failing takes precedence over everything else, so any failures makes the
# test a failure.
func is_failing():
return fail_texts.size() > 0
# test is only pending if pending was called and the test is not failing.
func is_pending():
return pending_texts.size() > 0 and fail_texts.size() == 0
func is_risky():
return should_skip or (was_run and !did_something())
func did_something():
return is_passing() or is_failing() or is_pending()
func get_status_text():
var to_return = GutUtils.TEST_STATUSES.NO_ASSERTS
if should_skip:
to_return = GutUtils.TEST_STATUSES.SKIPPED
elif !was_run:
to_return = GutUtils.TEST_STATUSES.NOT_RUN
elif pending_texts.size() > 0:
to_return = GutUtils.TEST_STATUSES.PENDING
elif fail_texts.size() > 0:
to_return = GutUtils.TEST_STATUSES.FAILED
elif pass_texts.size() > 0:
to_return = GutUtils.TEST_STATUSES.PASSED
return to_return
# Deprecated
func get_status():
return get_status_text()
func to_s():
var pad = " "
var to_return = str(name, "[", get_status_text(), "]\n")
for i in range(fail_texts.size()):
to_return += str(pad, "Fail: ", fail_texts[i])
for i in range(pending_texts.size()):
to_return += str(pad, "Pending: ", pending_texts[i], "\n")
for i in range(pass_texts.size()):
to_return += str(pad, "Pass: ", pass_texts[i], "\n")
return to_return

View file

@ -1,131 +0,0 @@
var _utils = load("res://addons/gut/utils.gd").get_instance()
var _strutils = _utils.Strutils.new()
var _max_length = 100
var _should_compare_int_to_float = true
const MISSING = "|__missing__gut__compare__value__|"
func _cannot_compare_text(v1, v2):
return str(
"Cannot compare ", _strutils.types[typeof(v1)], " with ", _strutils.types[typeof(v2)], "."
)
func _make_missing_string(text):
return "<missing " + text + ">"
func _create_missing_result(v1, v2, text):
var to_return = null
var v1_str = format_value(v1)
var v2_str = format_value(v2)
if typeof(v1) == TYPE_STRING and v1 == MISSING:
v1_str = _make_missing_string(text)
to_return = _utils.CompareResult.new()
elif typeof(v2) == TYPE_STRING and v2 == MISSING:
v2_str = _make_missing_string(text)
to_return = _utils.CompareResult.new()
if to_return != null:
to_return.summary = str(v1_str, " != ", v2_str)
to_return.are_equal = false
return to_return
func simple(v1, v2, missing_string = ""):
var missing_result = _create_missing_result(v1, v2, missing_string)
if missing_result != null:
return missing_result
var result = _utils.CompareResult.new()
var cmp_str = null
var extra = ""
var tv1 = typeof(v1)
var tv2 = typeof(v2)
# print(tv1, '::', tv2, ' ', _strutils.types[tv1], '::', _strutils.types[tv2])
if (
_should_compare_int_to_float
and [TYPE_INT, TYPE_FLOAT].has(tv1)
and [TYPE_INT, TYPE_FLOAT].has(tv2)
):
result.are_equal = v1 == v2
elif [TYPE_STRING, TYPE_STRING_NAME].has(tv1) and [TYPE_STRING, TYPE_STRING_NAME].has(tv2):
result.are_equal = v1 == v2
elif _utils.are_datatypes_same(v1, v2):
result.are_equal = v1 == v2
if typeof(v1) == TYPE_DICTIONARY or typeof(v1) == TYPE_ARRAY:
var sub_result = _utils.DiffTool.new(v1, v2, _utils.DIFF.DEEP)
result.summary = sub_result.get_short_summary()
if !sub_result.are_equal:
extra = ".\n" + sub_result.get_short_summary()
else:
cmp_str = "!="
result.are_equal = false
extra = str(". ", _cannot_compare_text(v1, v2))
cmp_str = get_compare_symbol(result.are_equal)
result.summary = str(format_value(v1), " ", cmp_str, " ", format_value(v2), extra)
return result
func shallow(v1, v2):
var result = null
if _utils.are_datatypes_same(v1, v2):
if typeof(v1) in [TYPE_ARRAY, TYPE_DICTIONARY]:
result = _utils.DiffTool.new(v1, v2, _utils.DIFF.DEEP)
else:
result = simple(v1, v2)
else:
result = simple(v1, v2)
return result
func deep(v1, v2):
var result = null
if _utils.are_datatypes_same(v1, v2):
if typeof(v1) in [TYPE_ARRAY, TYPE_DICTIONARY]:
result = _utils.DiffTool.new(v1, v2, _utils.DIFF.DEEP)
else:
result = simple(v1, v2)
else:
result = simple(v1, v2)
return result
func format_value(val, max_val_length = _max_length):
return _strutils.truncate_string(_strutils.type2str(val), max_val_length)
func compare(v1, v2, diff_type = _utils.DIFF.SIMPLE):
var result = null
if diff_type == _utils.DIFF.SIMPLE:
result = simple(v1, v2)
elif diff_type == _utils.DIFF.DEEP:
result = deep(v1, v2)
return result
func get_should_compare_int_to_float():
return _should_compare_int_to_float
func set_should_compare_int_to_float(should_compare_int_float):
_should_compare_int_to_float = should_compare_int_float
func get_compare_symbol(is_equal):
if is_equal:
return "=="
else:
return "!="

View file

@ -1,83 +0,0 @@
var _are_equal = false
var are_equal = false:
get:
return get_are_equal()
set(val):
set_are_equal(val)
var _summary = null
var summary = null:
get:
return get_summary()
set(val):
set_summary(val)
var _max_differences = 30
var max_differences = 30:
get:
return get_max_differences()
set(val):
set_max_differences(val)
var _differences = {}
var differences:
get:
return get_differences()
set(val):
set_differences(val)
func _block_set(which, val):
push_error(str("cannot set ", which, ", value [", val, "] ignored."))
func _to_string():
return str(get_summary()) # could be null, gotta str it.
func get_are_equal():
return _are_equal
func set_are_equal(r_eq):
_are_equal = r_eq
func get_summary():
return _summary
func set_summary(smry):
_summary = smry
func get_total_count():
pass
func get_different_count():
pass
func get_short_summary():
return summary
func get_max_differences():
return _max_differences
func set_max_differences(max_diff):
_max_differences = max_diff
func get_differences():
return _differences
func set_differences(diffs):
_block_set("differences", diffs)
func get_brackets():
return null

View file

@ -1,67 +0,0 @@
var _utils = load("res://addons/gut/utils.gd").get_instance()
var _strutils = _utils.Strutils.new()
const INDENT = " "
var _max_to_display = 30
const ABSOLUTE_MAX_DISPLAYED = 10000
const UNLIMITED = -1
func _single_diff(diff, depth = 0):
var to_return = ""
var brackets = diff.get_brackets()
if brackets != null and !diff.are_equal:
to_return = ""
to_return += str(
brackets.open,
"\n",
_strutils.indent_text(differences_to_s(diff.differences, depth), depth + 1, INDENT),
"\n",
brackets.close
)
else:
to_return = str(diff)
return to_return
func make_it(diff):
var to_return = ""
if diff.are_equal:
to_return = diff.summary
else:
if _max_to_display == ABSOLUTE_MAX_DISPLAYED:
to_return = str(diff.get_value_1(), " != ", diff.get_value_2())
else:
to_return = diff.get_short_summary()
to_return += str("\n", _strutils.indent_text(_single_diff(diff, 0), 1, " "))
return to_return
func differences_to_s(differences, depth = 0):
var to_return = ""
var keys = differences.keys()
keys.sort()
var limit = min(_max_to_display, differences.size())
for i in range(limit):
var key = keys[i]
to_return += str(key, ": ", _single_diff(differences[key], depth))
if i != limit - 1:
to_return += "\n"
if differences.size() > _max_to_display:
to_return += str("\n\n... ", differences.size() - _max_to_display, " more.")
return to_return
func get_max_to_display():
return _max_to_display
func set_max_to_display(max_to_display):
_max_to_display = max_to_display
if _max_to_display == UNLIMITED:
_max_to_display = ABSOLUTE_MAX_DISPLAYED

View file

@ -1,175 +0,0 @@
extends "res://addons/gut/compare_result.gd"
const INDENT = " "
enum { DEEP, SIMPLE }
var _utils = load("res://addons/gut/utils.gd").get_instance()
var _strutils = _utils.Strutils.new()
var _compare = _utils.Comparator.new()
var DiffTool = load("res://addons/gut/diff_tool.gd")
var _value_1 = null
var _value_2 = null
var _total_count = 0
var _diff_type = null
var _brackets = null
var _valid = true
var _desc_things = "somethings"
# -------- comapre_result.gd "interface" ---------------------
func set_are_equal(val):
_block_set("are_equal", val)
func get_are_equal():
if !_valid:
return null
else:
return differences.size() == 0
func set_summary(val):
_block_set("summary", val)
func get_summary():
return summarize()
func get_different_count():
return differences.size()
func get_total_count():
return _total_count
func get_short_summary():
var text = str(
_strutils.truncate_string(str(_value_1), 50),
" ",
_compare.get_compare_symbol(are_equal),
" ",
_strutils.truncate_string(str(_value_2), 50)
)
if !are_equal:
text += str(
" ",
get_different_count(),
" of ",
get_total_count(),
" ",
_desc_things,
" do not match."
)
return text
func get_brackets():
return _brackets
# -------- comapre_result.gd "interface" ---------------------
func _invalidate():
_valid = false
differences = null
func _init(v1, v2, diff_type = DEEP):
_value_1 = v1
_value_2 = v2
_diff_type = diff_type
_compare.set_should_compare_int_to_float(false)
_find_differences(_value_1, _value_2)
func _find_differences(v1, v2):
if _utils.are_datatypes_same(v1, v2):
if typeof(v1) == TYPE_ARRAY:
_brackets = {"open": "[", "close": "]"}
_desc_things = "indexes"
_diff_array(v1, v2)
elif typeof(v2) == TYPE_DICTIONARY:
_brackets = {"open": "{", "close": "}"}
_desc_things = "keys"
_diff_dictionary(v1, v2)
else:
_invalidate()
_utils.get_logger().error("Only Arrays and Dictionaries are supported.")
else:
_invalidate()
_utils.get_logger().error("Only Arrays and Dictionaries are supported.")
func _diff_array(a1, a2):
_total_count = max(a1.size(), a2.size())
for i in range(a1.size()):
var result = null
if i < a2.size():
if _diff_type == DEEP:
result = _compare.deep(a1[i], a2[i])
else:
result = _compare.simple(a1[i], a2[i])
else:
result = _compare.simple(a1[i], _compare.MISSING, "index")
if !result.are_equal:
differences[i] = result
if a1.size() < a2.size():
for i in range(a1.size(), a2.size()):
differences[i] = _compare.simple(_compare.MISSING, a2[i], "index")
func _diff_dictionary(d1, d2):
var d1_keys = d1.keys()
var d2_keys = d2.keys()
# Process all the keys in d1
_total_count += d1_keys.size()
for key in d1_keys:
if !d2.has(key):
differences[key] = _compare.simple(d1[key], _compare.MISSING, "key")
else:
d2_keys.remove_at(d2_keys.find(key))
var result = null
if _diff_type == DEEP:
result = _compare.deep(d1[key], d2[key])
else:
result = _compare.simple(d1[key], d2[key])
if !result.are_equal:
differences[key] = result
# Process all the keys in d2 that didn't exist in d1
_total_count += d2_keys.size()
for i in range(d2_keys.size()):
differences[d2_keys[i]] = _compare.simple(_compare.MISSING, d2[d2_keys[i]], "key")
func summarize():
var summary = ""
if are_equal:
summary = get_short_summary()
else:
var formatter = load("res://addons/gut/diff_formatter.gd").new()
formatter.set_max_to_display(max_differences)
summary = formatter.make_it(self)
return summary
func get_diff_type():
return _diff_type
func get_value_1():
return _value_1
func get_value_2():
return _value_2

View file

@ -1,7 +0,0 @@
{func_decleration}
{vararg_warning}__gutdbl.spy_on('{method_name}', {param_array})
if(__gutdbl.should_call_super('{method_name}', {param_array})):
return {super_call}
else:
return __gutdbl.get_stubbed_return('{method_name}', {param_array})

View file

@ -1,4 +0,0 @@
{func_decleration}:
super({super_params})
__gutdbl.spy_on('{method_name}', {param_array})

View file

@ -1,32 +0,0 @@
# ##############################################################################
# Gut Doubled Script
# ##############################################################################
{extends}
{constants}
{properties}
# ------------------------------------------------------------------------------
# GUT stuff
# ------------------------------------------------------------------------------
var __gutdbl_values = {
double = self,
thepath = '{path}',
subpath = '{subpath}',
stubber = {stubber_id},
spy = {spy_id},
gut = {gut_id},
from_singleton = '{singleton_name}',
is_partial = {is_partial},
doubled_methods = {doubled_methods},
}
var __gutdbl = load('res://addons/gut/double_tools.gd').new(__gutdbl_values)
# Here so other things can check for a method to know if this is a double.
func __gutdbl_check_method__():
pass
# ------------------------------------------------------------------------------
# Doubled Methods
# ------------------------------------------------------------------------------

View file

@ -1,70 +0,0 @@
var thepath = ""
var subpath = ""
var stubber = null
var spy = null
var gut = null
var from_singleton = null
var is_partial = null
var double = null
const NO_DEFAULT_VALUE = "!__gut__no__default__value__!"
func _init(values = null):
if values != null:
double = values.double
thepath = values.thepath
subpath = values.subpath
stubber = from_id(values.stubber)
spy = from_id(values.spy)
gut = from_id(values.gut)
from_singleton = values.from_singleton
is_partial = values.is_partial
if gut != null:
gut.get_autofree().add_free(double)
func from_id(inst_id):
if inst_id == -1:
return null
else:
return instance_from_id(inst_id)
func should_call_super(method_name, called_with):
if stubber != null:
return stubber.should_call_super(double, method_name, called_with)
else:
return false
func spy_on(method_name, called_with):
if spy != null:
spy.add_call(double, method_name, called_with)
func get_stubbed_return(method_name, called_with):
if stubber != null:
return stubber.get_return(double, method_name, called_with)
else:
return null
func default_val(method_name, p_index, default_val = NO_DEFAULT_VALUE):
if stubber != null:
return stubber.get_default_value(double, method_name, p_index)
else:
return null
func vararg_warning():
if gut != null:
gut.get_logger().warn(
(
"This method contains a vararg argument and the paramter count was not stubbed. "
+ "GUT adds extra parameters to this method which should fill most needs. "
+ "It is recommended that you stub param_count for this object's class to ensure "
+ "that there are not any parameter count mismatch errors."
)
)

View file

@ -1,384 +0,0 @@
# ##############################################################################
#(G)odot (U)nit (T)est class
#
# ##############################################################################
# The MIT License (MIT)
# =====================
#
# Copyright (c) 2020 Tom "Butch" Wesley
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ##############################################################################
# Description
# -----------
# ##############################################################################
# ------------------------------------------------------------------------------
# A stroke of genius if I do say so. This allows for doubling a scene without
# having to write any files. By overloading the "instantiate" method we can
# make whatever we want.
# ------------------------------------------------------------------------------
class PackedSceneDouble:
extends PackedScene
var _script = null
var _scene = null
func set_script_obj(obj):
_script = obj
func instantiate(edit_state = 0):
var inst = _scene.instantiate(edit_state)
if _script != null:
inst.set_script(_script)
return inst
func load_scene(path):
_scene = load(path)
# ------------------------------------------------------------------------------
# START Doubler
# ------------------------------------------------------------------------------
var _utils = load("res://addons/gut/utils.gd").get_instance()
var _base_script_text = _utils.get_file_as_text(
"res://addons/gut/double_templates/script_template.txt"
)
var _script_collector = _utils.ScriptCollector.new()
# used by tests for debugging purposes.
var print_source = false
var inner_class_registry = _utils.InnerClassRegistry.new()
# ###############
# Properties
# ###############
var _stubber = _utils.Stubber.new()
func get_stubber():
return _stubber
func set_stubber(stubber):
_stubber = stubber
var _lgr = _utils.get_logger()
func get_logger():
return _lgr
func set_logger(logger):
_lgr = logger
_method_maker.set_logger(logger)
var _spy = null
func get_spy():
return _spy
func set_spy(spy):
_spy = spy
var _gut = null
func get_gut():
return _gut
func set_gut(gut):
_gut = gut
var _strategy = null
func get_strategy():
return _strategy
func set_strategy(strategy):
if GutUtils.DOUBLE_STRATEGY.values().has(strategy):
_strategy = strategy
else:
_lgr.error(str("doubler.gd: invalid double strategy ", strategy))
var _method_maker = _utils.MethodMaker.new()
func get_method_maker():
return _method_maker
var _ignored_methods = _utils.OneToMany.new()
func get_ignored_methods():
return _ignored_methods
# ###############
# Private
# ###############
func _init(strategy = _utils.DOUBLE_STRATEGY.SCRIPT_ONLY):
set_logger(_utils.get_logger())
_strategy = strategy
func _get_indented_line(indents, text):
var to_return = ""
for _i in range(indents):
to_return += "\t"
return str(to_return, text, "\n")
func _stub_to_call_super(parsed, method_name):
if !parsed.get_method(method_name).is_eligible_for_doubling():
return
var params = _utils.StubParams.new(parsed.script_path, method_name, parsed.subpath)
params.to_call_super()
_stubber.add_stub(params)
func _get_base_script_text(parsed, override_path, partial, included_methods):
var path = parsed.script_path
if override_path != null:
path = override_path
var stubber_id = -1
if _stubber != null:
stubber_id = _stubber.get_instance_id()
var spy_id = -1
if _spy != null:
spy_id = _spy.get_instance_id()
var gut_id = -1
if _gut != null:
gut_id = _gut.get_instance_id()
var extends_text = parsed.get_extends_text()
var values = {
# Top sections
"extends": extends_text,
"constants": "", #obj_info.get_constants_text(),
"properties": "", #obj_info.get_properties_text(),
# metadata values
"path": path,
"subpath": GutUtils.nvl(parsed.subpath, ""),
"stubber_id": stubber_id,
"spy_id": spy_id,
"gut_id": gut_id,
"singleton_name": "", #GutUtils.nvl(obj_info.get_singleton_name(), ''),
"is_partial": partial,
"doubled_methods": included_methods,
}
return _base_script_text.format(values)
func _is_method_eligible_for_doubling(parsed_script, parsed_method):
return (
!parsed_method.is_accessor()
and parsed_method.is_eligible_for_doubling()
and !_ignored_methods.has(parsed_script.resource, parsed_method.meta.name)
)
# Disable the native_method_override setting so that doubles do not generate
# errors or warnings when doubling with INCLUDE_NATIVE or when a method has
# been added because of param_count stub.
func _create_script_no_warnings(src):
var prev_native_override_value = null
var native_method_override = "debug/gdscript/warnings/native_method_override"
prev_native_override_value = ProjectSettings.get_setting(native_method_override)
ProjectSettings.set_setting(native_method_override, 0)
var DblClass = _utils.create_script_from_source(src)
ProjectSettings.set_setting(native_method_override, prev_native_override_value)
return DblClass
func _create_double(parsed, strategy, override_path, partial):
var path = ""
path = parsed.script_path
var dbl_src = ""
var included_methods = []
for method in parsed.get_local_methods():
if _is_method_eligible_for_doubling(parsed, method):
included_methods.append(method.meta.name)
var mthd = parsed.get_local_method(method.meta.name)
if parsed.is_native:
dbl_src += _get_func_text(method.meta, parsed.resource)
else:
dbl_src += _get_func_text(method.meta, path)
if strategy == _utils.DOUBLE_STRATEGY.INCLUDE_NATIVE:
for method in parsed.get_super_methods():
if _is_method_eligible_for_doubling(parsed, method):
included_methods.append(method.meta.name)
_stub_to_call_super(parsed, method.meta.name)
if parsed.is_native:
dbl_src += _get_func_text(method.meta, parsed.resource)
else:
dbl_src += _get_func_text(method.meta, path)
var base_script = _get_base_script_text(parsed, override_path, partial, included_methods)
dbl_src = base_script + "\n\n" + dbl_src
if print_source:
print(_utils.add_line_numbers(dbl_src))
var DblClass = _create_script_no_warnings(dbl_src)
if _stubber != null:
_stub_method_default_values(DblClass, parsed, strategy)
return DblClass
func _stub_method_default_values(which, parsed, strategy):
for method in parsed.get_local_methods():
if (
method.is_eligible_for_doubling()
&& !_ignored_methods.has(parsed.resource, method.meta.name)
):
_stubber.stub_defaults_from_meta(parsed.script_path, method.meta)
func _double_scene_and_script(scene, strategy, partial):
var to_return = PackedSceneDouble.new()
to_return.load_scene(scene.get_path())
var script_obj = GutUtils.get_scene_script_object(scene)
if script_obj != null:
var script_dbl = null
if partial:
script_dbl = _partial_double(script_obj, strategy, scene.get_path())
else:
script_dbl = _double(script_obj, strategy, scene.get_path())
to_return.set_script_obj(script_dbl)
return to_return
func _get_inst_id_ref_str(inst):
var ref_str = "null"
if inst:
ref_str = str("instance_from_id(", inst.get_instance_id(), ")")
return ref_str
func _get_func_text(method_hash, path):
var override_count = null
if _stubber != null:
override_count = _stubber.get_parameter_count(path, method_hash.name)
var text = _method_maker.get_function_text(method_hash, override_count) + "\n"
return text
func _parse_script(obj):
var parsed = null
if _utils.is_inner_class(obj):
if inner_class_registry.has(obj):
parsed = _script_collector.parse(inner_class_registry.get_base_resource(obj), obj)
else:
(
_lgr
. error(
"Doubling Inner Classes requires you register them first. Call register_inner_classes passing the script that contains the inner class."
)
)
else:
parsed = _script_collector.parse(obj)
return parsed
# Override path is used with scenes.
func _double(obj, strategy, override_path = null):
var parsed = _parse_script(obj)
if parsed != null:
return _create_double(parsed, strategy, override_path, false)
func _partial_double(obj, strategy, override_path = null):
var parsed = _parse_script(obj)
if parsed != null:
return _create_double(parsed, strategy, override_path, true)
# -------------------------
# Public
# -------------------------
# double a script/object
func double(obj, strategy = _strategy):
return _double(obj, strategy)
func partial_double(obj, strategy = _strategy):
return _partial_double(obj, strategy)
# double a scene
func double_scene(scene, strategy = _strategy):
return _double_scene_and_script(scene, strategy, false)
func partial_double_scene(scene, strategy = _strategy):
return _double_scene_and_script(scene, strategy, true)
func double_gdnative(which):
return _double(which, _utils.DOUBLE_STRATEGY.INCLUDE_NATIVE)
func partial_double_gdnative(which):
return _partial_double(which, _utils.DOUBLE_STRATEGY.INCLUDE_NATIVE)
func double_inner(parent, inner, strategy = _strategy):
var parsed = _script_collector.parse(parent, inner)
return _create_double(parsed, strategy, null, false)
func partial_double_inner(parent, inner, strategy = _strategy):
var parsed = _script_collector.parse(parent, inner)
return _create_double(parsed, strategy, null, true)
func add_ignored_method(obj, method_name):
_ignored_methods.add(obj, method_name)

View file

@ -1,33 +0,0 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://c8axnpxc0nrk4"
path="res://.godot/imported/AnonymousPro-Bold.ttf-9d8fef4d357af5b52cd60afbe608aa49.fontdata"
[deps]
source_file="res://addons/gut/fonts/AnonymousPro-Bold.ttf"
dest_files=["res://.godot/imported/AnonymousPro-Bold.ttf-9d8fef4d357af5b52cd60afbe608aa49.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

View file

@ -1,33 +0,0 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://msst1l2s2s"
path="res://.godot/imported/AnonymousPro-BoldItalic.ttf-4274bf704d3d6b9cd32c4f0754d8c83d.fontdata"
[deps]
source_file="res://addons/gut/fonts/AnonymousPro-BoldItalic.ttf"
dest_files=["res://.godot/imported/AnonymousPro-BoldItalic.ttf-4274bf704d3d6b9cd32c4f0754d8c83d.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

View file

@ -1,33 +0,0 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://hf5rdg67jcwc"
path="res://.godot/imported/AnonymousPro-Italic.ttf-9989590b02137b799e13d570de2a42c1.fontdata"
[deps]
source_file="res://addons/gut/fonts/AnonymousPro-Italic.ttf"
dest_files=["res://.godot/imported/AnonymousPro-Italic.ttf-9989590b02137b799e13d570de2a42c1.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

View file

@ -1,33 +0,0 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://c6c7gnx36opr0"
path="res://.godot/imported/AnonymousPro-Regular.ttf-856c843fd6f89964d2ca8d8ff1724f13.fontdata"
[deps]
source_file="res://addons/gut/fonts/AnonymousPro-Regular.ttf"
dest_files=["res://.godot/imported/AnonymousPro-Regular.ttf-856c843fd6f89964d2ca8d8ff1724f13.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

View file

@ -1,33 +0,0 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://bhjgpy1dovmyq"
path="res://.godot/imported/CourierPrime-Bold.ttf-1f003c66d63ebed70964e7756f4fa235.fontdata"
[deps]
source_file="res://addons/gut/fonts/CourierPrime-Bold.ttf"
dest_files=["res://.godot/imported/CourierPrime-Bold.ttf-1f003c66d63ebed70964e7756f4fa235.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

View file

@ -1,33 +0,0 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://n6mxiov5sbgc"
path="res://.godot/imported/CourierPrime-BoldItalic.ttf-65ebcc61dd5e1dfa8f96313da4ad7019.fontdata"
[deps]
source_file="res://addons/gut/fonts/CourierPrime-BoldItalic.ttf"
dest_files=["res://.godot/imported/CourierPrime-BoldItalic.ttf-65ebcc61dd5e1dfa8f96313da4ad7019.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

View file

@ -1,33 +0,0 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://mcht266g817e"
path="res://.godot/imported/CourierPrime-Italic.ttf-baa9156a73770735a0f72fb20b907112.fontdata"
[deps]
source_file="res://addons/gut/fonts/CourierPrime-Italic.ttf"
dest_files=["res://.godot/imported/CourierPrime-Italic.ttf-baa9156a73770735a0f72fb20b907112.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

View file

@ -1,33 +0,0 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://bnh0lslf4yh87"
path="res://.godot/imported/CourierPrime-Regular.ttf-3babe7e4a7a588dfc9a84c14b4f1fe23.fontdata"
[deps]
source_file="res://addons/gut/fonts/CourierPrime-Regular.ttf"
dest_files=["res://.godot/imported/CourierPrime-Regular.ttf-3babe7e4a7a588dfc9a84c14b4f1fe23.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

Binary file not shown.

View file

@ -1,33 +0,0 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://cmiuntu71oyl3"
path="res://.godot/imported/LobsterTwo-Bold.ttf-7c7f734103b58a32491a4788186f3dcb.fontdata"
[deps]
source_file="res://addons/gut/fonts/LobsterTwo-Bold.ttf"
dest_files=["res://.godot/imported/LobsterTwo-Bold.ttf-7c7f734103b58a32491a4788186f3dcb.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

View file

@ -1,33 +0,0 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://bll38n2ct6qme"
path="res://.godot/imported/LobsterTwo-BoldItalic.ttf-227406a33e84448e6aa974176016de19.fontdata"
[deps]
source_file="res://addons/gut/fonts/LobsterTwo-BoldItalic.ttf"
dest_files=["res://.godot/imported/LobsterTwo-BoldItalic.ttf-227406a33e84448e6aa974176016de19.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

View file

@ -1,33 +0,0 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://dis65h8wxc3f2"
path="res://.godot/imported/LobsterTwo-Italic.ttf-f93abf6c25390c85ad5fb6c4ee75159e.fontdata"
[deps]
source_file="res://addons/gut/fonts/LobsterTwo-Italic.ttf"
dest_files=["res://.godot/imported/LobsterTwo-Italic.ttf-f93abf6c25390c85ad5fb6c4ee75159e.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

View file

@ -1,33 +0,0 @@
[remap]
importer="font_data_dynamic"
type="FontFile"
uid="uid://5e8msj0ih2pv"
path="res://.godot/imported/LobsterTwo-Regular.ttf-f3fcfa01cd671c8da433dd875d0fe04b.fontdata"
[deps]
source_file="res://addons/gut/fonts/LobsterTwo-Regular.ttf"
dest_files=["res://.godot/imported/LobsterTwo-Regular.ttf-f3fcfa01cd671c8da433dd875d0fe04b.fontdata"]
[params]
Rendering=null
antialiasing=1
generate_mipmaps=false
multichannel_signed_distance_field=false
msdf_pixel_range=8
msdf_size=48
allow_system_fallback=true
force_autohinter=false
hinting=1
subpixel_positioning=1
oversampling=0.0
Fallbacks=null
fallbacks=[]
Compress=null
compress=true
preload=[]
language_support={}
script_support={}
opentype_features={}

View file

@ -1,94 +0,0 @@
Copyright (c) 2009, Mark Simonson (http://www.ms-studio.com, mark@marksimonson.com),
with Reserved Font Name Anonymous Pro.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View file

@ -1,90 +0,0 @@
@tool
extends Window
@onready var _ctrls = {
run_all = $Layout/CRunAll/ShortcutButton,
run_current_script = $Layout/CRunCurrentScript/ShortcutButton,
run_current_inner = $Layout/CRunCurrentInner/ShortcutButton,
run_current_test = $Layout/CRunCurrentTest/ShortcutButton,
panel_button = $Layout/CPanelButton/ShortcutButton,
}
func _ready():
for key in _ctrls:
var sc_button = _ctrls[key]
sc_button.connect("start_edit", _on_edit_start.bind(sc_button))
sc_button.connect("end_edit", _on_edit_end)
# show dialog when running scene from editor.
if get_parent() == get_tree().root:
popup_centered()
# ------------
# Events
# ------------
func _on_Hide_pressed():
hide()
func _on_edit_start(which):
for key in _ctrls:
var sc_button = _ctrls[key]
if sc_button != which:
sc_button.disable_set(true)
sc_button.disable_clear(true)
func _on_edit_end():
for key in _ctrls:
var sc_button = _ctrls[key]
sc_button.disable_set(false)
sc_button.disable_clear(false)
# ------------
# Public
# ------------
func get_run_all():
return _ctrls.run_all.get_shortcut()
func get_run_current_script():
return _ctrls.run_current_script.get_shortcut()
func get_run_current_inner():
return _ctrls.run_current_inner.get_shortcut()
func get_run_current_test():
return _ctrls.run_current_test.get_shortcut()
func get_panel_button():
return _ctrls.panel_button.get_shortcut()
func save_shortcuts(path):
var f = ConfigFile.new()
f.set_value("main", "run_all", _ctrls.run_all.get_shortcut())
f.set_value("main", "run_current_script", _ctrls.run_current_script.get_shortcut())
f.set_value("main", "run_current_inner", _ctrls.run_current_inner.get_shortcut())
f.set_value("main", "run_current_test", _ctrls.run_current_test.get_shortcut())
f.set_value("main", "panel_button", _ctrls.panel_button.get_shortcut())
f.save(path)
func load_shortcuts(path):
var emptyShortcut = Shortcut.new()
var f = ConfigFile.new()
f.load(path)
_ctrls.run_all.set_shortcut(f.get_value("main", "run_all", emptyShortcut))
_ctrls.run_current_script.set_shortcut(f.get_value("main", "run_current_script", emptyShortcut))
_ctrls.run_current_inner.set_shortcut(f.get_value("main", "run_current_inner", emptyShortcut))
_ctrls.run_current_test.set_shortcut(f.get_value("main", "run_current_test", emptyShortcut))
_ctrls.panel_button.set_shortcut(f.get_value("main", "panel_button", emptyShortcut))

View file

@ -1,150 +0,0 @@
[gd_scene load_steps=3 format=3 uid="uid://bsk32dh41b4gs"]
[ext_resource type="PackedScene" uid="uid://sfb1fw8j6ufu" path="res://addons/gut/gui/ShortcutButton.tscn" id="1"]
[ext_resource type="Script" path="res://addons/gut/gui/BottomPanelShortcuts.gd" id="2"]
[node name="BottomPanelShortcuts" type="Popup"]
title = "Shortcuts"
size = Vector2i(500, 350)
visible = true
exclusive = true
script = ExtResource("2")
[node name="Layout" type="VBoxContainer" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 5.0
offset_right = -5.0
offset_bottom = 2.0
[node name="TopPad" type="CenterContainer" parent="Layout"]
custom_minimum_size = Vector2(0, 5)
layout_mode = 2
[node name="Label2" type="Label" parent="Layout"]
custom_minimum_size = Vector2(0, 20)
layout_mode = 2
text = "Always Active"
[node name="ColorRect" type="ColorRect" parent="Layout/Label2"]
show_behind_parent = true
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
color = Color(0, 0, 0, 0.196078)
[node name="CPanelButton" type="HBoxContainer" parent="Layout"]
layout_mode = 2
[node name="Label" type="Label" parent="Layout/CPanelButton"]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_vertical = 7
text = "Show/Hide GUT Panel"
[node name="ShortcutButton" parent="Layout/CPanelButton" instance=ExtResource("1")]
layout_mode = 2
size_flags_horizontal = 3
[node name="GutPanelPad" type="CenterContainer" parent="Layout"]
custom_minimum_size = Vector2(0, 5)
layout_mode = 2
[node name="Label" type="Label" parent="Layout"]
custom_minimum_size = Vector2(0, 20)
layout_mode = 2
text = "Only Active When GUT Panel Shown"
[node name="ColorRect2" type="ColorRect" parent="Layout/Label"]
show_behind_parent = true
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
color = Color(0, 0, 0, 0.196078)
[node name="TopPad2" type="CenterContainer" parent="Layout"]
custom_minimum_size = Vector2(0, 5)
layout_mode = 2
[node name="CRunAll" type="HBoxContainer" parent="Layout"]
layout_mode = 2
[node name="Label" type="Label" parent="Layout/CRunAll"]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_vertical = 7
text = "Run All"
[node name="ShortcutButton" parent="Layout/CRunAll" instance=ExtResource("1")]
layout_mode = 2
size_flags_horizontal = 3
[node name="CRunCurrentScript" type="HBoxContainer" parent="Layout"]
layout_mode = 2
[node name="Label" type="Label" parent="Layout/CRunCurrentScript"]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_vertical = 7
text = "Run Current Script"
[node name="ShortcutButton" parent="Layout/CRunCurrentScript" instance=ExtResource("1")]
layout_mode = 2
size_flags_horizontal = 3
[node name="CRunCurrentInner" type="HBoxContainer" parent="Layout"]
layout_mode = 2
[node name="Label" type="Label" parent="Layout/CRunCurrentInner"]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_vertical = 7
text = "Run Current Inner Class"
[node name="ShortcutButton" parent="Layout/CRunCurrentInner" instance=ExtResource("1")]
layout_mode = 2
size_flags_horizontal = 3
[node name="CRunCurrentTest" type="HBoxContainer" parent="Layout"]
layout_mode = 2
[node name="Label" type="Label" parent="Layout/CRunCurrentTest"]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_vertical = 7
text = "Run Current Test"
[node name="ShortcutButton" parent="Layout/CRunCurrentTest" instance=ExtResource("1")]
layout_mode = 2
size_flags_horizontal = 3
[node name="CenterContainer2" type="CenterContainer" parent="Layout"]
custom_minimum_size = Vector2(0, 5)
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="ShiftDisclaimer" type="Label" parent="Layout"]
layout_mode = 2
text = "\"Shift\" cannot be the only modifier for a shortcut."
[node name="HBoxContainer" type="HBoxContainer" parent="Layout"]
layout_mode = 2
[node name="CenterContainer" type="CenterContainer" parent="Layout/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Hide" type="Button" parent="Layout/HBoxContainer"]
custom_minimum_size = Vector2(60, 30)
layout_mode = 2
text = "Close"
[node name="BottomPad" type="CenterContainer" parent="Layout"]
custom_minimum_size = Vector2(0, 10)
layout_mode = 2
size_flags_horizontal = 3
[connection signal="pressed" from="Layout/HBoxContainer/Hide" to="." method="_on_Hide_pressed"]

View file

@ -1,378 +0,0 @@
@tool
extends Control
const RUNNER_JSON_PATH = "res://.gut_editor_config.json"
const RESULT_FILE = "user://.gut_editor.bbcode"
const RESULT_JSON = "user://.gut_editor.json"
const SHORTCUTS_PATH = "res://.gut_editor_shortcuts.cfg"
var TestScript = load("res://addons/gut/test.gd")
var GutConfigGui = load("res://addons/gut/gui/gut_config_gui.gd")
var ScriptTextEditors = load("res://addons/gut/gui/script_text_editor_controls.gd")
var _interface = null
var _is_running = false
var _gut_config = load("res://addons/gut/gut_config.gd").new()
var _gut_config_gui = null
var _gut_plugin = null
var _light_color = Color(0, 0, 0, .5)
var _panel_button = null
var _last_selected_path = null
@onready var _ctrls = {
output = $layout/RSplit/CResults/TabBar/OutputText.get_rich_text_edit(),
output_ctrl = $layout/RSplit/CResults/TabBar/OutputText,
run_button = $layout/ControlBar/RunAll,
shortcuts_button = $layout/ControlBar/Shortcuts,
settings_button = $layout/ControlBar/Settings,
run_results_button = $layout/ControlBar/RunResultsBtn,
output_button = $layout/ControlBar/OutputBtn,
settings = $layout/RSplit/sc/Settings,
shortcut_dialog = $BottomPanelShortcuts,
light = $layout/RSplit/CResults/ControlBar/Light3D,
results =
{
bar = $layout/RSplit/CResults/ControlBar,
passing = $layout/RSplit/CResults/ControlBar/Passing/value,
failing = $layout/RSplit/CResults/ControlBar/Failing/value,
pending = $layout/RSplit/CResults/ControlBar/Pending/value,
errors = $layout/RSplit/CResults/ControlBar/Errors/value,
warnings = $layout/RSplit/CResults/ControlBar/Warnings/value,
orphans = $layout/RSplit/CResults/ControlBar/Orphans/value
},
run_at_cursor = $layout/ControlBar/RunAtCursor,
run_results = $layout/RSplit/CResults/TabBar/RunResults
}
func _init():
_gut_config.load_panel_options(RUNNER_JSON_PATH)
func _ready():
_ctrls.results.bar.connect("draw", _on_results_bar_draw.bind(_ctrls.results.bar))
hide_settings(!_ctrls.settings_button.button_pressed)
_gut_config_gui = GutConfigGui.new(_ctrls.settings)
_gut_config_gui.set_options(_gut_config.options)
_apply_options_to_controls()
_ctrls.shortcuts_button.icon = get_theme_icon("Shortcut", "EditorIcons")
_ctrls.settings_button.icon = get_theme_icon("Tools", "EditorIcons")
_ctrls.run_results_button.icon = get_theme_icon("AnimationTrackGroup", "EditorIcons") # Tree
_ctrls.output_button.icon = get_theme_icon("Font", "EditorIcons")
_ctrls.run_results.set_output_control(_ctrls.output_ctrl)
_ctrls.run_results.set_font(
_gut_config.options.panel_options.font_name, _gut_config.options.panel_options.font_size
)
var check_import = load("res://addons/gut/images/red.png")
if check_import == null:
_ctrls.run_results.add_centered_text(
"GUT got some new images that are not imported yet. Please restart Godot."
)
print("GUT got some new images that are not imported yet. Please restart Godot.")
else:
_ctrls.run_results.add_centered_text("Let's run some tests!")
func _apply_options_to_controls():
hide_settings(_gut_config.options.panel_options.hide_settings)
hide_result_tree(_gut_config.options.panel_options.hide_result_tree)
hide_output_text(_gut_config.options.panel_options.hide_output_text)
_ctrls.output_ctrl.set_use_colors(_gut_config.options.panel_options.use_colors)
_ctrls.output_ctrl.set_all_fonts(_gut_config.options.panel_options.font_name)
_ctrls.output_ctrl.set_font_size(_gut_config.options.panel_options.font_size)
_ctrls.run_results.set_font(
_gut_config.options.panel_options.font_name, _gut_config.options.panel_options.font_size
)
_ctrls.run_results.set_show_orphans(!_gut_config.options.hide_orphans)
func _process(delta):
if _is_running:
if !_interface.is_playing_scene():
_is_running = false
_ctrls.output_ctrl.add_text("\ndone")
load_result_output()
_gut_plugin.make_bottom_panel_item_visible(self)
# ---------------
# Private
# ---------------
func load_shortcuts():
_ctrls.shortcut_dialog.load_shortcuts(SHORTCUTS_PATH)
_apply_shortcuts()
func _is_test_script(script):
var from = script.get_base_script()
while from and from.resource_path != "res://addons/gut/test.gd":
from = from.get_base_script()
return from != null
func _show_errors(errs):
_ctrls.output_ctrl.clear()
var text = "Cannot run tests, you have a configuration error:\n"
for e in errs:
text += str("* ", e, "\n")
text += "Check your settings ----->"
_ctrls.output_ctrl.add_text(text)
hide_output_text(false)
hide_settings(false)
func _save_config():
_gut_config.options = _gut_config_gui.get_options(_gut_config.options)
_gut_config.options.panel_options.hide_settings = !_ctrls.settings_button.button_pressed
_gut_config.options.panel_options.hide_result_tree = !_ctrls.run_results_button.button_pressed
_gut_config.options.panel_options.hide_output_text = !_ctrls.output_button.button_pressed
_gut_config.options.panel_options.use_colors = _ctrls.output_ctrl.get_use_colors()
var w_result = _gut_config.write_options(RUNNER_JSON_PATH)
if w_result != OK:
push_error(str("Could not write options to ", RUNNER_JSON_PATH, ": ", w_result))
return
func _run_tests():
var issues = _gut_config_gui.get_config_issues()
if issues.size() > 0:
_show_errors(issues)
return
write_file(RESULT_FILE, "Run in progress")
_save_config()
_apply_options_to_controls()
_ctrls.output_ctrl.clear()
_ctrls.run_results.clear()
_ctrls.run_results.add_centered_text("Running...")
_interface.play_custom_scene("res://addons/gut/gui/GutRunner.tscn")
_is_running = true
_ctrls.output_ctrl.add_text("Running...")
func _apply_shortcuts():
_ctrls.run_button.shortcut = _ctrls.shortcut_dialog.get_run_all()
_ctrls.run_at_cursor.get_script_button().shortcut = (
_ctrls.shortcut_dialog.get_run_current_script()
)
_ctrls.run_at_cursor.get_inner_button().shortcut = (
_ctrls.shortcut_dialog.get_run_current_inner()
)
_ctrls.run_at_cursor.get_test_button().shortcut = _ctrls.shortcut_dialog.get_run_current_test()
_panel_button.shortcut = _ctrls.shortcut_dialog.get_panel_button()
func _run_all():
_gut_config.options.selected = null
_gut_config.options.inner_class = null
_gut_config.options.unit_test_name = null
_run_tests()
# ---------------
# Events
# ---------------
func _on_results_bar_draw(bar):
bar.draw_rect(Rect2(Vector2(0, 0), bar.size), Color(0, 0, 0, .2))
func _on_Light_draw():
var l = _ctrls.light
l.draw_circle(Vector2(l.size.x / 2, l.size.y / 2), l.size.x / 2, _light_color)
func _on_editor_script_changed(script):
if script:
set_current_script(script)
func _on_RunAll_pressed():
_run_all()
func _on_Shortcuts_pressed():
_ctrls.shortcut_dialog.popup_centered()
func _on_bottom_panel_shortcuts_visibility_changed():
_apply_shortcuts()
_ctrls.shortcut_dialog.save_shortcuts(SHORTCUTS_PATH)
func _on_RunAtCursor_run_tests(what):
_gut_config.options.selected = what.script
_gut_config.options.inner_class = what.inner_class
_gut_config.options.unit_test_name = what.test_method
_run_tests()
func _on_Settings_pressed():
hide_settings(!_ctrls.settings_button.button_pressed)
_save_config()
func _on_OutputBtn_pressed():
hide_output_text(!_ctrls.output_button.button_pressed)
_save_config()
func _on_RunResultsBtn_pressed():
hide_result_tree(!_ctrls.run_results_button.button_pressed)
_save_config()
# Currently not used, but will be when I figure out how to put
# colors into the text results
func _on_UseColors_pressed():
pass
# ---------------
# Public
# ---------------
func hide_result_tree(should):
_ctrls.run_results.visible = !should
_ctrls.run_results_button.button_pressed = !should
func hide_settings(should):
var s_scroll = _ctrls.settings.get_parent()
s_scroll.visible = !should
# collapse only collapses the first control, so we move
# settings around to be the collapsed one
if should:
s_scroll.get_parent().move_child(s_scroll, 0)
else:
s_scroll.get_parent().move_child(s_scroll, 1)
$layout/RSplit.collapsed = should
_ctrls.settings_button.button_pressed = !should
func hide_output_text(should):
$layout/RSplit/CResults/TabBar/OutputText.visible = !should
_ctrls.output_button.button_pressed = !should
func load_result_output():
_ctrls.output_ctrl.load_file(RESULT_FILE)
var summary = get_file_as_text(RESULT_JSON)
var test_json_conv = JSON.new()
if test_json_conv.parse(summary) != OK:
return
var results = test_json_conv.get_data()
_ctrls.run_results.load_json_results(results)
var summary_json = results["test_scripts"]["props"]
_ctrls.results.passing.text = str(summary_json.passing)
_ctrls.results.passing.get_parent().visible = true
_ctrls.results.failing.text = str(summary_json.failures)
_ctrls.results.failing.get_parent().visible = true
_ctrls.results.pending.text = str(summary_json.pending)
_ctrls.results.pending.get_parent().visible = _ctrls.results.pending.text != "0"
_ctrls.results.errors.text = str(summary_json.errors)
_ctrls.results.errors.get_parent().visible = _ctrls.results.errors.text != "0"
_ctrls.results.warnings.text = str(summary_json.warnings)
_ctrls.results.warnings.get_parent().visible = _ctrls.results.warnings.text != "0"
_ctrls.results.orphans.text = str(summary_json.orphans)
_ctrls.results.orphans.get_parent().visible = (
_ctrls.results.orphans.text != "0" and !_gut_config.options.hide_orphans
)
if summary_json.tests == 0:
_light_color = Color(1, 0, 0, .75)
elif summary_json.failures != 0:
_light_color = Color(1, 0, 0, .75)
elif summary_json.pending != 0:
_light_color = Color(1, 1, 0, .75)
else:
_light_color = Color(0, 1, 0, .75)
_ctrls.light.visible = true
_ctrls.light.queue_redraw()
func set_current_script(script):
if script:
if _is_test_script(script):
var file = script.resource_path.get_file()
_last_selected_path = script.resource_path.get_file()
_ctrls.run_at_cursor.activate_for_script(script.resource_path)
func set_interface(value):
_interface = value
_interface.get_script_editor().connect(
"editor_script_changed", Callable(self, "_on_editor_script_changed")
)
var ste = ScriptTextEditors.new(_interface.get_script_editor())
_ctrls.run_results.set_interface(_interface)
_ctrls.run_results.set_script_text_editors(ste)
_ctrls.run_at_cursor.set_script_text_editors(ste)
set_current_script(_interface.get_script_editor().get_current_script())
func set_plugin(value):
_gut_plugin = value
func set_panel_button(value):
_panel_button = value
# ------------------------------------------------------------------------------
# Write a file.
# ------------------------------------------------------------------------------
func write_file(path, content):
var f = FileAccess.open(path, FileAccess.WRITE)
if f != null:
f.store_string(content)
f = null
return FileAccess.get_open_error()
# ------------------------------------------------------------------------------
# Returns the text of a file or an empty string if the file could not be opened.
# ------------------------------------------------------------------------------
func get_file_as_text(path):
var to_return = ""
var f = FileAccess.open(path, FileAccess.READ)
if f != null:
to_return = f.get_as_text()
f = null
return to_return
# ------------------------------------------------------------------------------
# return if_null if value is null otherwise return value
# ------------------------------------------------------------------------------
func nvl(value, if_null):
if value == null:
return if_null
else:
return value

View file

@ -1,249 +0,0 @@
[gd_scene load_steps=10 format=3 uid="uid://b3bostcslstem"]
[ext_resource type="Script" path="res://addons/gut/gui/GutBottomPanel.gd" id="1"]
[ext_resource type="PackedScene" uid="uid://bsk32dh41b4gs" path="res://addons/gut/gui/BottomPanelShortcuts.tscn" id="2"]
[ext_resource type="PackedScene" uid="uid://0yunjxtaa8iw" path="res://addons/gut/gui/RunAtCursor.tscn" id="3"]
[ext_resource type="Texture2D" uid="uid://cr6tvdv0ve6cv" path="res://addons/gut/gui/play.png" id="4"]
[ext_resource type="PackedScene" uid="uid://4gyyn12um08h" path="res://addons/gut/gui/RunResults.tscn" id="5"]
[ext_resource type="PackedScene" uid="uid://bqmo4dj64c7yl" path="res://addons/gut/gui/OutputText.tscn" id="6"]
[sub_resource type="Shortcut" id="9"]
[sub_resource type="Image" id="Image_abbh7"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 93, 93, 55, 255, 97, 97, 58, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_x655i"]
image = SubResource("Image_abbh7")
[node name="GutBottomPanel" type="Control"]
custom_minimum_size = Vector2(250, 250)
layout_mode = 3
anchor_left = -0.0025866
anchor_top = -0.00176575
anchor_right = 0.997413
anchor_bottom = 0.998234
offset_left = 2.64868
offset_top = 1.05945
offset_right = 2.64862
offset_bottom = 1.05945
script = ExtResource("1")
[node name="layout" type="VBoxContainer" parent="."]
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
[node name="ControlBar" type="HBoxContainer" parent="layout"]
layout_mode = 2
[node name="RunAll" type="Button" parent="layout/ControlBar"]
layout_mode = 2
size_flags_vertical = 11
shortcut = SubResource("9")
text = "Run All"
icon = ExtResource("4")
[node name="Label" type="Label" parent="layout/ControlBar"]
layout_mode = 2
mouse_filter = 1
text = "Current: "
[node name="RunAtCursor" parent="layout/ControlBar" instance=ExtResource("3")]
layout_mode = 2
[node name="CenterContainer2" type="CenterContainer" parent="layout/ControlBar"]
layout_mode = 2
size_flags_horizontal = 3
[node name="Sep1" type="ColorRect" parent="layout/ControlBar"]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="RunResultsBtn" type="Button" parent="layout/ControlBar"]
layout_mode = 2
toggle_mode = true
button_pressed = true
icon = SubResource("ImageTexture_x655i")
[node name="OutputBtn" type="Button" parent="layout/ControlBar"]
layout_mode = 2
toggle_mode = true
icon = SubResource("ImageTexture_x655i")
[node name="Settings" type="Button" parent="layout/ControlBar"]
layout_mode = 2
toggle_mode = true
button_pressed = true
icon = SubResource("ImageTexture_x655i")
[node name="Sep2" type="ColorRect" parent="layout/ControlBar"]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="Shortcuts" type="Button" parent="layout/ControlBar"]
layout_mode = 2
size_flags_vertical = 11
icon = SubResource("ImageTexture_x655i")
[node name="RSplit" type="HSplitContainer" parent="layout"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="CResults" type="VBoxContainer" parent="layout/RSplit"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="ControlBar" type="HBoxContainer" parent="layout/RSplit/CResults"]
layout_mode = 2
[node name="Sep2" type="ColorRect" parent="layout/RSplit/CResults/ControlBar"]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="Light3D" type="Control" parent="layout/RSplit/CResults/ControlBar"]
custom_minimum_size = Vector2(30, 30)
layout_mode = 2
[node name="Passing" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
visible = false
layout_mode = 2
[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Passing"]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Passing"]
layout_mode = 2
text = "Passing"
[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Passing"]
layout_mode = 2
text = "---"
[node name="Failing" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
visible = false
layout_mode = 2
[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Failing"]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Failing"]
layout_mode = 2
text = "Failing"
[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Failing"]
layout_mode = 2
text = "---"
[node name="Pending" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
visible = false
layout_mode = 2
[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Pending"]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Pending"]
layout_mode = 2
text = "Pending"
[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Pending"]
layout_mode = 2
text = "---"
[node name="Orphans" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
visible = false
layout_mode = 2
[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Orphans"]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Orphans"]
layout_mode = 2
text = "Orphans"
[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Orphans"]
layout_mode = 2
text = "---"
[node name="Errors" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
visible = false
layout_mode = 2
[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Errors"]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Errors"]
layout_mode = 2
text = "Errors"
[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Errors"]
layout_mode = 2
text = "---"
[node name="Warnings" type="HBoxContainer" parent="layout/RSplit/CResults/ControlBar"]
visible = false
layout_mode = 2
[node name="Sep" type="ColorRect" parent="layout/RSplit/CResults/ControlBar/Warnings"]
custom_minimum_size = Vector2(1, 2.08165e-12)
layout_mode = 2
[node name="label" type="Label" parent="layout/RSplit/CResults/ControlBar/Warnings"]
layout_mode = 2
text = "Warnings"
[node name="value" type="Label" parent="layout/RSplit/CResults/ControlBar/Warnings"]
layout_mode = 2
text = "---"
[node name="CenterContainer" type="CenterContainer" parent="layout/RSplit/CResults/ControlBar"]
layout_mode = 2
size_flags_horizontal = 3
[node name="TabBar" type="HSplitContainer" parent="layout/RSplit/CResults"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="RunResults" parent="layout/RSplit/CResults/TabBar" instance=ExtResource("5")]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="OutputText" parent="layout/RSplit/CResults/TabBar" instance=ExtResource("6")]
visible = false
layout_mode = 2
[node name="sc" type="ScrollContainer" parent="layout/RSplit"]
custom_minimum_size = Vector2(500, 2.08165e-12)
layout_mode = 2
size_flags_vertical = 3
[node name="Settings" type="VBoxContainer" parent="layout/RSplit/sc"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="BottomPanelShortcuts" parent="." instance=ExtResource("2")]
visible = false
[connection signal="pressed" from="layout/ControlBar/RunAll" to="." method="_on_RunAll_pressed"]
[connection signal="run_tests" from="layout/ControlBar/RunAtCursor" to="." method="_on_RunAtCursor_run_tests"]
[connection signal="pressed" from="layout/ControlBar/RunResultsBtn" to="." method="_on_RunResultsBtn_pressed"]
[connection signal="pressed" from="layout/ControlBar/OutputBtn" to="." method="_on_OutputBtn_pressed"]
[connection signal="pressed" from="layout/ControlBar/Settings" to="." method="_on_Settings_pressed"]
[connection signal="pressed" from="layout/ControlBar/Shortcuts" to="." method="_on_Shortcuts_pressed"]
[connection signal="draw" from="layout/RSplit/CResults/ControlBar/Light3D" to="." method="_on_Light_draw"]
[connection signal="visibility_changed" from="BottomPanelShortcuts" to="." method="_on_bottom_panel_shortcuts_visibility_changed"]

View file

@ -1,322 +0,0 @@
@tool
extends Control
const RUNNER_JSON_PATH = "res://.gut_editor_config.json"
var GutConfig = load("res://addons/gut/gut_config.gd")
var GutRunnerScene = load("res://addons/gut/gui/GutRunner.tscn")
var GutConfigGui = load("res://addons/gut/gui/gut_config_gui.gd")
var _config = GutConfig.new()
var _config_gui = null
var _gut_runner = GutRunnerScene.instantiate()
var _has_connected = false
var _tree_root: TreeItem = null
var _script_icon = load("res://addons/gut/images/Script.svg")
var _folder_icon = load("res://addons/gut/images/Folder.svg")
var _tree_scripts = {}
var _tree_directories = {}
const TREE_SCRIPT = "Script"
const TREE_DIR = "Directory"
@onready var _ctrls = {
run_tests_button = $VBox/Buttons/RunTests,
run_selected = $VBox/Buttons/RunSelected,
test_tree = $VBox/Tabs/Tests,
settings_vbox = $VBox/Tabs/SettingsScroll/Settings,
tabs = $VBox/Tabs,
bg = $Bg
}
@export var bg_color: Color = Color(.36, .36, .36):
get:
return bg_color
set(val):
bg_color = val
if is_inside_tree():
$Bg.color = bg_color
func _ready():
if Engine.is_editor_hint():
return
$Bg.color = bg_color
_ctrls.tabs.set_tab_title(0, "Tests")
_ctrls.tabs.set_tab_title(1, "Settings")
_config_gui = GutConfigGui.new(_ctrls.settings_vbox)
_ctrls.test_tree.hide_root = true
# Stop tests from kicking off when the runner is "ready" and
# prevents it from writing results file that is used by
# the panel.
_gut_runner.set_cmdln_mode(true)
add_child(_gut_runner)
# Becuase of the janky _utils psuedo-global script, we cannot
# do all this in _ready. If we do this in _ready, it generates
# a bunch of errors. The errors don't matter, but it looks bad.
call_deferred("_post_ready")
func _draw():
if Engine.is_editor_hint():
return
var gut = _gut_runner.get_gut()
if !gut.is_running():
var r = Rect2(Vector2(0, 0), get_rect().size)
draw_rect(r, Color.BLACK, false, 2)
func _post_ready():
var gut = _gut_runner.get_gut()
gut.start_run.connect(_on_gut_run_started)
gut.end_run.connect(_on_gut_run_ended)
_refresh_tree_and_settings()
func _set_meta_for_script_tree_item(item, script, test = null):
var meta = {
type = TREE_SCRIPT, script = script.path, inner_class = script.inner_class_name, test = ""
}
if test != null:
meta.test = test.name
item.set_metadata(0, meta)
func _set_meta_for_directory_tree_item(item, path, temp_item):
var meta = {type = TREE_DIR, path = path, temp_item = temp_item}
item.set_metadata(0, meta)
func _get_script_tree_item(script, parent_item):
if !_tree_scripts.has(script.path):
var item = _ctrls.test_tree.create_item(parent_item)
item.set_text(0, script.path.get_file())
item.set_icon(0, _script_icon)
_tree_scripts[script.path] = item
_set_meta_for_script_tree_item(item, script)
return _tree_scripts[script.path]
func _get_directory_tree_item(path):
var parent = _tree_root
if !_tree_directories.has(path):
var item: TreeItem = null
if parent != _tree_root:
item = parent.create_child(0)
else:
item = parent.create_child()
_tree_directories[path] = item
item.collapsed = false
item.set_text(0, path)
item.set_icon(0, _folder_icon)
item.set_icon_modulate(0, Color.ROYAL_BLUE)
# temp_item is used in calls with move_before since you must use
# move_before or move_after to reparent tree items. This ensures that
# there is an item on all directories. These are deleted later.
var temp_item = item.create_child()
temp_item.set_text(0, "<temp>")
_set_meta_for_directory_tree_item(item, path, temp_item)
return _tree_directories[path]
func _find_dir_item_to_move_before(path):
var max_matching_len = 0
var best_parent = null
# Go through all the directory items finding the one that has the longest
# path that contains our path.
for key in _tree_directories.keys():
if path != key and path.begins_with(key) and key.length() > max_matching_len:
max_matching_len = key.length()
best_parent = _tree_directories[key]
var to_return = null
if best_parent != null:
to_return = best_parent.get_metadata(0).temp_item
return to_return
func _reorder_dir_items():
var the_keys = _tree_directories.keys()
the_keys.sort()
for key in _tree_directories.keys():
var to_move = _tree_directories[key]
to_move.collapsed = false
var move_before = _find_dir_item_to_move_before(key)
if move_before != null:
to_move.move_before(move_before)
var new_text = key.substr(move_before.get_parent().get_metadata(0).path.length())
to_move.set_text(0, new_text)
func _remove_dir_temp_items():
for key in _tree_directories.keys():
var item = _tree_directories[key].get_metadata(0).temp_item
_tree_directories[key].remove_child(item)
func _add_dir_and_script_tree_items():
var tree: Tree = _ctrls.test_tree
tree.clear()
_tree_root = _ctrls.test_tree.create_item()
var scripts = _gut_runner.get_gut().get_test_collector().scripts
for script in scripts:
var dir_item = _get_directory_tree_item(script.path.get_base_dir())
var item = _get_script_tree_item(script, dir_item)
if script.inner_class_name != "":
var inner_item = tree.create_item(item)
inner_item.set_text(0, script.inner_class_name)
_set_meta_for_script_tree_item(inner_item, script)
item = inner_item
for test in script.tests:
var test_item = tree.create_item(item)
test_item.set_text(0, test.name)
_set_meta_for_script_tree_item(test_item, script, test)
func _populate_tree():
_add_dir_and_script_tree_items()
_tree_root.set_collapsed_recursive(true)
_tree_root.set_collapsed(false)
_reorder_dir_items()
_remove_dir_temp_items()
func _refresh_tree_and_settings():
if _config.options.has("panel_options"):
_config_gui.set_options(_config.options)
_config.apply_options(_gut_runner.get_gut())
_gut_runner.set_gut_config(_config)
_populate_tree()
# ---------------------------
# Events
# ---------------------------
func _on_gut_run_started():
_ctrls.run_tests_button.disabled = true
_ctrls.run_selected.visible = false
_ctrls.tabs.visible = false
_ctrls.bg.visible = false
_ctrls.run_tests_button.text = "Running"
queue_redraw()
func _on_gut_run_ended():
_ctrls.run_tests_button.disabled = false
_ctrls.run_selected.visible = true
_ctrls.tabs.visible = true
_ctrls.bg.visible = true
_ctrls.run_tests_button.text = "Run All"
queue_redraw()
func _on_run_tests_pressed():
run_all()
func _on_run_selected_pressed():
run_selected()
func _on_tests_item_activated():
run_selected()
# ---------------------------
# Public
# ---------------------------
func get_gut():
return _gut_runner.get_gut()
func get_config():
return _config
func run_all():
_config.options.selected = ""
_config.options.inner_class_name = ""
_config.options.unit_test_name = ""
run_tests()
func run_tests(options = null):
if options == null:
_config.options = _config_gui.get_options(_config.options)
else:
_config.options = options
_gut_runner.get_gut().get_test_collector().clear()
_gut_runner.set_gut_config(_config)
_gut_runner.run_tests()
func run_selected():
var sel_item = _ctrls.test_tree.get_selected()
if sel_item == null:
return
var options = _config_gui.get_options(_config.options)
var meta = sel_item.get_metadata(0)
if meta.type == TREE_SCRIPT:
options.selected = meta.script.get_file()
options.inner_class_name = meta.inner_class
options.unit_test_name = meta.test
elif meta.type == TREE_DIR:
options.dirs = [meta.path]
options.include_subdirectories = true
options.selected = ""
options.inner_class_name = ""
options.unit_test_name = ""
run_tests(options)
func load_config_file(path):
_config.load_panel_options(path)
_config.options.selected = ""
_config.options.inner_class_name = ""
_config.options.unit_test_name = ""
# ##############################################################################
# The MIT License (MIT)
# =====================
#
# Copyright (c) 2023 Tom "Butch" Wesley
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ##############################################################################

View file

@ -1,63 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://4jb53yqktyfg"]
[ext_resource type="Script" path="res://addons/gut/gui/GutControl.gd" id="1_eprql"]
[node name="GutControl" type="Control"]
layout_mode = 3
anchors_preset = 0
offset_right = 295.0
offset_bottom = 419.0
script = ExtResource("1_eprql")
[node name="Bg" type="ColorRect" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0.36, 0.36, 0.36, 1)
[node name="VBox" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Tabs" type="TabContainer" parent="VBox"]
layout_mode = 2
size_flags_vertical = 3
[node name="Tests" type="Tree" parent="VBox/Tabs"]
layout_mode = 2
size_flags_vertical = 3
hide_root = true
[node name="SettingsScroll" type="ScrollContainer" parent="VBox/Tabs"]
visible = false
layout_mode = 2
size_flags_vertical = 3
[node name="Settings" type="VBoxContainer" parent="VBox/Tabs/SettingsScroll"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Buttons" type="HBoxContainer" parent="VBox"]
layout_mode = 2
[node name="RunTests" type="Button" parent="VBox/Buttons"]
layout_mode = 2
size_flags_horizontal = 3
text = "Run All"
[node name="RunSelected" type="Button" parent="VBox/Buttons"]
layout_mode = 2
size_flags_horizontal = 3
text = "Run Selected"
[connection signal="item_activated" from="VBox/Tabs/Tests" to="." method="_on_tests_item_activated"]
[connection signal="pressed" from="VBox/Buttons/RunTests" to="." method="_on_run_tests_pressed"]
[connection signal="pressed" from="VBox/Buttons/RunSelected" to="." method="_on_run_selected_pressed"]

View file

@ -1,170 +0,0 @@
# ##############################################################################
# This class joins together GUT, GUT Gui, GutConfig and is the main way to
# run a test suite.
#
# This creates its own instance of gut.gd that it manages.
# Use set_gut_config to set the gut_config.gd that should be used to configure
# gut.
# This will create a GUI and wire it up and apply gut_config.gd options.
#
# Running tests:
# By default, this will run tests once this control has been added to the tree.
# You can override this by setting auto_run_tests to false prior to adding
# this to the tree. To run tests manually, call run_tests.
#
# ##############################################################################
extends Node2D
var Gut = load("res://addons/gut/gut.gd")
var ResultExporter = load("res://addons/gut/result_exporter.gd")
var GutConfig = load("res://addons/gut/gut_config.gd")
const RUNNER_JSON_PATH = "res://.gut_editor_config.json"
const RESULT_FILE = "user://.gut_editor.bbcode"
const RESULT_JSON = "user://.gut_editor.json"
var _gut_config = null
var _gut = null
var _wrote_results = false
# Flag for when this is being used at the command line. Otherwise it is
# assumed this is being used by the panel and being launched with
# play_custom_scene
var _cmdln_mode = false
@onready var _gut_layer = $GutLayer
@onready var _gui = $GutLayer/GutScene
# When true, tests will be kicked off in _ready.
var auto_run_tests = true
func _ready():
if _gut_config == null:
_gut_config = GutConfig.new()
_gut_config.load_panel_options(RUNNER_JSON_PATH)
# The command line will call run_tests on its own. When used from the panel
# we have to kick off the tests ourselves b/c there's no way I know of to
# interact with the scene that was run via play_custom_scene.
if !_cmdln_mode and auto_run_tests:
call_deferred("run_tests")
func _lazy_make_gut():
if _gut == null:
_gut = Gut.new()
func _setup_gui(show_gui):
if show_gui:
_gui.gut = _gut
var printer = _gut.logger.get_printer("gui")
printer.set_textbox(_gui.get_textbox())
else:
_gut.logger.disable_printer("gui", true)
_gui.visible = false
var opts = _gut_config.options
_gui.set_font_size(opts.font_size)
_gui.set_font(opts.font_name)
if opts.font_color != null and opts.font_color.is_valid_html_color():
_gui.set_default_font_color(Color(opts.font_color))
if opts.background_color != null and opts.background_color.is_valid_html_color():
_gui.set_background_color(Color(opts.background_color))
_gui.set_opacity(min(1.0, float(opts.opacity) / 100))
# if(opts.should_maximize):
# _tester.maximize()
_gui.use_compact_mode(opts.compact_mode)
func _write_results():
var content = _gui.get_textbox().get_parsed_text() #_gut.logger.get_gui_bbcode()
var f = FileAccess.open(RESULT_FILE, FileAccess.WRITE)
if f != null:
f.store_string(content)
f = null # closes file
else:
push_error("Could not save bbcode, result = ", FileAccess.get_open_error())
var exporter = ResultExporter.new()
var f_result = exporter.write_json_file(_gut, RESULT_JSON)
_wrote_results = true
func _exit_tree():
if !_wrote_results and !_cmdln_mode:
_write_results()
func _on_tests_finished(should_exit, should_exit_on_success):
_write_results()
if should_exit:
get_tree().quit()
elif should_exit_on_success and _gut.get_fail_count() == 0:
get_tree().quit()
func run_tests(show_gui = true):
_lazy_make_gut()
_setup_gui(show_gui)
_gut.add_children_to = self
if _gut.get_parent() == null:
if _gut_config.options.gut_on_top:
_gut_layer.add_child(_gut)
else:
add_child(_gut)
if !_cmdln_mode:
_gut.end_run.connect(
_on_tests_finished.bind(
_gut_config.options.should_exit, _gut_config.options.should_exit_on_success
)
)
_gut_config.apply_options(_gut)
var run_rest_of_scripts = _gut_config.options.unit_test_name == ""
_gut.test_scripts(run_rest_of_scripts)
func get_gut():
_lazy_make_gut()
return _gut
func set_gut_config(which):
_gut_config = which
func set_cmdln_mode(is_it):
_cmdln_mode = is_it
# ##############################################################################
# The MIT License (MIT)
# =====================
#
# Copyright (c) 2023 Tom "Butch" Wesley
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ##############################################################################

View file

@ -1,12 +0,0 @@
[gd_scene load_steps=3 format=3 uid="uid://bqy3ikt6vu4b5"]
[ext_resource type="Script" path="res://addons/gut/gui/GutRunner.gd" id="1"]
[ext_resource type="PackedScene" uid="uid://m28heqtswbuq" path="res://addons/gut/GutScene.tscn" id="2_6ruxb"]
[node name="GutRunner" type="Node2D"]
script = ExtResource("1")
[node name="GutLayer" type="CanvasLayer" parent="."]
layer = 128
[node name="GutScene" parent="GutLayer" instance=ExtResource("2_6ruxb")]

File diff suppressed because one or more lines are too long

View file

@ -1,161 +0,0 @@
[gd_scene load_steps=5 format=3 uid="uid://cnqqdfsn80ise"]
[ext_resource type="Theme" uid="uid://cstkhwkpajvqu" path="res://addons/gut/gui/GutSceneTheme.tres" id="1_farmq"]
[ext_resource type="FontFile" uid="uid://bnh0lslf4yh87" path="res://addons/gut/fonts/CourierPrime-Regular.ttf" id="2_a2e2l"]
[ext_resource type="Script" path="res://addons/gut/gui/gut_gui.gd" id="2_eokrf"]
[ext_resource type="PackedScene" uid="uid://bvrqqgjpyouse" path="res://addons/gut/gui/ResizeHandle.tscn" id="4_xrhva"]
[node name="Min" type="Panel"]
clip_contents = true
custom_minimum_size = Vector2(280, 145)
offset_right = 280.0
offset_bottom = 145.0
theme = ExtResource("1_farmq")
script = ExtResource("2_eokrf")
[node name="MainBox" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
metadata/_edit_layout_mode = 1
[node name="TitleBar" type="Panel" parent="MainBox"]
custom_minimum_size = Vector2(0, 25)
layout_mode = 2
[node name="TitleBox" type="HBoxContainer" parent="MainBox/TitleBar"]
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
offset_top = 2.0
offset_bottom = 3.0
grow_horizontal = 2
grow_vertical = 2
metadata/_edit_layout_mode = 1
[node name="Spacer1" type="CenterContainer" parent="MainBox/TitleBar/TitleBox"]
layout_mode = 2
size_flags_horizontal = 3
[node name="Title" type="Label" parent="MainBox/TitleBar/TitleBox"]
layout_mode = 2
text = "Title"
[node name="Spacer2" type="CenterContainer" parent="MainBox/TitleBar/TitleBox"]
layout_mode = 2
size_flags_horizontal = 3
[node name="TimeLabel" type="Label" parent="MainBox/TitleBar/TitleBox"]
layout_mode = 2
text = "0.000s"
[node name="Body" type="HBoxContainer" parent="MainBox"]
layout_mode = 2
size_flags_vertical = 3
[node name="LeftMargin" type="CenterContainer" parent="MainBox/Body"]
custom_minimum_size = Vector2(5, 0)
layout_mode = 2
[node name="BodyRows" type="VBoxContainer" parent="MainBox/Body"]
layout_mode = 2
size_flags_horizontal = 3
[node name="ProgressBars" type="HBoxContainer" parent="MainBox/Body/BodyRows"]
layout_mode = 2
size_flags_horizontal = 3
[node name="HBoxContainer" type="HBoxContainer" parent="MainBox/Body/BodyRows/ProgressBars"]
layout_mode = 2
size_flags_horizontal = 3
[node name="Label" type="Label" parent="MainBox/Body/BodyRows/ProgressBars/HBoxContainer"]
layout_mode = 2
text = "T:"
[node name="ProgressTest" type="ProgressBar" parent="MainBox/Body/BodyRows/ProgressBars/HBoxContainer"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
size_flags_horizontal = 3
value = 25.0
[node name="HBoxContainer2" type="HBoxContainer" parent="MainBox/Body/BodyRows/ProgressBars"]
layout_mode = 2
size_flags_horizontal = 3
[node name="Label" type="Label" parent="MainBox/Body/BodyRows/ProgressBars/HBoxContainer2"]
layout_mode = 2
text = "S:"
[node name="ProgressScript" type="ProgressBar" parent="MainBox/Body/BodyRows/ProgressBars/HBoxContainer2"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
size_flags_horizontal = 3
value = 75.0
[node name="PathDisplay" type="VBoxContainer" parent="MainBox/Body/BodyRows"]
clip_contents = true
layout_mode = 2
size_flags_vertical = 3
[node name="Path" type="Label" parent="MainBox/Body/BodyRows/PathDisplay"]
layout_mode = 2
theme_override_fonts/font = ExtResource("2_a2e2l")
theme_override_font_sizes/font_size = 14
text = "res://test/integration/whatever"
clip_text = true
text_overrun_behavior = 3
[node name="HBoxContainer" type="HBoxContainer" parent="MainBox/Body/BodyRows/PathDisplay"]
clip_contents = true
layout_mode = 2
[node name="S3" type="CenterContainer" parent="MainBox/Body/BodyRows/PathDisplay/HBoxContainer"]
custom_minimum_size = Vector2(5, 0)
layout_mode = 2
[node name="File" type="Label" parent="MainBox/Body/BodyRows/PathDisplay/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_fonts/font = ExtResource("2_a2e2l")
theme_override_font_sizes/font_size = 14
text = "test_this_thing.gd"
text_overrun_behavior = 3
[node name="Footer" type="HBoxContainer" parent="MainBox/Body/BodyRows"]
layout_mode = 2
[node name="HandleLeft" parent="MainBox/Body/BodyRows/Footer" node_paths=PackedStringArray("resize_control") instance=ExtResource("4_xrhva")]
layout_mode = 2
orientation = 0
resize_control = NodePath("../../../../..")
vertical_resize = false
[node name="SwitchModes" type="Button" parent="MainBox/Body/BodyRows/Footer"]
layout_mode = 2
text = "Expand"
[node name="CenterContainer" type="CenterContainer" parent="MainBox/Body/BodyRows/Footer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="Continue" type="Button" parent="MainBox/Body/BodyRows/Footer"]
layout_mode = 2
text = "Continue
"
[node name="HandleRight" parent="MainBox/Body/BodyRows/Footer" node_paths=PackedStringArray("resize_control") instance=ExtResource("4_xrhva")]
layout_mode = 2
resize_control = NodePath("../../../../..")
vertical_resize = false
[node name="RightMargin" type="CenterContainer" parent="MainBox/Body"]
custom_minimum_size = Vector2(5, 0)
layout_mode = 2
[node name="CenterContainer" type="CenterContainer" parent="MainBox"]
custom_minimum_size = Vector2(2.08165e-12, 2)
layout_mode = 2

View file

@ -1,213 +0,0 @@
[gd_scene load_steps=5 format=3 uid="uid://duxblir3vu8x7"]
[ext_resource type="Theme" uid="uid://cstkhwkpajvqu" path="res://addons/gut/gui/GutSceneTheme.tres" id="1_5hlsm"]
[ext_resource type="Script" path="res://addons/gut/gui/gut_gui.gd" id="2_fue6q"]
[ext_resource type="FontFile" uid="uid://bnh0lslf4yh87" path="res://addons/gut/fonts/CourierPrime-Regular.ttf" id="2_u5uc1"]
[ext_resource type="PackedScene" uid="uid://bvrqqgjpyouse" path="res://addons/gut/gui/ResizeHandle.tscn" id="4_2r8a8"]
[node name="Large" type="Panel"]
custom_minimum_size = Vector2(500, 150)
offset_right = 632.0
offset_bottom = 260.0
theme = ExtResource("1_5hlsm")
script = ExtResource("2_fue6q")
[node name="MainBox" type="VBoxContainer" parent="."]
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
metadata/_edit_layout_mode = 1
[node name="TitleBar" type="Panel" parent="MainBox"]
custom_minimum_size = Vector2(0, 25)
layout_mode = 2
[node name="TitleBox" type="HBoxContainer" parent="MainBox/TitleBar"]
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
offset_top = 2.0
offset_bottom = 3.0
grow_horizontal = 2
grow_vertical = 2
metadata/_edit_layout_mode = 1
[node name="Spacer1" type="CenterContainer" parent="MainBox/TitleBar/TitleBox"]
layout_mode = 2
size_flags_horizontal = 3
[node name="Title" type="Label" parent="MainBox/TitleBar/TitleBox"]
layout_mode = 2
text = "Title"
[node name="Spacer2" type="CenterContainer" parent="MainBox/TitleBar/TitleBox"]
layout_mode = 2
size_flags_horizontal = 3
[node name="TimeLabel" type="Label" parent="MainBox/TitleBar/TitleBox"]
custom_minimum_size = Vector2(90, 0)
layout_mode = 2
text = "999.999s"
[node name="HBoxContainer" type="HBoxContainer" parent="MainBox"]
layout_mode = 2
size_flags_vertical = 3
[node name="VBoxContainer" type="VBoxContainer" parent="MainBox/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="OutputBG" type="ColorRect" parent="MainBox/HBoxContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
color = Color(0.0745098, 0.0705882, 0.0784314, 1)
metadata/_edit_layout_mode = 1
[node name="HBoxContainer" type="HBoxContainer" parent="MainBox/HBoxContainer/VBoxContainer/OutputBG"]
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="S2" type="CenterContainer" parent="MainBox/HBoxContainer/VBoxContainer/OutputBG/HBoxContainer"]
custom_minimum_size = Vector2(5, 0)
layout_mode = 2
[node name="TestOutput" type="RichTextLabel" parent="MainBox/HBoxContainer/VBoxContainer/OutputBG/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
focus_mode = 2
bbcode_enabled = true
scroll_following = true
autowrap_mode = 0
selection_enabled = true
[node name="S1" type="CenterContainer" parent="MainBox/HBoxContainer/VBoxContainer/OutputBG/HBoxContainer"]
custom_minimum_size = Vector2(5, 0)
layout_mode = 2
[node name="ControlBox" type="HBoxContainer" parent="MainBox/HBoxContainer/VBoxContainer"]
layout_mode = 2
[node name="S1" type="CenterContainer" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox"]
custom_minimum_size = Vector2(5, 0)
layout_mode = 2
[node name="ProgressBars" type="VBoxContainer" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox"]
custom_minimum_size = Vector2(2.08165e-12, 2.08165e-12)
layout_mode = 2
[node name="TestBox" type="HBoxContainer" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox/ProgressBars"]
layout_mode = 2
[node name="Label" type="Label" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox/ProgressBars/TestBox"]
custom_minimum_size = Vector2(60, 0)
layout_mode = 2
size_flags_horizontal = 3
text = "Tests"
[node name="ProgressTest" type="ProgressBar" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox/ProgressBars/TestBox"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
value = 25.0
[node name="ScriptBox" type="HBoxContainer" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox/ProgressBars"]
layout_mode = 2
[node name="Label" type="Label" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox/ProgressBars/ScriptBox"]
custom_minimum_size = Vector2(60, 0)
layout_mode = 2
size_flags_horizontal = 3
text = "Scripts"
[node name="ProgressScript" type="ProgressBar" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox/ProgressBars/ScriptBox"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
value = 75.0
[node name="PathDisplay" type="VBoxContainer" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Path" type="Label" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox/PathDisplay"]
layout_mode = 2
size_flags_vertical = 6
theme_override_fonts/font = ExtResource("2_u5uc1")
theme_override_font_sizes/font_size = 14
text = "res://test/integration/whatever"
text_overrun_behavior = 3
[node name="HBoxContainer" type="HBoxContainer" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox/PathDisplay"]
layout_mode = 2
size_flags_vertical = 3
[node name="S3" type="CenterContainer" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox/PathDisplay/HBoxContainer"]
custom_minimum_size = Vector2(5, 0)
layout_mode = 2
[node name="File" type="Label" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox/PathDisplay/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_fonts/font = ExtResource("2_u5uc1")
theme_override_font_sizes/font_size = 14
text = "test_this_thing.gd"
text_overrun_behavior = 3
[node name="Spacer1" type="CenterContainer" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox"]
visible = false
layout_mode = 2
size_flags_horizontal = 10
[node name="Continue" type="Button" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox"]
layout_mode = 2
size_flags_vertical = 4
text = "Continue
"
[node name="S3" type="CenterContainer" parent="MainBox/HBoxContainer/VBoxContainer/ControlBox"]
custom_minimum_size = Vector2(5, 0)
layout_mode = 2
[node name="BottomPad" type="CenterContainer" parent="MainBox"]
custom_minimum_size = Vector2(0, 5)
layout_mode = 2
[node name="Footer" type="HBoxContainer" parent="MainBox"]
layout_mode = 2
[node name="SidePad1" type="CenterContainer" parent="MainBox/Footer"]
custom_minimum_size = Vector2(2, 2.08165e-12)
layout_mode = 2
[node name="ResizeHandle3" parent="MainBox/Footer" node_paths=PackedStringArray("resize_control") instance=ExtResource("4_2r8a8")]
custom_minimum_size = Vector2(25, 25)
layout_mode = 2
orientation = 0
resize_control = NodePath("../../..")
[node name="SwitchModes" type="Button" parent="MainBox/Footer"]
layout_mode = 2
text = "Compact
"
[node name="CenterContainer" type="CenterContainer" parent="MainBox/Footer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="ResizeHandle2" parent="MainBox/Footer" node_paths=PackedStringArray("resize_control") instance=ExtResource("4_2r8a8")]
custom_minimum_size = Vector2(25, 25)
layout_mode = 2
resize_control = NodePath("../../..")
[node name="SidePad2" type="CenterContainer" parent="MainBox/Footer"]
custom_minimum_size = Vector2(2, 2.08165e-12)
layout_mode = 2
[node name="BottomPad2" type="CenterContainer" parent="MainBox"]
custom_minimum_size = Vector2(2.08165e-12, 2)
layout_mode = 2

View file

@ -1,345 +0,0 @@
@tool
extends VBoxContainer
# ##############################################################################
# Keeps search results from the TextEdit
# ##############################################################################
class TextEditSearcher:
var te: TextEdit
var _last_term = ""
var _last_pos = Vector2(-1, -1)
var _ignore_caret_change = false
func set_text_edit(which):
te = which
te.caret_changed.connect(_on_caret_changed)
func _on_caret_changed():
if _ignore_caret_change:
_ignore_caret_change = false
else:
_last_pos = _get_caret()
func _get_caret():
return Vector2(te.get_caret_column(), te.get_caret_line())
func _set_caret_and_sel(pos, len):
te.set_caret_line(pos.y)
te.set_caret_column(pos.x)
if len > 0:
te.select(pos.y, pos.x, pos.y, pos.x + len)
func _find(term, search_flags):
var pos = _get_caret()
if term == _last_term:
if search_flags == 0:
pos = _last_pos
pos.x += 1
else:
pos = _last_pos
pos.x -= 1
var result = te.search(term, search_flags, pos.y, pos.x)
# print('searching from ', pos, ' for "', term, '" = ', result)
if result.y != -1:
_ignore_caret_change = true
_set_caret_and_sel(result, term.length())
_last_pos = result
_last_term = term
func find_next(term):
_find(term, 0)
func find_prev(term):
_find(term, te.SEARCH_BACKWARDS)
# ##############################################################################
# Start OutputText control code
# ##############################################################################
@onready var _ctrls = {
output = $Output,
copy_button = $Toolbar/CopyButton,
use_colors = $Toolbar/UseColors,
clear_button = $Toolbar/ClearButton,
word_wrap = $Toolbar/WordWrap,
show_search = $Toolbar/ShowSearch,
caret_position = $Toolbar/LblPosition,
search_bar =
{
bar = $Search,
search_term = $Search/SearchTerm,
}
}
var _sr = TextEditSearcher.new()
var _highlighter: CodeHighlighter
var _font_name = null
# Automatically used when running the OutputText scene from the editor. Changes
# to this method only affect test-running the control through the editor.
func _test_running_setup():
_ctrls.use_colors.text = "use colors"
_ctrls.show_search.text = "search"
_ctrls.word_wrap.text = "ww"
set_all_fonts("CourierPrime")
set_font_size(30)
_ctrls.output.queue_redraw()
load_file("user://.gut_editor.bbcode")
await get_tree().process_frame
show_search(true)
_ctrls.output.set_caret_line(0)
_ctrls.output.scroll_vertical = 0
_ctrls.output.caret_changed.connect(_on_caret_changed)
func _ready():
_sr.set_text_edit(_ctrls.output)
_ctrls.use_colors.icon = get_theme_icon("RichTextEffect", "EditorIcons")
_ctrls.show_search.icon = get_theme_icon("Search", "EditorIcons")
_ctrls.word_wrap.icon = get_theme_icon("Loop", "EditorIcons")
_setup_colors()
_ctrls.use_colors.button_pressed = true
_use_highlighting(true)
if get_parent() == get_tree().root:
_test_running_setup()
func _on_caret_changed():
var txt = str(
"line:", _ctrls.output.get_caret_line(), " col:", _ctrls.output.get_caret_column()
)
_ctrls.caret_position.text = str(txt)
# ------------------
# Private
# ------------------
# Call this after changes in colors and the like to get them to apply. reloads
# the text of the output control.
func _refresh_output():
var orig_pos = _ctrls.output.scroll_vertical
var text = _ctrls.output.text
_ctrls.output.text = text
_ctrls.output.scroll_vertical = orig_pos
func _create_highlighter(default_color = Color(1, 1, 1, 1)):
var to_return = CodeHighlighter.new()
to_return.function_color = default_color
to_return.number_color = default_color
to_return.symbol_color = default_color
to_return.member_variable_color = default_color
var keywords = [
["Failed", Color.RED],
["Passed", Color.GREEN],
["Pending", Color.YELLOW],
["Orphans", Color.YELLOW],
["WARNING", Color.YELLOW],
["ERROR", Color.RED]
]
for keyword in keywords:
to_return.add_keyword_color(keyword[0], keyword[1])
return to_return
func _setup_colors():
_ctrls.output.clear()
var f_color = null
if _ctrls.output.theme == null:
f_color = get_theme_color("font_color")
else:
f_color = _ctrls.output.theme.font_color
_highlighter = _create_highlighter()
_ctrls.output.queue_redraw()
func _use_highlighting(should):
if should:
_ctrls.output.syntax_highlighter = _highlighter
else:
_ctrls.output.syntax_highlighter = null
_refresh_output()
# ------------------
# Events
# ------------------
func _on_CopyButton_pressed():
copy_to_clipboard()
func _on_UseColors_pressed():
_use_highlighting(_ctrls.use_colors.button_pressed)
func _on_ClearButton_pressed():
clear()
func _on_ShowSearch_pressed():
show_search(_ctrls.show_search.button_pressed)
func _on_SearchTerm_focus_entered():
_ctrls.search_bar.search_term.call_deferred("select_all")
func _on_SearchNext_pressed():
_sr.find_next(_ctrls.search_bar.search_term.text)
func _on_SearchPrev_pressed():
_sr.find_prev(_ctrls.search_bar.search_term.text)
func _on_SearchTerm_text_changed(new_text):
if new_text == "":
_ctrls.output.deselect()
else:
_sr.find_next(new_text)
func _on_SearchTerm_text_entered(new_text):
if Input.is_physical_key_pressed(KEY_SHIFT):
_sr.find_prev(new_text)
else:
_sr.find_next(new_text)
func _on_SearchTerm_gui_input(event):
if event is InputEventKey and !event.pressed and event.keycode == KEY_ESCAPE:
show_search(false)
func _on_WordWrap_pressed():
if _ctrls.word_wrap.button_pressed:
_ctrls.output.wrap_mode = TextEdit.LINE_WRAPPING_BOUNDARY
else:
_ctrls.output.wrap_mode = TextEdit.LINE_WRAPPING_NONE
_ctrls.output.queue_redraw()
# ------------------
# Public
# ------------------
func show_search(should):
_ctrls.search_bar.bar.visible = should
if should:
_ctrls.search_bar.search_term.grab_focus()
_ctrls.search_bar.search_term.select_all()
_ctrls.show_search.button_pressed = should
func search(text, start_pos, highlight = true):
return _sr.find_next(text)
func copy_to_clipboard():
var selected = _ctrls.output.get_selected_text()
if selected != "":
DisplayServer.clipboard_set(selected)
else:
DisplayServer.clipboard_set(_ctrls.output.text)
func clear():
_ctrls.output.text = ""
func _set_font(font_name, custom_name):
var rtl = _ctrls.output
if font_name == null:
rtl.add_theme_font_override(custom_name, null)
else:
var dyn_font = FontFile.new()
dyn_font.load_dynamic_font("res://addons/gut/fonts/" + font_name + ".ttf")
rtl.add_theme_font_override(custom_name, dyn_font)
func set_all_fonts(base_name):
_font_name = GutUtils.nvl(base_name, "Default")
if base_name == "Default":
_set_font(null, "font")
_set_font(null, "normal_font")
_set_font(null, "bold_font")
_set_font(null, "italics_font")
_set_font(null, "bold_italics_font")
else:
_set_font(base_name + "-Regular", "font")
_set_font(base_name + "-Regular", "normal_font")
_set_font(base_name + "-Bold", "bold_font")
_set_font(base_name + "-Italic", "italics_font")
_set_font(base_name + "-BoldItalic", "bold_italics_font")
func set_font_size(new_size):
var rtl = _ctrls.output
rtl.set("theme_override_font_sizes/font_size", new_size)
# rtl.add_theme_font_size_override("font", new_size)
# rtl.add_theme_font_size_override("normal_font", new_size)
# rtl.add_theme_font_size_override("bold_font", new_size)
# rtl.add_theme_font_size_override("italics_font", new_size)
# rtl.add_theme_font_size_override("bold_italics_font", new_size)
# if(rtl.get('custom_fonts/font') != null):
# rtl.get('custom_fonts/font').size = new_size
# rtl.get('custom_fonts/bold_italics_font').size = new_size
# rtl.get('custom_fonts/bold_font').size = new_size
# rtl.get('custom_fonts/italics_font').size = new_size
# rtl.get('custom_fonts/normal_font').size = new_size
func set_use_colors(value):
pass
func get_use_colors():
return false
func get_rich_text_edit():
return _ctrls.output
func load_file(path):
var f = FileAccess.open(path, FileAccess.READ)
if f == null:
return
var t = f.get_as_text()
f = null # closes file
_ctrls.output.text = t
_ctrls.output.scroll_vertical = _ctrls.output.get_line_count()
_ctrls.output.set_deferred("scroll_vertical", _ctrls.output.get_line_count())
func add_text(text):
if is_inside_tree():
_ctrls.output.text += text
func scroll_to_line(line):
_ctrls.output.scroll_vertical = line
_ctrls.output.set_caret_line(line)

View file

@ -1,117 +0,0 @@
[gd_scene load_steps=5 format=3 uid="uid://bqmo4dj64c7yl"]
[ext_resource type="Script" path="res://addons/gut/gui/OutputText.gd" id="1"]
[sub_resource type="Image" id="Image_abbh7"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 93, 93, 55, 255, 97, 97, 58, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_x655i"]
image = SubResource("Image_abbh7")
[sub_resource type="CodeHighlighter" id="CodeHighlighter_4pcgd"]
number_color = Color(1, 1, 1, 1)
symbol_color = Color(1, 1, 1, 1)
function_color = Color(1, 1, 1, 1)
member_variable_color = Color(1, 1, 1, 1)
keyword_colors = {
"ERROR": Color(1, 0, 0, 1),
"Failed": Color(1, 0, 0, 1),
"Orphans": Color(1, 1, 0, 1),
"Passed": Color(0, 1, 0, 1),
"Pending": Color(1, 1, 0, 1),
"WARNING": Color(1, 1, 0, 1)
}
[node name="OutputText" type="VBoxContainer"]
offset_right = 862.0
offset_bottom = 523.0
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource("1")
[node name="Toolbar" type="HBoxContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 3
[node name="ShowSearch" type="Button" parent="Toolbar"]
layout_mode = 2
tooltip_text = "Search"
toggle_mode = true
icon = SubResource("ImageTexture_x655i")
[node name="UseColors" type="Button" parent="Toolbar"]
layout_mode = 2
tooltip_text = "Colorized Text"
toggle_mode = true
button_pressed = true
icon = SubResource("ImageTexture_x655i")
[node name="WordWrap" type="Button" parent="Toolbar"]
layout_mode = 2
tooltip_text = "Word Wrap"
toggle_mode = true
icon = SubResource("ImageTexture_x655i")
[node name="CenterContainer" type="CenterContainer" parent="Toolbar"]
layout_mode = 2
size_flags_horizontal = 3
[node name="LblPosition" type="Label" parent="Toolbar"]
layout_mode = 2
[node name="CopyButton" type="Button" parent="Toolbar"]
layout_mode = 2
text = " Copy "
[node name="ClearButton" type="Button" parent="Toolbar"]
layout_mode = 2
text = " Clear "
[node name="Output" type="TextEdit" parent="."]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
text = "Hello World
This is a bunch of text
That exists for you to see."
deselect_on_focus_loss_enabled = false
virtual_keyboard_enabled = false
middle_mouse_paste_enabled = false
highlight_all_occurrences = true
highlight_current_line = true
syntax_highlighter = SubResource("CodeHighlighter_4pcgd")
scroll_smooth = true
[node name="Search" type="HBoxContainer" parent="."]
visible = false
layout_mode = 2
[node name="SearchTerm" type="LineEdit" parent="Search"]
layout_mode = 2
size_flags_horizontal = 3
[node name="SearchNext" type="Button" parent="Search"]
layout_mode = 2
text = "Next"
[node name="SearchPrev" type="Button" parent="Search"]
layout_mode = 2
text = "Prev"
[connection signal="pressed" from="Toolbar/ShowSearch" to="." method="_on_ShowSearch_pressed"]
[connection signal="pressed" from="Toolbar/UseColors" to="." method="_on_UseColors_pressed"]
[connection signal="pressed" from="Toolbar/WordWrap" to="." method="_on_WordWrap_pressed"]
[connection signal="pressed" from="Toolbar/CopyButton" to="." method="_on_CopyButton_pressed"]
[connection signal="pressed" from="Toolbar/ClearButton" to="." method="_on_ClearButton_pressed"]
[connection signal="focus_entered" from="Search/SearchTerm" to="." method="_on_SearchTerm_focus_entered"]
[connection signal="gui_input" from="Search/SearchTerm" to="." method="_on_SearchTerm_gui_input"]
[connection signal="text_changed" from="Search/SearchTerm" to="." method="_on_SearchTerm_text_changed"]
[connection signal="text_submitted" from="Search/SearchTerm" to="." method="_on_SearchTerm_text_entered"]
[connection signal="pressed" from="Search/SearchNext" to="." method="_on_SearchNext_pressed"]
[connection signal="pressed" from="Search/SearchPrev" to="." method="_on_SearchPrev_pressed"]

View file

@ -1,108 +0,0 @@
@tool
extends ColorRect
# #############################################################################
# Resize Handle control. Place onto a control. Set the orientation, then
# set the control that this should resize. Then you can resize the control
# by dragging this thing around. It's pretty neat.
# #############################################################################
enum ORIENTATION { LEFT, RIGHT }
@export var orientation := ORIENTATION.RIGHT:
get:
return orientation
set(val):
orientation = val
queue_redraw()
@export var resize_control: Control = null
@export var vertical_resize := true
var _line_width = .5
var _line_color = Color(.4, .4, .4)
var _active_line_color = Color(.3, .3, .3)
var _invalid_line_color = Color(1, 0, 0)
var _grab_margin = 2
var _line_space = 3
var _num_lines = 8
var _mouse_down = false
# Called when the node enters the scene tree for the first time.
func _draw():
var c = _line_color
if resize_control == null:
c = _invalid_line_color
elif _mouse_down:
c = _active_line_color
if orientation == ORIENTATION.LEFT:
_draw_resize_handle_left(c)
else:
_draw_resize_handle_right(c)
func _gui_input(event):
if resize_control == null:
return
if orientation == ORIENTATION.LEFT:
_handle_left_input(event)
else:
_handle_right_input(event)
# Draw the lines in the corner to show where you can
# drag to resize the dialog
func _draw_resize_handle_right(color):
var br = size
for i in range(_num_lines):
var start = br - Vector2(i * _line_space, 0)
var end = br - Vector2(0, i * _line_space)
draw_line(start, end, color, _line_width, true)
func _draw_resize_handle_left(color):
var bl = Vector2(0, size.y)
for i in range(_num_lines):
var start = bl + Vector2(i * _line_space, 0)
var end = bl - Vector2(0, i * _line_space)
draw_line(start, end, color, _line_width, true)
func _handle_right_input(event: InputEvent):
if event is InputEventMouseMotion:
if (
_mouse_down
and event.global_position.x > 0
and event.global_position.y < DisplayServer.window_get_size().y
):
if vertical_resize:
resize_control.size.y += event.relative.y
resize_control.size.x += event.relative.x
elif event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT:
_mouse_down = event.pressed
queue_redraw()
func _handle_left_input(event: InputEvent):
if event is InputEventMouseMotion:
if (
_mouse_down
and event.global_position.x > 0
and event.global_position.y < DisplayServer.window_get_size().y
):
var start_size = resize_control.size
resize_control.size.x -= event.relative.x
if resize_control.size.x != start_size.x:
resize_control.global_position.x += event.relative.x
if vertical_resize:
resize_control.size.y += event.relative.y
elif event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT:
_mouse_down = event.pressed
queue_redraw()

View file

@ -1,8 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://bvrqqgjpyouse"]
[ext_resource type="Script" path="res://addons/gut/gui/ResizeHandle.gd" id="1_oi5ed"]
[node name="ResizeHandle" type="ColorRect"]
custom_minimum_size = Vector2(20, 20)
color = Color(1, 1, 1, 0)
script = ExtResource("1_oi5ed")

View file

@ -1,360 +0,0 @@
@tool
extends Control
var _show_orphans = true
var show_orphans = true:
get:
return _show_orphans
set(val):
_show_orphans = val
var _hide_passing = true
var hide_passing = true:
get:
return _hide_passing
set(val):
_hide_passing = val
var _icons = {
red = load("res://addons/gut/images/red.png"),
green = load("res://addons/gut/images/green.png"),
yellow = load("res://addons/gut/images/yellow.png"),
}
const _col_1_bg_color = Color(0, 0, 0, .1)
var _max_icon_width = 10
var _root: TreeItem
@onready var _ctrls = {tree = $Tree, lbl_overlay = $Tree/TextOverlay}
signal item_selected(script_path, inner_class, test_name, line_number)
# -------------------
# Private
# -------------------
func _ready():
_root = _ctrls.tree.create_item()
_root = _ctrls.tree.create_item()
_ctrls.tree.set_hide_root(true)
_ctrls.tree.columns = 2
_ctrls.tree.set_column_expand(0, true)
_ctrls.tree.set_column_expand(1, false)
_ctrls.tree.set_column_clip_content(0, true)
$Tree.item_selected.connect(_on_tree_item_selected)
if get_parent() == get_tree().root:
_test_running_setup()
func _test_running_setup():
load_json_file("user://.gut_editor.json")
func _on_tree_item_selected():
var item = _ctrls.tree.get_selected()
var item_meta = item.get_metadata(0)
var item_type = null
# Only select the left side of the tree item, cause I like that better.
# you can still click the right, but only the left gets highlighted.
if item.is_selected(1):
item.deselect(1)
item.select(0)
if item_meta == null:
return
else:
item_type = item_meta.type
var script_path = ""
var line = -1
var test_name = ""
var inner_class = ""
if item_type == "test":
var s_item = item.get_parent()
script_path = s_item.get_metadata(0)["path"]
inner_class = s_item.get_metadata(0)["inner_class"]
line = -1
test_name = item.get_text(0)
elif item_type == "assert":
var s_item = item.get_parent().get_parent()
script_path = s_item.get_metadata(0)["path"]
inner_class = s_item.get_metadata(0)["inner_class"]
line = _get_line_number_from_assert_msg(item.get_text(0))
test_name = item.get_parent().get_text(0)
elif item_type == "script":
script_path = item.get_metadata(0)["path"]
if item.get_parent() != _root:
inner_class = item.get_text(0)
line = -1
test_name = ""
else:
return
item_selected.emit(script_path, inner_class, test_name, line)
func _get_line_number_from_assert_msg(msg):
var line = -1
if msg.find("at line") > 0:
line = msg.split("at line")[-1].split(" ")[-1].to_int()
return line
func _get_path_and_inner_class_name_from_test_path(path):
var to_return = {path = "", inner_class = ""}
to_return.path = path
if !path.ends_with(".gd"):
var loc = path.find(".gd")
to_return.inner_class = path.split(".")[-1]
to_return.path = path.substr(0, loc + 3)
return to_return
func _find_script_item_with_path(path):
var items = _root.get_children()
var to_return = null
var idx = 0
while idx < items.size() and to_return == null:
var item = items[idx]
if item.get_metadata(0).path == path:
to_return = item
else:
idx += 1
return to_return
func _add_script_tree_item(script_path, script_json):
var path_info = _get_path_and_inner_class_name_from_test_path(script_path)
var item_text = script_path
var parent = _root
if path_info.inner_class != "":
parent = _find_script_item_with_path(path_info.path)
item_text = path_info.inner_class
if parent == null:
parent = _add_script_tree_item(path_info.path, {})
parent.get_metadata(0).inner_tests += script_json["props"]["tests"]
parent.get_metadata(0).inner_passing += script_json["props"]["tests"]
parent.get_metadata(0).inner_passing -= script_json["props"]["failures"]
parent.get_metadata(0).inner_passing -= script_json["props"]["pending"]
var total_text = str("All ", parent.get_metadata(0).inner_tests, " passed")
if parent.get_metadata(0).inner_passing != parent.get_metadata(0).inner_tests:
total_text = str(
parent.get_metadata(0).inner_passing,
"/",
parent.get_metadata(0).inner_tests,
" passed."
)
parent.set_text(1, total_text)
var item = _ctrls.tree.create_item(parent)
item.set_text(0, item_text)
var meta = {
"type": "script",
"path": path_info.path,
"inner_class": path_info.inner_class,
"json": script_json,
"inner_passing": 0,
"inner_tests": 0
}
item.set_metadata(0, meta)
item.set_custom_bg_color(1, _col_1_bg_color)
return item
func _add_assert_item(text, icon, parent_item):
# print(' * adding assert')
var assert_item = _ctrls.tree.create_item(parent_item)
assert_item.set_icon_max_width(0, _max_icon_width)
assert_item.set_text(0, text)
assert_item.set_metadata(0, {"type": "assert"})
assert_item.set_icon(0, icon)
assert_item.set_custom_bg_color(1, _col_1_bg_color)
return assert_item
func _add_test_tree_item(test_name, test_json, script_item):
# print(' * adding test ', test_name)
var no_orphans_to_show = !_show_orphans or (_show_orphans and test_json.orphans == 0)
if _hide_passing and test_json["status"] == "pass" and no_orphans_to_show:
return
var item = _ctrls.tree.create_item(script_item)
var status = test_json["status"]
var meta = {"type": "test", "json": test_json}
item.set_text(0, test_name)
item.set_text(1, status)
item.set_text_alignment(1, HORIZONTAL_ALIGNMENT_RIGHT)
item.set_custom_bg_color(1, _col_1_bg_color)
item.set_metadata(0, meta)
item.set_icon_max_width(0, _max_icon_width)
var orphan_text = "orphans"
if test_json.orphans == 1:
orphan_text = "orphan"
orphan_text = str(test_json.orphans, " ", orphan_text)
if status == "pass" and no_orphans_to_show:
item.set_icon(0, _icons.green)
elif status == "pass" and !no_orphans_to_show:
item.set_icon(0, _icons.yellow)
item.set_text(1, orphan_text)
elif status == "fail":
item.set_icon(0, _icons.red)
else:
item.set_icon(0, _icons.yellow)
if !_hide_passing:
for passing in test_json.passing:
_add_assert_item("pass: " + passing, _icons.green, item)
for failure in test_json.failing:
_add_assert_item("fail: " + failure.replace("\n", ""), _icons.red, item)
for pending in test_json.pending:
_add_assert_item("pending: " + pending.replace("\n", ""), _icons.yellow, item)
if status != "pass" and !no_orphans_to_show:
_add_assert_item(orphan_text, _icons.yellow, item)
return item
func _add_script_to_tree(key, script_json):
var tests = script_json["tests"]
var test_keys = tests.keys()
var s_item = _add_script_tree_item(key, script_json)
var bad_count = 0
for test_key in test_keys:
var t_item = _add_test_tree_item(test_key, tests[test_key], s_item)
if tests[test_key].status != "pass":
bad_count += 1
elif t_item != null:
t_item.collapsed = true
if s_item.get_children().size() == 0:
s_item.free()
else:
var total_text = str("All ", test_keys.size(), " passed")
if bad_count == 0:
s_item.collapsed = true
else:
total_text = str(test_keys.size() - bad_count, "/", test_keys.size(), " passed")
s_item.set_text(1, total_text)
func _free_childless_scripts():
var items = _root.get_children()
for item in items:
var next_item = item.get_next()
if item.get_children().size() == 0:
item.free()
item = next_item
func _show_all_passed():
if _root.get_children().size() == 0:
add_centered_text("Everything passed!")
func _load_result_tree(j):
var scripts = j["test_scripts"]["scripts"]
var script_keys = scripts.keys()
# if we made it here, the json is valid and we did something, otherwise the
# 'nothing to see here' should be visible.
clear_centered_text()
for key in script_keys:
if scripts[key]["props"]["tests"] > 0:
_add_script_to_tree(key, scripts[key])
_free_childless_scripts()
_show_all_passed()
# -------------------
# Public
# -------------------
func load_json_file(path):
var file = FileAccess.open(path, FileAccess.READ)
var text = ""
if file != null:
text = file.get_as_text()
if text != "":
var test_json_conv = JSON.new()
var result = test_json_conv.parse(text)
if result != OK:
add_centered_text(
str(
path,
" has invalid json in it \n",
"Error ",
result,
"@",
test_json_conv.get_error_line(),
"\n",
test_json_conv.get_error_message()
)
)
return
var data = test_json_conv.get_data()
load_json_results(data)
else:
add_centered_text(str(path, " was empty or does not exist."))
func load_json_results(j):
clear()
_load_result_tree(j)
func clear():
_ctrls.tree.clear()
_root = _ctrls.tree.create_item()
func set_summary_min_width(width):
_ctrls.tree.set_column_custom_minimum_width(1, width)
func add_centered_text(t):
_ctrls.lbl_overlay.visible = true
_ctrls.lbl_overlay.text = t
func clear_centered_text():
_ctrls.lbl_overlay.visible = false
_ctrls.lbl_overlay.text = ""
func collapse_all():
set_collapsed_on_all(_root, true)
func expand_all():
set_collapsed_on_all(_root, false)
func set_collapsed_on_all(item, value):
item.set_collapsed_recursive(value)
if item == _root and value:
item.set_collapsed(false)
func get_selected():
return _ctrls.tree.get_selected()

View file

@ -1,32 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://dls5r5f6157nq"]
[ext_resource type="Script" path="res://addons/gut/gui/ResultsTree.gd" id="1_b4uub"]
[node name="ResultsTree" type="VBoxContainer"]
custom_minimum_size = Vector2(10, 10)
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_right = -70.0
offset_bottom = -104.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource("1_b4uub")
[node name="Tree" type="Tree" parent="."]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
columns = 2
hide_root = true
[node name="TextOverlay" type="Label" parent="Tree"]
visible = false
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2

View file

@ -1,155 +0,0 @@
@tool
extends Control
var ScriptTextEditors = load("res://addons/gut/gui/script_text_editor_controls.gd")
@onready var _ctrls = {
btn_script = $HBox/BtnRunScript,
btn_inner = $HBox/BtnRunInnerClass,
btn_method = $HBox/BtnRunMethod,
lbl_none = $HBox/LblNoneSelected,
arrow_1 = $HBox/Arrow1,
arrow_2 = $HBox/Arrow2
}
var _editors = null
var _cur_editor = null
var _last_line = -1
var _cur_script_path = null
var _last_info = {script = null, inner_class = null, test_method = null}
signal run_tests(what)
func _ready():
_ctrls.lbl_none.visible = true
_ctrls.btn_script.visible = false
_ctrls.btn_inner.visible = false
_ctrls.btn_method.visible = false
_ctrls.arrow_1.visible = false
_ctrls.arrow_2.visible = false
# ----------------
# Private
# ----------------
func _set_editor(which):
_last_line = -1
if _cur_editor != null and _cur_editor.get_ref():
# _cur_editor.get_ref().disconnect('cursor_changed',Callable(self,'_on_cursor_changed'))
_cur_editor.get_ref().caret_changed.disconnect(_on_cursor_changed)
if which != null:
_cur_editor = weakref(which)
which.caret_changed.connect(_on_cursor_changed.bind(which))
# which.connect('cursor_changed',Callable(self,'_on_cursor_changed'),[which])
_last_line = which.get_caret_line()
_last_info = _editors.get_line_info()
_update_buttons(_last_info)
func _update_buttons(info):
_ctrls.lbl_none.visible = _cur_script_path == null
_ctrls.btn_script.visible = _cur_script_path != null
_ctrls.btn_inner.visible = info.inner_class != null
_ctrls.arrow_1.visible = info.inner_class != null
_ctrls.btn_inner.text = str(info.inner_class)
_ctrls.btn_inner.tooltip_text = str("Run all tests in Inner-Test-Class ", info.inner_class)
_ctrls.btn_method.visible = info.test_method != null
_ctrls.arrow_2.visible = info.test_method != null
_ctrls.btn_method.text = str(info.test_method)
_ctrls.btn_method.tooltip_text = str("Run test ", info.test_method)
# The button's new size won't take effect until the next frame.
# This appears to be what was causing the button to not be clickable the
# first time.
call_deferred("_update_size")
func _update_size():
custom_minimum_size.x = _ctrls.btn_method.size.x + _ctrls.btn_method.position.x
# ----------------
# Events
# ----------------
func _on_cursor_changed(which):
if which.get_caret_line() != _last_line:
_last_line = which.get_caret_line()
_last_info = _editors.get_line_info()
_update_buttons(_last_info)
func _on_BtnRunScript_pressed():
var info = _last_info.duplicate()
info.script = _cur_script_path.get_file()
info.inner_class = null
info.test_method = null
emit_signal("run_tests", info)
func _on_BtnRunInnerClass_pressed():
var info = _last_info.duplicate()
info.script = _cur_script_path.get_file()
info.test_method = null
emit_signal("run_tests", info)
func _on_BtnRunMethod_pressed():
var info = _last_info.duplicate()
info.script = _cur_script_path.get_file()
emit_signal("run_tests", info)
# ----------------
# Public
# ----------------
func set_script_text_editors(value):
_editors = value
func activate_for_script(path):
_ctrls.btn_script.visible = true
_ctrls.btn_script.text = path.get_file()
_ctrls.btn_script.tooltip_text = str("Run all tests in script ", path)
_cur_script_path = path
_editors.refresh()
# We have to wait a beat for the visibility to change on
# the editors, otherwise we always get the first one.
await get_tree().process_frame
_set_editor(_editors.get_current_text_edit())
func get_script_button():
return _ctrls.btn_script
func get_inner_button():
return _ctrls.btn_inner
func get_test_button():
return _ctrls.btn_method
# not used, thought was configurable but it's just the script prefix
func set_method_prefix(value):
_editors.set_method_prefix(value)
# not used, thought was configurable but it's just the script prefix
func set_inner_class_prefix(value):
_editors.set_inner_class_prefix(value)
# Mashed this function in here b/c it has _editors. Probably should be
# somewhere else (possibly in script_text_editor_controls).
func search_current_editor_for_text(txt):
var te = _editors.get_current_text_edit()
var result = te.search(txt, 0, 0, 0)
var to_return = -1
return to_return

View file

@ -1,65 +0,0 @@
[gd_scene load_steps=4 format=3 uid="uid://0yunjxtaa8iw"]
[ext_resource type="Script" path="res://addons/gut/gui/RunAtCursor.gd" id="1"]
[ext_resource type="Texture2D" uid="uid://cr6tvdv0ve6cv" path="res://addons/gut/gui/play.png" id="2"]
[ext_resource type="Texture2D" uid="uid://6wra5rxmfsrl" path="res://addons/gut/gui/arrow.png" id="3"]
[node name="RunAtCursor" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_right = 1.0
offset_bottom = -527.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource("1")
[node name="HBox" type="HBoxContainer" parent="."]
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="LblNoneSelected" type="Label" parent="HBox"]
layout_mode = 2
text = "<None>"
[node name="BtnRunScript" type="Button" parent="HBox"]
visible = false
layout_mode = 2
text = "<script>"
icon = ExtResource("2")
[node name="Arrow1" type="TextureButton" parent="HBox"]
visible = false
custom_minimum_size = Vector2(24, 0)
layout_mode = 2
texture_normal = ExtResource("3")
stretch_mode = 3
[node name="BtnRunInnerClass" type="Button" parent="HBox"]
visible = false
layout_mode = 2
text = "<inner class>"
icon = ExtResource("2")
[node name="Arrow2" type="TextureButton" parent="HBox"]
visible = false
custom_minimum_size = Vector2(24, 0)
layout_mode = 2
texture_normal = ExtResource("3")
stretch_mode = 3
[node name="BtnRunMethod" type="Button" parent="HBox"]
visible = false
layout_mode = 2
text = "<method>"
icon = ExtResource("2")
[connection signal="pressed" from="HBox/BtnRunScript" to="." method="_on_BtnRunScript_pressed"]
[connection signal="pressed" from="HBox/BtnRunInnerClass" to="." method="_on_BtnRunInnerClass_pressed"]
[connection signal="pressed" from="HBox/BtnRunMethod" to="." method="_on_BtnRunMethod_pressed"]

View file

@ -1,256 +0,0 @@
@tool
extends Control
var _interface = null
var _font = null
var _font_size = null
var _editors = null # script_text_editor_controls.gd
var _output_control = null
@onready var _ctrls = {
tree = $VBox/Output/Scroll/Tree,
toolbar =
{
toolbar = $VBox/Toolbar,
collapse = $VBox/Toolbar/Collapse,
collapse_all = $VBox/Toolbar/CollapseAll,
expand = $VBox/Toolbar/Expand,
expand_all = $VBox/Toolbar/ExpandAll,
hide_passing = $VBox/Toolbar/HidePassing,
show_script = $VBox/Toolbar/ShowScript,
scroll_output = $VBox/Toolbar/ScrollOutput
}
}
func _ready():
var f = null
if $FontSampler.get_label_settings() == null:
f = get_theme_default_font()
else:
f = $FontSampler.get_label_settings().font
var s_size = f.get_string_size("000 of 000 passed")
_ctrls.tree.set_summary_min_width(s_size.x)
_set_toolbutton_icon(_ctrls.toolbar.collapse, "CollapseTree", "c")
_set_toolbutton_icon(_ctrls.toolbar.collapse_all, "CollapseTree", "c")
_set_toolbutton_icon(_ctrls.toolbar.expand, "ExpandTree", "e")
_set_toolbutton_icon(_ctrls.toolbar.expand_all, "ExpandTree", "e")
_set_toolbutton_icon(_ctrls.toolbar.show_script, "Script", "ss")
_set_toolbutton_icon(_ctrls.toolbar.scroll_output, "Font", "so")
_ctrls.tree.hide_passing = true
_ctrls.toolbar.hide_passing.button_pressed = false
_ctrls.tree.show_orphans = true
_ctrls.tree.item_selected.connect(_on_item_selected)
if get_parent() == get_tree().root:
_test_running_setup()
call_deferred("_update_min_width")
func _test_running_setup():
_ctrls.tree.hide_passing = true
_ctrls.tree.show_orphans = true
var _gut_config = load("res://addons/gut/gut_config.gd").new()
_gut_config.load_panel_options("res://.gut_editor_config.json")
set_font(
_gut_config.options.panel_options.font_name, _gut_config.options.panel_options.font_size
)
_ctrls.toolbar.hide_passing.text = "[hp]"
_ctrls.tree.load_json_file("user://.gut_editor.json")
func _set_toolbutton_icon(btn, icon_name, text):
if Engine.is_editor_hint():
btn.icon = get_theme_icon(icon_name, "EditorIcons")
else:
btn.text = str("[", text, "]")
func _update_min_width():
custom_minimum_size.x = _ctrls.toolbar.toolbar.size.x
func _open_script_in_editor(path, line_number):
if _interface == null:
print("Too soon, wait a bit and try again.")
return
var r = load(path)
if line_number != null and line_number != -1:
_interface.edit_script(r, line_number)
else:
_interface.edit_script(r)
if _ctrls.toolbar.show_script.pressed:
_interface.set_main_screen_editor("Script")
# starts at beginning of text edit and searches for each search term, moving
# through the text as it goes; ensuring that, when done, it found the first
# occurance of the last srting that happend after the first occurance of
# each string before it. (Generic way of searching for a method name in an
# inner class that may have be a duplicate of a method name in a different
# inner class)
func _get_line_number_for_seq_search(search_strings, te):
if te == null:
print("No Text editor to get line number for")
return 0
var result = null
var line = Vector2i(0, 0)
var s_flags = 0
var i = 0
var string_found = true
while i < search_strings.size() and string_found:
result = te.search(search_strings[i], s_flags, line.y, line.x)
if result.x != -1:
line = result
else:
string_found = false
i += 1
return line.y
func _goto_code(path, line, method_name = "", inner_class = ""):
if _interface == null:
print("going to ", [path, line, method_name, inner_class])
return
_open_script_in_editor(path, line)
if line == -1:
var search_strings = []
if inner_class != "":
search_strings.append(inner_class)
if method_name != "":
search_strings.append(method_name)
await get_tree().process_frame
line = _get_line_number_for_seq_search(search_strings, _editors.get_current_text_edit())
if line != null and line != -1:
_interface.get_script_editor().goto_line(line)
func _goto_output(path, method_name, inner_class):
if _output_control == null:
return
var search_strings = [path]
if inner_class != "":
search_strings.append(inner_class)
if method_name != "":
search_strings.append(method_name)
var line = _get_line_number_for_seq_search(search_strings, _output_control.get_rich_text_edit())
if line != null and line != -1:
_output_control.scroll_to_line(line)
# --------------
# Events
# --------------
func _on_Collapse_pressed():
collapse_selected()
func _on_Expand_pressed():
expand_selected()
func _on_CollapseAll_pressed():
collapse_all()
func _on_ExpandAll_pressed():
expand_all()
func _on_Hide_Passing_pressed():
_ctrls.tree.hide_passing = !_ctrls.toolbar.hide_passing.button_pressed
_ctrls.tree.load_json_file("user://.gut_editor.json")
func _on_item_selected(script_path, inner_class, test_name, line):
if _ctrls.toolbar.show_script.button_pressed:
_goto_code(script_path, line, test_name, inner_class)
if _ctrls.toolbar.scroll_output.button_pressed:
_goto_output(script_path, test_name, inner_class)
# --------------
# Public
# --------------
func add_centered_text(t):
_ctrls.tree.add_centered_text(t)
func clear_centered_text():
_ctrls.tree.clear_centered_text()
func clear():
_ctrls.tree.clear()
clear_centered_text()
func set_interface(which):
_interface = which
func set_script_text_editors(value):
_editors = value
func collapse_all():
_ctrls.tree.collapse_all()
func expand_all():
_ctrls.tree.expand_all()
func collapse_selected():
var item = _ctrls.tree.get_selected()
if item != null:
_ctrls.tree.set_collapsed_on_all(item, true)
func expand_selected():
var item = _ctrls.tree.get_selected()
if item != null:
_ctrls.tree.set_collapsed_on_all(item, false)
func set_show_orphans(should):
_ctrls.tree.show_orphans = should
func set_font(font_name, size):
pass
# var dyn_font = FontFile.new()
# var font_data = FontFile.new()
# font_data.font_path = 'res://addons/gut/fonts/' + font_name + '-Regular.ttf'
# font_data.antialiased = true
# dyn_font.font_data = font_data
#
# _font = dyn_font
# _font.size = size
# _font_size = size
func set_output_control(value):
_output_control = value
func load_json_results(j):
_ctrls.tree.load_json_results(j)

View file

@ -1,116 +0,0 @@
[gd_scene load_steps=5 format=3 uid="uid://4gyyn12um08h"]
[ext_resource type="Script" path="res://addons/gut/gui/RunResults.gd" id="1"]
[ext_resource type="PackedScene" uid="uid://dls5r5f6157nq" path="res://addons/gut/gui/ResultsTree.tscn" id="2_o808v"]
[sub_resource type="Image" id="Image_abbh7"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 93, 93, 55, 255, 97, 97, 58, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_x655i"]
image = SubResource("Image_abbh7")
[node name="RunResults" type="Control"]
custom_minimum_size = Vector2(345, 0)
layout_mode = 3
anchors_preset = 0
offset_right = 709.0
offset_bottom = 321.0
script = ExtResource("1")
[node name="VBox" type="VBoxContainer" parent="."]
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
[node name="Toolbar" type="HBoxContainer" parent="VBox"]
layout_mode = 2
size_flags_horizontal = 0
[node name="Expand" type="Button" parent="VBox/Toolbar"]
layout_mode = 2
icon = SubResource("ImageTexture_x655i")
[node name="Collapse" type="Button" parent="VBox/Toolbar"]
layout_mode = 2
icon = SubResource("ImageTexture_x655i")
[node name="Sep" type="ColorRect" parent="VBox/Toolbar"]
custom_minimum_size = Vector2(2, 0)
layout_mode = 2
[node name="LblAll" type="Label" parent="VBox/Toolbar"]
layout_mode = 2
text = "All:"
[node name="ExpandAll" type="Button" parent="VBox/Toolbar"]
layout_mode = 2
icon = SubResource("ImageTexture_x655i")
[node name="CollapseAll" type="Button" parent="VBox/Toolbar"]
layout_mode = 2
icon = SubResource("ImageTexture_x655i")
[node name="Sep2" type="ColorRect" parent="VBox/Toolbar"]
custom_minimum_size = Vector2(2, 0)
layout_mode = 2
[node name="HidePassing" type="CheckBox" parent="VBox/Toolbar"]
layout_mode = 2
size_flags_horizontal = 4
text = "Passing"
[node name="Sep3" type="ColorRect" parent="VBox/Toolbar"]
custom_minimum_size = Vector2(2, 0)
layout_mode = 2
[node name="LblSync" type="Label" parent="VBox/Toolbar"]
layout_mode = 2
text = "Sync:"
[node name="ShowScript" type="Button" parent="VBox/Toolbar"]
layout_mode = 2
toggle_mode = true
button_pressed = true
icon = SubResource("ImageTexture_x655i")
[node name="ScrollOutput" type="Button" parent="VBox/Toolbar"]
layout_mode = 2
toggle_mode = true
button_pressed = true
icon = SubResource("ImageTexture_x655i")
[node name="Output" type="Panel" parent="VBox"]
self_modulate = Color(1, 1, 1, 0.541176)
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Scroll" type="ScrollContainer" parent="VBox/Output"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Tree" parent="VBox/Output/Scroll" instance=ExtResource("2_o808v")]
layout_mode = 2
[node name="FontSampler" type="Label" parent="."]
visible = false
layout_mode = 0
offset_right = 40.0
offset_bottom = 14.0
text = "000 of 000 passed"
[connection signal="pressed" from="VBox/Toolbar/Expand" to="." method="_on_Expand_pressed"]
[connection signal="pressed" from="VBox/Toolbar/Collapse" to="." method="_on_Collapse_pressed"]
[connection signal="pressed" from="VBox/Toolbar/ExpandAll" to="." method="_on_ExpandAll_pressed"]
[connection signal="pressed" from="VBox/Toolbar/CollapseAll" to="." method="_on_CollapseAll_pressed"]
[connection signal="pressed" from="VBox/Toolbar/HidePassing" to="." method="_on_Hide_Passing_pressed"]

View file

@ -1,7 +0,0 @@
[gd_scene format=3 uid="uid://cvvvtsah38l0e"]
[node name="Settings" type="VBoxContainer"]
offset_right = 388.0
offset_bottom = 586.0
size_flags_horizontal = 3
size_flags_vertical = 3

View file

@ -1,146 +0,0 @@
@tool
extends Control
@onready var _ctrls = {
shortcut_label = $Layout/lblShortcut,
set_button = $Layout/SetButton,
save_button = $Layout/SaveButton,
cancel_button = $Layout/CancelButton,
clear_button = $Layout/ClearButton
}
signal changed
signal start_edit
signal end_edit
const NO_SHORTCUT = "<None>"
var _source_event = InputEventKey.new()
var _pre_edit_event = null
var _key_disp = NO_SHORTCUT
var _modifier_keys = [KEY_ALT, KEY_CTRL, KEY_META, KEY_SHIFT]
# Called when the node enters the scene tree for the first time.
func _ready():
set_process_unhandled_key_input(false)
func _display_shortcut():
if _key_disp == "":
_key_disp = NO_SHORTCUT
_ctrls.shortcut_label.text = _key_disp
func _is_shift_only_modifier():
return (
_source_event.shift_pressed
and !(_source_event.alt_pressed or _source_event.ctrl_pressed or _source_event.meta_pressed)
and !_is_modifier(_source_event.keycode)
)
func _has_modifier(event):
return event.alt_pressed or event.ctrl_pressed or event.meta_pressed or event.shift_pressed
func _is_modifier(keycode):
return _modifier_keys.has(keycode)
func _edit_mode(should):
set_process_unhandled_key_input(should)
_ctrls.set_button.visible = !should
_ctrls.save_button.visible = should
_ctrls.save_button.disabled = should
_ctrls.cancel_button.visible = should
_ctrls.clear_button.visible = !should
if should and to_s() == "":
_ctrls.shortcut_label.text = "press buttons"
else:
_ctrls.shortcut_label.text = to_s()
if should:
emit_signal("start_edit")
else:
emit_signal("end_edit")
# ---------------
# Events
# ---------------
func _unhandled_key_input(event):
if event is InputEventKey:
if event.pressed:
if _has_modifier(event) and !_is_modifier(event.get_keycode_with_modifiers()):
_source_event = event
_key_disp = OS.get_keycode_string(event.get_keycode_with_modifiers())
else:
_source_event = InputEventKey.new()
_key_disp = NO_SHORTCUT
_display_shortcut()
_ctrls.save_button.disabled = !is_valid()
func _on_SetButton_pressed():
_pre_edit_event = _source_event.duplicate(true)
_edit_mode(true)
func _on_SaveButton_pressed():
_edit_mode(false)
_pre_edit_event = null
emit_signal("changed")
func _on_CancelButton_pressed():
_edit_mode(false)
_source_event = _pre_edit_event
_key_disp = to_s()
_display_shortcut()
func _on_ClearButton_pressed():
clear_shortcut()
# ---------------
# Public
# ---------------
func to_s():
return OS.get_keycode_string(_source_event.get_keycode_with_modifiers())
func is_valid():
return _has_modifier(_source_event) and !_is_shift_only_modifier()
func get_shortcut():
var to_return = Shortcut.new()
to_return.events.append(_source_event)
return to_return
func set_shortcut(sc):
if sc == null or sc.events == null || sc.events.size() <= 0:
clear_shortcut()
else:
_source_event = sc.events[0]
_key_disp = to_s()
_display_shortcut()
func clear_shortcut():
_source_event = InputEventKey.new()
_key_disp = NO_SHORTCUT
_display_shortcut()
func disable_set(should):
_ctrls.set_button.disabled = should
func disable_clear(should):
_ctrls.clear_button.disabled = should

View file

@ -1,55 +0,0 @@
[gd_scene load_steps=2 format=3 uid="uid://sfb1fw8j6ufu"]
[ext_resource type="Script" path="res://addons/gut/gui/ShortcutButton.gd" id="1"]
[node name="ShortcutButton" type="Control"]
custom_minimum_size = Vector2(210, 30)
layout_mode = 3
anchor_right = 0.123
anchor_bottom = 0.04
offset_right = 68.304
offset_bottom = 6.08
script = ExtResource("1")
[node name="Layout" type="HBoxContainer" parent="."]
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
[node name="lblShortcut" type="Label" parent="Layout"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 7
text = "<None>"
horizontal_alignment = 2
[node name="CenterContainer" type="CenterContainer" parent="Layout"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
[node name="SetButton" type="Button" parent="Layout"]
custom_minimum_size = Vector2(60, 0)
layout_mode = 2
text = "Set"
[node name="SaveButton" type="Button" parent="Layout"]
visible = false
custom_minimum_size = Vector2(60, 0)
layout_mode = 2
text = "Save"
[node name="CancelButton" type="Button" parent="Layout"]
visible = false
custom_minimum_size = Vector2(60, 0)
layout_mode = 2
text = "Cancel"
[node name="ClearButton" type="Button" parent="Layout"]
custom_minimum_size = Vector2(60, 0)
layout_mode = 2
text = "Clear"
[connection signal="pressed" from="Layout/SetButton" to="." method="_on_SetButton_pressed"]
[connection signal="pressed" from="Layout/SaveButton" to="." method="_on_SaveButton_pressed"]
[connection signal="pressed" from="Layout/CancelButton" to="." method="_on_CancelButton_pressed"]
[connection signal="pressed" from="Layout/ClearButton" to="." method="_on_ClearButton_pressed"]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 B

View file

@ -1,34 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://6wra5rxmfsrl"
path="res://.godot/imported/arrow.png-2b5b2d838b5b3467cf300ac2da1630d9.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/gut/gui/arrow.png"
dest_files=["res://.godot/imported/arrow.png-2b5b2d838b5b3467cf300ac2da1630d9.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

View file

@ -1,590 +0,0 @@
# ------------------------------------------------------------------------------
# Choose an existing directory from res://. Dialog allows for creating a
# directory.
# ------------------------------------------------------------------------------
class DirectoryCtrl:
extends HBoxContainer
var text = "":
get:
return _txt_path.text
set(val):
_txt_path.text = val
var _txt_path := LineEdit.new()
var _btn_dir := Button.new()
var _dialog := FileDialog.new()
func _init():
_btn_dir.text = "..."
_btn_dir.pressed.connect(_on_dir_button_pressed)
_txt_path.size_flags_horizontal = _txt_path.SIZE_EXPAND_FILL
_dialog.file_mode = _dialog.FILE_MODE_OPEN_DIR
_dialog.unresizable = false
_dialog.dir_selected.connect(_on_selected)
_dialog.file_selected.connect(_on_selected)
_dialog.size = Vector2(1000, 700)
func _on_selected(path):
text = path
func _on_dir_button_pressed():
_dialog.current_dir = _txt_path.text
_dialog.popup_centered()
func _ready():
add_child(_txt_path)
add_child(_btn_dir)
add_child(_dialog)
func get_line_edit():
return _txt_path
# ------------------------------------------------------------------------------
# Choose an existing file in res://
# ------------------------------------------------------------------------------
class FileCtrl:
extends DirectoryCtrl
func _init():
super._init()
_dialog.file_mode = _dialog.FILE_MODE_OPEN_FILE
# ------------------------------------------------------------------------------
# Choose a save location. Can pick anywhere on file system. Will warn if you
# pick a file that already exists.
# ------------------------------------------------------------------------------
class SaveFileAnywhere:
extends DirectoryCtrl
func _init():
super._init()
_dialog.file_mode = _dialog.FILE_MODE_SAVE_FILE
_dialog.access = _dialog.ACCESS_FILESYSTEM
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
class Vector2Ctrl:
extends VBoxContainer
var value = Vector2(-1, -1):
get:
return get_value()
set(val):
set_value(val)
var disabled = false:
get:
return get_disabled()
set(val):
set_disabled(val)
var x_spin = SpinBox.new()
var y_spin = SpinBox.new()
func _init():
add_child(_make_one("x: ", x_spin))
add_child(_make_one("y: ", y_spin))
func _make_one(txt, spinner):
var hbox = HBoxContainer.new()
var lbl = Label.new()
lbl.text = txt
hbox.add_child(lbl)
hbox.add_child(spinner)
spinner.min_value = -1
spinner.max_value = 10000
spinner.size_flags_horizontal = spinner.SIZE_EXPAND_FILL
return hbox
func set_value(v):
if v != null:
x_spin.value = v[0]
y_spin.value = v[1]
# Returns array instead of vector2 b/c that is what is stored in
# in the dictionary and what is expected everywhere else.
func get_value():
return [x_spin.value, y_spin.value]
func set_disabled(should):
get_parent().visible = !should
x_spin.visible = !should
y_spin.visible = !should
func get_disabled():
pass
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
var _base_container = null
var _base_control = null
const DIRS_TO_LIST = 6
var _cfg_ctrls = {}
var _avail_fonts = ["AnonymousPro", "CourierPrime", "LobsterTwo", "Default"]
func _init(cont):
_base_container = cont
_base_control = HBoxContainer.new()
_base_control.size_flags_horizontal = _base_control.SIZE_EXPAND_FILL
_base_control.mouse_filter = _base_control.MOUSE_FILTER_PASS
# I don't remember what this is all about at all. Could be
# garbage. Decided to spend more time typing this message
# than figuring it out.
var lbl = Label.new()
lbl.size_flags_horizontal = lbl.SIZE_EXPAND_FILL
lbl.mouse_filter = lbl.MOUSE_FILTER_STOP
_base_control.add_child(lbl)
func _notification(what):
if what == NOTIFICATION_PREDELETE:
_base_control.free()
# ------------------
# Private
# ------------------
func _new_row(key, disp_text, value_ctrl, hint):
var ctrl = _base_control.duplicate()
var lbl = ctrl.get_child(0)
lbl.tooltip_text = hint
lbl.text = disp_text
_base_container.add_child(ctrl)
_cfg_ctrls[key] = value_ctrl
ctrl.add_child(value_ctrl)
var rpad = CenterContainer.new()
# rpad.custom_minimum_size.x = 5
ctrl.add_child(rpad)
return ctrl
func _add_title(text):
var row = _base_control.duplicate()
var lbl = row.get_child(0)
lbl.text = text
# lbl.align = Label.ALIGNMENT_CENTER
_base_container.add_child(row)
row.connect("draw", _on_title_cell_draw.bind(row))
func _add_number(key, value, disp_text, v_min, v_max, hint = ""):
var value_ctrl = SpinBox.new()
value_ctrl.value = value
value_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL
value_ctrl.min_value = v_min
value_ctrl.max_value = v_max
_wire_select_on_focus(value_ctrl.get_line_edit())
return _new_row(key, disp_text, value_ctrl, hint)
func _add_select(key, value, values, disp_text, hint = ""):
var value_ctrl = OptionButton.new()
var select_idx = 0
for i in range(values.size()):
value_ctrl.add_item(values[i])
if value == values[i]:
select_idx = i
value_ctrl.selected = select_idx
value_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL
return _new_row(key, disp_text, value_ctrl, hint)
func _add_value(key, value, disp_text, hint = ""):
var value_ctrl = LineEdit.new()
value_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL
value_ctrl.text = value
_wire_select_on_focus(value_ctrl)
return _new_row(key, disp_text, value_ctrl, hint)
func _add_boolean(key, value, disp_text, hint = ""):
var value_ctrl = CheckBox.new()
value_ctrl.button_pressed = value
return _new_row(key, disp_text, value_ctrl, hint)
func _add_directory(key, value, disp_text, hint = ""):
var value_ctrl = DirectoryCtrl.new()
value_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL
value_ctrl.text = value
_wire_select_on_focus(value_ctrl.get_line_edit())
return _new_row(key, disp_text, value_ctrl, hint)
func _add_file(key, value, disp_text, hint = ""):
var value_ctrl = FileCtrl.new()
value_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL
value_ctrl.text = value
_wire_select_on_focus(value_ctrl.get_line_edit())
return _new_row(key, disp_text, value_ctrl, hint)
func _add_save_file_anywhere(key, value, disp_text, hint = ""):
var value_ctrl = SaveFileAnywhere.new()
value_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL
value_ctrl.text = value
_wire_select_on_focus(value_ctrl.get_line_edit())
return _new_row(key, disp_text, value_ctrl, hint)
func _add_color(key, value, disp_text, hint = ""):
var value_ctrl = ColorPickerButton.new()
value_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL
value_ctrl.color = value
return _new_row(key, disp_text, value_ctrl, hint)
func _add_vector2(key, value, disp_text, hint = ""):
var value_ctrl = Vector2Ctrl.new()
value_ctrl.size_flags_horizontal = value_ctrl.SIZE_EXPAND_FILL
value_ctrl.value = value
_wire_select_on_focus(value_ctrl.x_spin.get_line_edit())
_wire_select_on_focus(value_ctrl.y_spin.get_line_edit())
return _new_row(key, disp_text, value_ctrl, hint)
# -----------------------------
# ------------------
# Events
# ------------------
func _wire_select_on_focus(which):
which.connect("focus_entered", _on_ctrl_focus_highlight.bind(which))
which.connect("focus_exited", _on_ctrl_focus_unhighlight.bind(which))
func _on_ctrl_focus_highlight(which):
if which.has_method("select_all"):
which.call_deferred("select_all")
func _on_ctrl_focus_unhighlight(which):
if which.has_method("select"):
which.select(0, 0)
func _on_title_cell_draw(which):
which.draw_rect(Rect2(Vector2(0, 0), which.size), Color(0, 0, 0, .15))
# ------------------
# Public
# ------------------
func get_config_issues():
var to_return = []
var has_directory = false
for i in range(DIRS_TO_LIST):
var key = str("directory_", i)
var path = _cfg_ctrls[key].text
if path != null and path != "":
has_directory = true
if !DirAccess.dir_exists_absolute(path):
to_return.append(str("Test directory ", path, " does not exist."))
if !has_directory:
to_return.append("You do not have any directories set.")
if !_cfg_ctrls["suffix"].text.ends_with(".gd"):
to_return.append("Script suffix must end in '.gd'")
return to_return
# --------------
# SUPER dumb but VERY fun hack to hide settings. The various _add methods will
# return what they add. If you want to hide it, just assign the result to this.
# YES, I could have just put .visible at the end, but I didn't think of that
# until just now, and this was fun, non-permanent and the .visible at the end
# isn't as obvious as hide_this =
#
# Also, we can't just skip adding the controls because other things are looking
# for them and things start to blow up if you don't add them.
var hide_this = null:
set(val):
val.visible = false
# --------------
func set_options(options):
_add_title("Settings") # ----------------------------------
_add_number(
"log_level",
options.log_level,
"Log Level",
0,
3,
(
"Detail level for log messages.\n"
+ "\t0: Errors and failures only.\n"
+ "\t1: Adds all test names + warnings + info\n"
+ "\t2: Shows all asserts\n"
+ "\t3: Adds more stuff probably, maybe not."
)
)
_add_boolean(
"ignore_pause",
options.ignore_pause,
"Ignore Pause",
"Ignore calls to pause_before_teardown"
)
_add_boolean(
"hide_orphans",
options.hide_orphans,
"Hide Orphans",
"Do not display orphan counts in output."
)
_add_boolean("should_exit", options.should_exit, "Exit on Finish", "Exit when tests finished.")
_add_boolean(
"should_exit_on_success",
options.should_exit_on_success,
"Exit on Success",
"Exit if there are no failures. Does nothing if 'Exit on Finish' is enabled."
)
var ds = _add_select(
"double_strategy",
"Script Only",
["Include Native", "Script Only"],
"Double Strategy",
(
'"Include Native" will include native methods in Doubles. "Script Only" will not. '
+ "\n"
+ "The native method override warning is disabled when creating Doubles."
+ "\n"
+ "This is the default, you can override this at the script level or when creating doubles."
)
)
_cfg_ctrls["double_strategy"].selected = GutUtils.get_enum_value(
options.double_strategy, GutUtils.DOUBLE_STRATEGY, GutUtils.DOUBLE_STRATEGY.SCRIPT_ONLY
)
_add_boolean(
"errors_cause_failure",
!options.errors_do_not_cause_failure,
"Errors cause failures.",
"When GUT generates an error (not an engine error) it causes tests to fail."
)
_add_title("Panel Output") # ----------------------------------
_add_select(
"output_font_name",
options.panel_options.output_font_name,
_avail_fonts,
"Font",
"The name of the font to use when running tests and in the output panel to the left."
)
_add_number(
"output_font_size",
options.panel_options.output_font_size,
"Font Size",
5,
100,
"The font size to use when running tests and in the output panel to the left."
)
_add_title("Runner Window") # ----------------------------------
hide_this = _add_boolean(
"gut_on_top",
options.gut_on_top,
"On Top",
"The GUT Runner appears above children added during tests."
)
_add_number(
"opacity", options.opacity, "Opacity", 0, 100, "The opacity of GUT when tests are running."
)
hide_this = _add_boolean(
"should_maximize",
options.should_maximize,
"Maximize",
"Maximize GUT when tests are being run."
)
_add_boolean(
"compact_mode",
options.compact_mode,
"Compact Mode",
"The runner will be in compact mode. This overrides Maximize."
)
_add_title("Runner Appearance") # ----------------------------------
_add_select(
"font_name",
options.font_name,
_avail_fonts,
"Font",
"The font to use for text output in the Gut Runner."
)
_add_number(
"font_size",
options.font_size,
"Font Size",
5,
100,
"The font size for text output in the Gut Runner."
)
hide_this = _add_color(
"font_color",
options.font_color,
"Font Color",
"The font color for text output in the Gut Runner."
)
_add_color(
"background_color",
options.background_color,
"Background Color",
"The background color for text output in the Gut Runner."
)
_add_boolean(
"disable_colors",
options.disable_colors,
"Disable Formatting",
"Disable formatting and colors used in the Runner. Does not affect panel output."
)
_add_title("Test Directories") # ----------------------------------
_add_boolean(
"include_subdirs",
options.include_subdirs,
"Include Subdirs",
"Include subdirectories of the directories configured below."
)
for i in range(DIRS_TO_LIST):
var value = ""
if options.dirs.size() > i:
value = options.dirs[i]
_add_directory(str("directory_", i), value, str("Directory ", i))
_add_title("XML Output") # ----------------------------------
_add_save_file_anywhere(
"junit_xml_file",
options.junit_xml_file,
"Output Path",
(
"Path3D and filename where GUT should create a JUnit compliant XML file. "
+ "This file will contain the results of the last test run. To avoid "
+ "overriding the file use Include Timestamp."
)
)
_add_boolean(
"junit_xml_timestamp",
options.junit_xml_timestamp,
"Include Timestamp",
"Include a timestamp in the filename so that each run gets its own xml file."
)
_add_title("Hooks") # ----------------------------------
_add_file(
"pre_run_script",
options.pre_run_script,
"Pre-Run Hook",
"This script will be run by GUT before any tests are run."
)
_add_file(
"post_run_script",
options.post_run_script,
"Post-Run Hook",
"This script will be run by GUT after all tests are run."
)
_add_title("Misc") # ----------------------------------
_add_value(
"prefix", options.prefix, "Script Prefix", "The filename prefix for all test scripts."
)
_add_value(
"suffix",
options.suffix,
"Script Suffix",
"Script suffix, including .gd extension. For example '_foo.gd'."
)
_add_number(
"paint_after",
options.paint_after,
"Paint After",
0.0,
1.0,
"How long GUT will wait before pausing for 1 frame to paint the screen. 0 is never."
)
# since _add_number doesn't set step property, it will default to a step of
# 1 and cast values to int. Give it a .5 step and re-set the value.
_cfg_ctrls.paint_after.step = .05
_cfg_ctrls.paint_after.value = options.paint_after
print("GUT config loaded")
func get_options(base_opts):
var to_return = base_opts.duplicate()
# Settings
to_return.log_level = _cfg_ctrls.log_level.value
to_return.ignore_pause = _cfg_ctrls.ignore_pause.button_pressed
to_return.hide_orphans = _cfg_ctrls.hide_orphans.button_pressed
to_return.should_exit = _cfg_ctrls.should_exit.button_pressed
to_return.should_exit_on_success = _cfg_ctrls.should_exit_on_success.button_pressed
to_return.double_strategy = _cfg_ctrls.double_strategy.selected
to_return.errors_do_not_cause_failure = !_cfg_ctrls.errors_cause_failure.button_pressed
#Output
to_return.panel_options.font_name = _cfg_ctrls.output_font_name.get_item_text(
_cfg_ctrls.output_font_name.selected
)
to_return.panel_options.font_size = _cfg_ctrls.output_font_size.value
# Runner Appearance
to_return.font_name = _cfg_ctrls.font_name.get_item_text(_cfg_ctrls.font_name.selected)
to_return.font_size = _cfg_ctrls.font_size.value
to_return.should_maximize = _cfg_ctrls.should_maximize.button_pressed
to_return.compact_mode = _cfg_ctrls.compact_mode.button_pressed
to_return.opacity = _cfg_ctrls.opacity.value
to_return.background_color = _cfg_ctrls.background_color.color.to_html()
to_return.font_color = _cfg_ctrls.font_color.color.to_html()
to_return.disable_colors = _cfg_ctrls.disable_colors.button_pressed
to_return.gut_on_top = _cfg_ctrls.gut_on_top.button_pressed
to_return.paint_after = _cfg_ctrls.paint_after.value
# Directories
to_return.include_subdirs = _cfg_ctrls.include_subdirs.button_pressed
var dirs = []
for i in range(DIRS_TO_LIST):
var key = str("directory_", i)
var val = _cfg_ctrls[key].text
if val != "" and val != null:
dirs.append(val)
to_return.dirs = dirs
# XML Output
to_return.junit_xml_file = _cfg_ctrls.junit_xml_file.text
to_return.junit_xml_timestamp = _cfg_ctrls.junit_xml_timestamp.button_pressed
# Hooks
to_return.pre_run_script = _cfg_ctrls.pre_run_script.text
to_return.post_run_script = _cfg_ctrls.post_run_script.text
# Misc
to_return.prefix = _cfg_ctrls.prefix.text
to_return.suffix = _cfg_ctrls.suffix.text
return to_return

View file

@ -1,238 +0,0 @@
extends Control
# ##############################################################################
# This is the decoupled GUI for gut.gd
#
# This is a "generic" interface between a GUI and gut.gd. It assumes there are
# certain controls with specific names. It will then interact with those
# controls based on signals emitted from gut.gd in order to give the user
# feedback about the progress of the test run and the results.
#
# Optional controls are marked as such in the _ctrls dictionary. The names
# of the controls can be found in _populate_ctrls.
# ##############################################################################
var _gut = null
var _ctrls = {
btn_continue = null,
path_dir = null,
path_file = null,
prog_script = null,
prog_test = null,
rtl = null, # optional
rtl_bg = null, # required if rtl exists
switch_modes = null,
time_label = null,
title = null,
title_bar = null,
}
var _title_mouse = {down = false}
var _resize_mouse = {down = false}
var _resize_left_mouse = {down = false}
signal switch_modes
var _max_position = Vector2(100, 100)
var _utils = null
func _ready():
_populate_ctrls()
_ctrls.btn_continue.visible = false
_ctrls.btn_continue.pressed.connect(_on_continue_pressed)
_ctrls.switch_modes.pressed.connect(_on_switch_modes_pressed)
_ctrls.title_bar.gui_input.connect(_on_title_bar_input)
_ctrls.prog_script.value = 0
_ctrls.prog_test.value = 0
_ctrls.path_dir.text = ""
_ctrls.path_file.text = ""
_ctrls.time_label.text = ""
_max_position = get_display_size() - Vector2(30, _ctrls.title_bar.size.y)
func _process(_delta):
if _gut != null and _gut.is_running():
set_elapsed_time(_gut.get_elapsed_time())
# ------------------
# Private
# ------------------
func get_display_size():
return get_viewport().get_visible_rect().size
func _populate_ctrls():
# Brute force, but flexible. This allows for all the controls to exist
# anywhere, and as long as they all have the right name, they will be
# found.
_ctrls.btn_continue = _get_first_child_named("Continue", self)
_ctrls.path_dir = _get_first_child_named("Path", self)
_ctrls.path_file = _get_first_child_named("File", self)
_ctrls.prog_script = _get_first_child_named("ProgressScript", self)
_ctrls.prog_test = _get_first_child_named("ProgressTest", self)
_ctrls.rtl = _get_first_child_named("TestOutput", self)
_ctrls.rtl_bg = _get_first_child_named("OutputBG", self)
_ctrls.switch_modes = _get_first_child_named("SwitchModes", self)
_ctrls.time_label = _get_first_child_named("TimeLabel", self)
_ctrls.title = _get_first_child_named("Title", self)
_ctrls.title_bar = _get_first_child_named("TitleBar", self)
func _get_first_child_named(obj_name, parent_obj):
if parent_obj == null:
return null
var kids = parent_obj.get_children()
var index = 0
var to_return = null
while index < kids.size() and to_return == null:
if str(kids[index]).find(str(obj_name, ":")) != -1:
to_return = kids[index]
else:
to_return = _get_first_child_named(obj_name, kids[index])
if to_return == null:
index += 1
return to_return
# ------------------
# Events
# ------------------
func _on_title_bar_input(event: InputEvent):
if event is InputEventMouseMotion:
if _title_mouse.down:
position += event.relative
position.x = clamp(position.x, 0, _max_position.x)
position.y = clamp(position.y, 0, _max_position.y)
elif event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT:
_title_mouse.down = event.pressed
func _on_continue_pressed():
_gut.end_teardown_pause()
func _on_gut_start_run():
if _ctrls.rtl != null:
_ctrls.rtl.clear()
set_num_scripts(_gut.get_test_collector().scripts.size())
func _on_gut_end_run():
_ctrls.prog_test.value = _ctrls.prog_test.max_value
_ctrls.prog_script.value = _ctrls.prog_script.max_value
func _on_gut_start_script(script_obj):
next_script(script_obj.get_full_name(), script_obj.tests.size())
func _on_gut_end_script():
pass
func _on_gut_start_test(test_name):
next_test(test_name)
func _on_gut_end_test():
pass
func _on_gut_start_pause():
pause_before_teardown()
func _on_gut_end_pause():
_ctrls.btn_continue.visible = false
func _on_switch_modes_pressed():
switch_modes.emit()
# ------------------
# Public
# ------------------
func set_num_scripts(val):
_ctrls.prog_script.value = 0
_ctrls.prog_script.max_value = val
func next_script(path, num_tests):
_ctrls.prog_script.value += 1
_ctrls.prog_test.value = 0
_ctrls.prog_test.max_value = num_tests
_ctrls.path_dir.text = path.get_base_dir()
_ctrls.path_file.text = path.get_file()
func next_test(test_name):
_ctrls.prog_test.value += 1
func pause_before_teardown():
_ctrls.btn_continue.visible = true
func set_gut(g):
if _gut == g:
return
_gut = g
g.start_run.connect(_on_gut_start_run)
g.end_run.connect(_on_gut_end_run)
g.start_script.connect(_on_gut_start_script)
g.end_script.connect(_on_gut_end_script)
g.start_test.connect(_on_gut_start_test)
g.end_test.connect(_on_gut_end_test)
g.start_pause_before_teardown.connect(_on_gut_start_pause)
g.end_pause_before_teardown.connect(_on_gut_end_pause)
func get_gut():
return _gut
func get_textbox():
return _ctrls.rtl
func set_elapsed_time(t):
_ctrls.time_label.text = str("%6.1f" % t, "s")
func set_bg_color(c):
_ctrls.rtl_bg.color = c
func set_title(text):
_ctrls.title.text = text
func to_top_left():
self.position = Vector2(5, 5)
func to_bottom_right():
var win_size = get_display_size()
self.position = win_size - Vector2(self.size) - Vector2(5, 5)
func align_right():
var win_size = get_display_size()
self.position.x = win_size.x - self.size.x - 5
self.position.y = 5
self.size.y = win_size.y - 10

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 B

View file

@ -1,34 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cr6tvdv0ve6cv"
path="res://.godot/imported/play.png-5c90e88e8136487a183a099d67a7de24.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/gut/gui/play.png"
dest_files=["res://.godot/imported/play.png-5c90e88e8136487a183a099d67a7de24.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

View file

@ -1,233 +0,0 @@
# Holds weakrefs to a ScriptTextEditor and related children nodes
# that might be useful. Though the CodeEdit is really the only one, but
# since the tree may change, the first TextEdit under a CodeTextEditor is
# the one we use...so we hold a ref to the CodeTextEditor too.
class ScriptEditorControlRef:
var _text_edit = null
var _script_editor = null
var _code_editor = null
func _init(script_edit):
_script_editor = weakref(script_edit)
_populate_controls()
# print("_script_editor = ", script_edit, ' vis = ', is_visible())
func _populate_controls():
# who knows if the tree will change so get the first instance of each
# type of control we care about. Chances are there won't be more than
# one of these in the future, but their position in the tree may change.
_code_editor = weakref(_get_first_child_named("CodeTextEditor", _script_editor.get_ref()))
_text_edit = weakref(_get_first_child_named("CodeEdit", _code_editor.get_ref()))
func _get_first_child_named(obj_name, parent_obj):
if parent_obj == null:
return null
var kids = parent_obj.get_children()
var index = 0
var to_return = null
while index < kids.size() and to_return == null:
if str(kids[index]).find(str("<", obj_name)) != -1:
to_return = kids[index]
else:
to_return = _get_first_child_named(obj_name, kids[index])
if to_return == null:
index += 1
return to_return
func get_script_text_edit():
return _script_editor.get_ref()
func get_text_edit():
# ScriptTextEditors that are loaded when the project is opened
# do not have their children populated yet. So if we may have to
# _populate_controls again if we don't have one.
if _text_edit == null:
_populate_controls()
return _text_edit.get_ref()
func get_script_editor():
return _script_editor
func is_visible():
var to_return = false
if _script_editor.get_ref():
to_return = _script_editor.get_ref().visible
return to_return
# ##############################################################################
#
# ##############################################################################
# Used to make searching for the controls easier and faster.
var _script_editors_parent = null
# reference the ScriptEditor instance
var _script_editor = null
# Array of ScriptEditorControlRef containing all the opened ScriptTextEditors
# and related controls at the time of the last refresh.
var _script_editor_controls = []
var _method_prefix = "test_"
var _inner_class_prefix = "Test"
func _init(script_edit):
_script_editor = script_edit
refresh()
func _is_script_editor(obj):
return str(obj).find("<ScriptTextEditor") != -1
# Find the first ScriptTextEditor and then get its parent. Done this way
# because who knows if the parent object will change. This is somewhat
# future proofed.
func _find_script_editors_parent():
var _first_editor = _get_first_child_of_type_name("ScriptTextEditor", _script_editor)
if _first_editor != null:
_script_editors_parent = _first_editor.get_parent()
func _populate_editors():
if _script_editors_parent == null:
return
_script_editor_controls.clear()
for child in _script_editors_parent.get_children():
if _is_script_editor(child):
var ref = ScriptEditorControlRef.new(child)
_script_editor_controls.append(ref)
# Yes, this is the same as the one above but with a different name. This was
# easier than trying to find a place where it could be used by both.
func _get_first_child_of_type_name(obj_name, parent_obj):
if parent_obj == null:
# print('aborting search for ', obj_name, ' parent is null')
return null
var kids = parent_obj.get_children()
var index = 0
var to_return = null
var search_for = str("<", obj_name)
# print('searching for ', search_for, ' in ', parent_obj, ' kids ', kids.size())
while index < kids.size() and to_return == null:
var this_one = str(kids[index])
# print(search_for, ' :: ', this_one)
if this_one.find(search_for) != -1:
to_return = kids[index]
else:
to_return = _get_first_child_of_type_name(obj_name, kids[index])
if to_return == null:
index += 1
return to_return
func _get_func_name_from_line(text):
text = text.strip_edges()
var left = text.split("(")[0]
var func_name = left.split(" ")[1]
return func_name
func _get_class_name_from_line(text):
text = text.strip_edges()
var right = text.split(" ")[1]
var the_name = right.rstrip(":")
return the_name
func refresh():
if _script_editors_parent == null:
_find_script_editors_parent()
# print("script editors parent = ", _script_editors_parent)
if _script_editors_parent != null:
_populate_editors()
# print("script editor controls = ", _script_editor_controls)
func get_current_text_edit():
var cur_script_editor = null
var idx = 0
while idx < _script_editor_controls.size() and cur_script_editor == null:
if _script_editor_controls[idx].is_visible():
cur_script_editor = _script_editor_controls[idx]
else:
idx += 1
var to_return = null
if cur_script_editor != null:
to_return = cur_script_editor.get_text_edit()
return to_return
func get_script_editor_controls():
var to_return = []
for ctrl_ref in _script_editor_controls:
to_return.append(ctrl_ref.get_script_text_edit())
return to_return
func get_line_info():
var editor = get_current_text_edit()
if editor == null:
return
var info = {script = null, inner_class = null, test_method = null}
var line = editor.get_caret_line()
var done_func = false
var done_inner = false
while line > 0 and (!done_func or !done_inner):
if editor.can_fold_line(line):
var text = editor.get_line(line)
var strip_text = text.strip_edges(true, false) # only left
if !done_func and strip_text.begins_with("func "):
var func_name = _get_func_name_from_line(text)
if func_name.begins_with(_method_prefix):
info.test_method = func_name
done_func = true
# If the func line is left justified then there won't be any
# inner classes above it.
if strip_text == text:
done_inner = true
if !done_inner and strip_text.begins_with("class"):
var inner_name = _get_class_name_from_line(text)
if inner_name.begins_with(_inner_class_prefix):
info.inner_class = inner_name
done_inner = true
# if we found an inner class then we are already past
# any test the cursor could be in.
done_func = true
line -= 1
return info
func get_method_prefix():
return _method_prefix
func set_method_prefix(method_prefix):
_method_prefix = method_prefix
func get_inner_class_prefix():
return _inner_class_prefix
func set_inner_class_prefix(inner_class_prefix):
_inner_class_prefix = inner_class_prefix

File diff suppressed because it is too large Load diff

View file

@ -1,457 +0,0 @@
# ------------------------------------------------------------------------------
# Description
# -----------
# Command line interface for the GUT unit testing tool. Allows you to run tests
# from the command line instead of running a scene. Place this script along with
# gut.gd into your scripts directory at the root of your project. Once there you
# can run this script (from the root of your project) using the following command:
# godot -s -d test/gut/gut_cmdln.gd
#
# See the readme for a list of options and examples. You can also use the -gh
# option to get more information about how to use the command line interface.
# ------------------------------------------------------------------------------
extends SceneTree
var Optparse = load("res://addons/gut/optparse.gd")
var Gut = load("res://addons/gut/gut.gd")
var GutRunner = load("res://addons/gut/gui/GutRunner.tscn")
var json = JSON.new()
# ------------------------------------------------------------------------------
# Helper class to resolve the various different places where an option can
# be set. Using the get_value method will enforce the order of precedence of:
# 1. command line value
# 2. config file value
# 3. default value
#
# The idea is that you set the base_opts. That will get you a copies of the
# hash with null values for the other types of values. Lower precedented hashes
# will punch through null values of higher precedented hashes.
# ------------------------------------------------------------------------------
class OptionResolver:
var base_opts = {}
var cmd_opts = {}
var config_opts = {}
func get_value(key):
return _nvl(cmd_opts[key], _nvl(config_opts[key], base_opts[key]))
func set_base_opts(opts):
base_opts = opts
cmd_opts = _null_copy(opts)
config_opts = _null_copy(opts)
# creates a copy of a hash with all values null.
func _null_copy(h):
var new_hash = {}
for key in h:
new_hash[key] = null
return new_hash
func _nvl(a, b):
if a == null:
return b
else:
return a
func _string_it(h):
var to_return = ""
for key in h:
to_return += str("(", key, ":", _nvl(h[key], "NULL"), ")")
return to_return
func to_s():
return str(
"base:\n",
_string_it(base_opts),
"\n",
"config:\n",
_string_it(config_opts),
"\n",
"cmd:\n",
_string_it(cmd_opts),
"\n",
"resolved:\n",
_string_it(get_resolved_values())
)
func get_resolved_values():
var to_return = {}
for key in base_opts:
to_return[key] = get_value(key)
return to_return
func to_s_verbose():
var to_return = ""
var resolved = get_resolved_values()
for key in base_opts:
to_return += str(key, "\n")
to_return += str(" default: ", _nvl(base_opts[key], "NULL"), "\n")
to_return += str(" config: ", _nvl(config_opts[key], " --"), "\n")
to_return += str(" cmd: ", _nvl(cmd_opts[key], " --"), "\n")
to_return += str(" final: ", _nvl(resolved[key], "NULL"), "\n")
return to_return
# ------------------------------------------------------------------------------
# Here starts the actual script that uses the Options class to kick off Gut
# and run your tests.
# ------------------------------------------------------------------------------
var _utils = null
var _gut_config = load("res://addons/gut/gut_config.gd").new()
# instance of gut
var _tester = null
# array of command line options specified
var _final_opts = []
func setup_options(options, font_names):
var opts = Optparse.new()
(
opts
. set_banner(
"""
The GUT CLI
-----------
The default behavior for GUT is to load options from a res://.gutconfig.json if
it exists. Any options specified on the command line will take precedence over
options specified in the gutconfig file. You can specify a different gutconfig
file with the -gconfig option.
To generate a .gutconfig.json file you can use -gprint_gutconfig_sample
To see the effective values of a CLI command and a gutconfig use -gpo
Any option that requires a value will take the form of \"-g<name>=<value>\".
There cannot be any spaces between the option, the \"=\", or ' + 'inside a
specified value or godot will think you are trying to run a scene.
"""
)
)
# Run specific things
opts.add(
"-gselect",
"",
"All scripts that contain the specified string in their filename will be ran"
)
opts.add(
"-ginner_class",
"",
"Only run inner classes that contain the specified string int their name."
)
opts.add(
"-gunit_test_name",
"",
"Any test that contains the specified text will be run, all others will be skipped."
)
# Run Config
opts.add("-ginclude_subdirs", false, "Include subdirectories of -gdir.")
opts.add("-gdir", options.dirs, "Comma delimited list of directories to add tests from.")
opts.add("-gtest", [], "Comma delimited list of full paths to test scripts to run.")
opts.add(
"-gprefix",
options.prefix,
'Prefix used to find tests when specifying -gdir. Default "[default]".'
)
opts.add(
"-gsuffix",
options.suffix,
'Test script suffix, including .gd extension. Default "[default]".'
)
opts.add(
"-gconfig",
"res://.gutconfig.json",
"A config file that contains configuration information. Default is res://.gutconfig.json"
)
opts.add("-gpre_run_script", "", "pre-run hook script path")
opts.add("-gpost_run_script", "", "post-run hook script path")
(
opts
. add(
"-gerrors_do_not_cause_failure",
false,
"When an internal GUT error occurs tests will fail. With this option set, that does not happen."
)
)
(
opts
. add(
"-gdouble_strategy",
"SCRIPT_ONLY",
'Default strategy to use when doubling. Valid values are [INCLUDE_NATIVE, SCRIPT_ONLY]. Default "[default]"'
)
)
# Misc
opts.add(
"-gpaint_after",
options.paint_after,
"Delay before GUT will add a 1 frame pause to paint the screen/GUI. default [default]"
)
# Display options
opts.add("-glog", options.log_level, "Log level. Default [default]")
opts.add(
"-ghide_orphans",
false,
'Display orphan counts for tests and scripts. Default "[default]".'
)
opts.add("-gmaximize", false, "Maximizes test runner window to fit the viewport.")
opts.add(
"-gcompact_mode", false, "The runner will be in compact mode. This overrides -gmaximize."
)
opts.add(
"-gopacity",
options.opacity,
"Set opacity of test runner window. Use range 0 - 100. 0 = transparent, 100 = opaque."
)
opts.add("-gdisable_colors", false, "Disable command line colors.")
opts.add(
"-gfont_name",
options.font_name,
str("Valid values are: ", font_names, '. Default "[default]"')
)
opts.add("-gfont_size", options.font_size, 'Font size, default "[default]"')
opts.add(
"-gbackground_color",
options.background_color,
'Background color as an html color, default "[default]"'
)
opts.add("-gfont_color", options.font_color, 'Font color as an html color, default "[default]"')
# End Behavior
opts.add(
"-gexit",
false,
"Exit after running tests. If not specified you have to manually close the window."
)
opts.add("-gexit_on_success", false, "Only exit if all tests pass.")
opts.add("-gignore_pause", false, "Ignores any calls to gut.pause_before_teardown.")
# Helpish options
opts.add(
"-gh", false, "Print this help. You did this to see this, so you probably understand."
)
opts.add("-gpo", false, "Print option values from all sources and the value used.")
opts.add(
"-gprint_gutconfig_sample",
false,
"Print out json that can be used to make a gutconfig file."
)
# Output options
opts.add(
"-gjunit_xml_file",
options.junit_xml_file,
"Export results of run to this file in the Junit XML format."
)
opts.add(
"-gjunit_xml_timestamp",
options.junit_xml_timestamp,
"Include a timestamp in the -gjunit_xml_file, default [default]"
)
return opts
# Parses options, applying them to the _tester or setting values
# in the options struct.
func extract_command_line_options(from, to):
to.config_file = from.get_value("-gconfig")
to.dirs = from.get_value("-gdir")
to.disable_colors = from.get_value("-gdisable_colors")
to.double_strategy = from.get_value("-gdouble_strategy")
to.ignore_pause = from.get_value("-gignore_pause")
to.include_subdirs = from.get_value("-ginclude_subdirs")
to.inner_class = from.get_value("-ginner_class")
to.log_level = from.get_value("-glog")
to.opacity = from.get_value("-gopacity")
to.post_run_script = from.get_value("-gpost_run_script")
to.pre_run_script = from.get_value("-gpre_run_script")
to.prefix = from.get_value("-gprefix")
to.selected = from.get_value("-gselect")
to.should_exit = from.get_value("-gexit")
to.should_exit_on_success = from.get_value("-gexit_on_success")
to.should_maximize = from.get_value("-gmaximize")
to.compact_mode = from.get_value("-gcompact_mode")
to.hide_orphans = from.get_value("-ghide_orphans")
to.suffix = from.get_value("-gsuffix")
to.errors_do_not_cause_failure = from.get_value("-gerrors_do_not_cause_failure")
to.tests = from.get_value("-gtest")
to.unit_test_name = from.get_value("-gunit_test_name")
to.font_size = from.get_value("-gfont_size")
to.font_name = from.get_value("-gfont_name")
to.background_color = from.get_value("-gbackground_color")
to.font_color = from.get_value("-gfont_color")
to.paint_after = from.get_value("-gpaint_after")
to.junit_xml_file = from.get_value("-gjunit_xml_file")
to.junit_xml_timestamp = from.get_value("-gjunit_xml_timestamp")
func _print_gutconfigs(values):
var header = """Here is a sample of a full .gutconfig.json file.
You do not need to specify all values in your own file. The values supplied in
this sample are what would be used if you ran gut w/o the -gprint_gutconfig_sample
option (option priority: command-line, .gutconfig, default)."""
print("\n", header.replace("\n", " "), "\n\n")
var resolved = values
# remove_at some options that don't make sense to be in config
resolved.erase("config_file")
resolved.erase("show_help")
print(
"Here's a config with all the properties set based off of your current command and config."
)
print(json.stringify(resolved, " "))
for key in resolved:
resolved[key] = null
print("\n\nAnd here's an empty config for you fill in what you want.")
print(json.stringify(resolved, " "))
# parse options and run Gut
func _run_gut():
var opt_resolver = OptionResolver.new()
opt_resolver.set_base_opts(_gut_config.default_options)
print("\n\n", " --- Gut ---")
var o = setup_options(_gut_config.default_options, _gut_config.valid_fonts)
var all_options_valid = o.parse()
extract_command_line_options(o, opt_resolver.cmd_opts)
var load_result = _gut_config.load_options_no_defaults(opt_resolver.get_value("config_file"))
# SHORTCIRCUIT
if !all_options_valid or load_result == -1:
quit(1)
else:
opt_resolver.config_opts = _gut_config.options
if o.get_value("-gh"):
print(_utils.get_version_text())
o.print_help()
quit()
elif o.get_value("-gpo"):
print(
(
"All command line options and where they are specified. "
+ 'The "final" value shows which value will actually be used '
+ "based on order of precedence (default < .gutconfig < cmd line)."
+ "\n"
)
)
print(opt_resolver.to_s_verbose())
quit()
elif o.get_value("-gprint_gutconfig_sample"):
_print_gutconfigs(opt_resolver.get_resolved_values())
quit()
else:
_final_opts = opt_resolver.get_resolved_values()
_gut_config.options = _final_opts
var runner = GutRunner.instantiate()
runner.set_cmdln_mode(true)
runner.set_gut_config(_gut_config)
get_root().add_child(runner)
_tester = runner.get_gut()
_tester.connect(
"end_run",
Callable(self, "_on_tests_finished").bind(
_final_opts.should_exit, _final_opts.should_exit_on_success
)
)
runner.run_tests()
# exit if option is set.
func _on_tests_finished(should_exit, should_exit_on_success):
if _final_opts.dirs.size() == 0:
if _tester.get_summary().get_totals().scripts == 0:
var lgr = _tester.logger
(
lgr
. error(
"No directories configured. Add directories with options or a .gutconfig.json file. Use the -gh option for more information."
)
)
var exit_code = 0
if _tester.get_fail_count():
exit_code = 1
# Overwrite the exit code with the post_script
var post_inst = _tester.get_post_run_script_instance()
if post_inst != null and post_inst.get_exit_code() != null:
exit_code = post_inst.get_exit_code()
if should_exit or (should_exit_on_success and _tester.get_fail_count() == 0):
quit(exit_code)
else:
print("Tests finished, exit manually")
# ------------------------------------------------------------------------------
# MAIN
# ------------------------------------------------------------------------------
func _init():
var max_iter = 20
var iter = 0
# Not seen this wait more than 1.
while Engine.get_main_loop() == null and iter < max_iter:
await create_timer(.01).timeout
iter += 1
if Engine.get_main_loop() == null:
push_error("Main loop did not start in time.")
quit(0)
return
_utils = GutUtils.get_instance()
if !_utils.is_version_ok():
print("\n\n", _utils.get_version_text())
push_error(_utils.get_bad_version_text())
quit(1)
else:
_run_gut()
# ##############################################################################
#(G)odot (U)nit (T)est class
#
# ##############################################################################
# The MIT License (MIT)
# =====================
#
# Copyright (c) 2023 Tom "Butch" Wesley
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ##############################################################################

View file

@ -1,209 +0,0 @@
# ##############################################################################
#
# This holds all the configuratoin values for GUT. It can load and save values
# to a json file. It is also responsible for applying these settings to GUT.
#
# ##############################################################################
var Gut = load("res://addons/gut/gut.gd")
var valid_fonts = ["AnonymousPro", "CourierPro", "LobsterTwo", "Default"]
var default_options = {
background_color = Color(.15, .15, .15, 1).to_html(),
config_file = "res://.gutconfig.json",
dirs = [],
disable_colors = false,
# double strategy can be the name of the enum value, the enum value or
# lowercase name with spaces: 0/SCRIPT_ONLY/script only
# The GUI gut config expects the value to be the enum value and not a string
# when saved.
double_strategy = "SCRIPT_ONLY",
# named differently than gut option so we can use it as a flag in the cli
errors_do_not_cause_failure = false,
font_color = Color(.8, .8, .8, 1).to_html(),
font_name = "CourierPrime",
font_size = 16,
hide_orphans = false,
ignore_pause = false,
include_subdirs = false,
inner_class = "",
junit_xml_file = "",
junit_xml_timestamp = false,
log_level = 1,
opacity = 100,
paint_after = .1,
post_run_script = "",
pre_run_script = "",
prefix = "test_",
selected = "",
should_exit = false,
should_exit_on_success = false,
should_maximize = false,
compact_mode = false,
show_help = false,
suffix = ".gd",
tests = [],
unit_test_name = "",
gut_on_top = true,
}
var default_panel_options = {
font_name = "CourierPrime",
font_size = 16,
output_font_name = "CourierPrime",
output_font_size = 30,
hide_result_tree = false,
hide_output_text = false,
hide_settings = false,
use_colors = true
}
var options = default_options.duplicate()
var json = JSON.new()
func _null_copy(h):
var new_hash = {}
for key in h:
new_hash[key] = null
return new_hash
func _load_options_from_config_file(file_path, into):
# SHORTCIRCUIT
if !FileAccess.file_exists(file_path):
if file_path != "res://.gutconfig.json":
print('ERROR: Config File "', file_path, '" does not exist.')
return -1
else:
return 1
var f = FileAccess.open(file_path, FileAccess.READ)
if f == null:
var result = FileAccess.get_open_error()
push_error(str("Could not load data ", file_path, " ", result))
return result
var json = f.get_as_text()
f = null # close file
var test_json_conv = JSON.new()
test_json_conv.parse(json)
var results = test_json_conv.get_data()
# SHORTCIRCUIT
if results == null:
print("\n\n", "!! ERROR parsing file: ", file_path)
print(" at line ", results.error_line, ":")
print(" ", results.error_string)
return -1
# Get all the options out of the config file using the option name. The
# options hash is now the default source of truth for the name of an option.
_load_dict_into(results, into)
return 1
func _load_dict_into(source, dest):
for key in dest:
if source.has(key):
if source[key] != null:
if typeof(source[key]) == TYPE_DICTIONARY:
_load_dict_into(source[key], dest[key])
else:
dest[key] = source[key]
# Apply all the options specified to tester. This is where the rubber meets
# the road.
func _apply_options(opts, gut):
gut.include_subdirectories = opts.include_subdirs
if opts.inner_class != "":
gut.inner_class_name = opts.inner_class
gut.log_level = opts.log_level
gut.ignore_pause_before_teardown = opts.ignore_pause
gut.select_script(opts.selected)
for i in range(opts.dirs.size()):
gut.add_directory(opts.dirs[i], opts.prefix, opts.suffix)
for i in range(opts.tests.size()):
gut.add_script(opts.tests[i])
# Sometimes it is the index, sometimes it's a string. This sets it regardless
gut.double_strategy = GutUtils.get_enum_value(
opts.double_strategy, GutUtils.DOUBLE_STRATEGY, GutUtils.DOUBLE_STRATEGY.INCLUDE_NATIVE
)
gut.unit_test_name = opts.unit_test_name
gut.pre_run_script = opts.pre_run_script
gut.post_run_script = opts.post_run_script
gut.color_output = !opts.disable_colors
gut.show_orphans(!opts.hide_orphans)
gut.junit_xml_file = opts.junit_xml_file
gut.junit_xml_timestamp = opts.junit_xml_timestamp
gut.paint_after = str(opts.paint_after).to_float()
gut.treat_error_as_failure = !opts.errors_do_not_cause_failure
return gut
# --------------------------
# Public
# --------------------------
func write_options(path):
var content = json.stringify(options, " ")
var f = FileAccess.open(path, FileAccess.WRITE)
var result = FileAccess.get_open_error()
if f != null:
f.store_string(content)
f = null # closes file
else:
print("ERROR: could not open file ", path, " ", result)
return result
func load_options(path):
return _load_options_from_config_file(path, options)
func load_panel_options(path):
options["panel_options"] = default_panel_options.duplicate()
return _load_options_from_config_file(path, options)
func load_options_no_defaults(path):
options = _null_copy(default_options)
return _load_options_from_config_file(path, options)
func apply_options(gut):
_apply_options(options, gut)
# ##############################################################################
# The MIT License (MIT)
# =====================
#
# Copyright (c) 2023 Tom "Butch" Wesley
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ##############################################################################

View file

@ -1,24 +0,0 @@
@tool
extends EditorPlugin
var _bottom_panel = null
func _enter_tree():
_bottom_panel = preload("res://addons/gut/gui/GutBottomPanel.tscn").instantiate()
var button = add_control_to_bottom_panel(_bottom_panel, "GUT")
button.shortcut_in_tooltip = true
await get_tree().create_timer(3).timeout
_bottom_panel.set_interface(get_editor_interface())
_bottom_panel.set_plugin(self)
_bottom_panel.set_panel_button(button)
_bottom_panel.load_shortcuts()
func _exit_tree():
# Clean-up of the plugin goes here
# Always remember to remove_at it from the engine when deactivated
remove_control_from_bottom_panel(_bottom_panel)
_bottom_panel.free()

View file

@ -1,90 +0,0 @@
# Temporary base script for gut.gd to hold the things to be remvoed and added
# to some utility somewhere.
extends Node
var _utils = load("res://addons/gut/utils.gd").get_instance()
# ------------------------------------------------------------------------------
# deletes all files in a given directory
# ------------------------------------------------------------------------------
func directory_delete_files(path):
var d = DirAccess.open(path)
# SHORTCIRCUIT
if d == null:
return
# Traversing a directory is kinda odd. You have to start the process of listing
# the contents of a directory with list_dir_begin then use get_next until it
# returns an empty string. Then I guess you should end it.
d.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547
var thing = d.get_next() # could be a dir or a file or something else maybe?
var full_path = ""
while thing != "":
full_path = path + "/" + thing
#file_exists returns fasle for directories
if d.file_exists(full_path):
d.remove(full_path)
thing = d.get_next()
d.list_dir_end()
# ------------------------------------------------------------------------------
# deletes the file at the specified path
# ------------------------------------------------------------------------------
func file_delete(path):
var d = DirAccess.open(path.get_base_dir())
if d != null:
d.remove(path)
# ------------------------------------------------------------------------------
# Checks to see if the passed in file has any data in it.
# ------------------------------------------------------------------------------
func is_file_empty(path):
var f = FileAccess.open(path, FileAccess.READ)
var result = FileAccess.get_open_error()
var empty = true
if result == OK:
empty = f.get_length() == 0
f = null
return empty
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
func get_file_as_text(path):
return _utils.get_file_as_text(path)
# ------------------------------------------------------------------------------
# Creates an empty file at the specified path
# ------------------------------------------------------------------------------
func file_touch(path):
FileAccess.open(path, FileAccess.WRITE)
# ------------------------------------------------------------------------------
# Simulate a number of frames by calling '_process' and '_physics_process' (if
# the methods exist) on an object and all of its descendents. The specified frame
# time, 'delta', will be passed to each simulated call.
#
# NOTE: Objects can disable their processing methods using 'set_process(false)' and
# 'set_physics_process(false)'. This is reflected in the 'Object' methods
# 'is_processing()' and 'is_physics_processing()', respectively. To make 'simulate'
# respect this status, for example if you are testing an object which toggles
# processing, pass 'check_is_processing' as 'true'.
# ------------------------------------------------------------------------------
func simulate(obj, times, delta, check_is_processing: bool = false):
for _i in range(times):
if obj.has_method("_process") and (not check_is_processing or obj.is_processing()):
obj._process(delta)
if (
obj.has_method("_physics_process")
and (not check_is_processing or obj.is_physics_processing())
):
obj._physics_process(delta)
for kid in obj.get_children():
simulate(kid, 1, delta, check_is_processing)

View file

@ -1,47 +0,0 @@
class_name GutHookScript
# ------------------------------------------------------------------------------
# This script is the base for custom scripts to be used in pre and post
# run hooks.
#
# To use, inherit from this script and then implement the run method.
# ------------------------------------------------------------------------------
var JunitXmlExport = load("res://addons/gut/junit_xml_export.gd")
# This is the instance of GUT that is running the tests. You can get
# information about the run from this object. This is set by GUT when the
# script is instantiated.
var gut = null
# the exit code to be used by gut_cmdln. See set method.
var _exit_code = null
var _should_abort = false
# Virtual method that will be called by GUT after instantiating
# this script.
func run():
gut.logger.error(
"Run method not overloaded. Create a 'run()' method in your hook script to run your code."
)
# Set the exit code when running from the command line. If not set then the
# default exit code will be returned (0 when no tests fail, 1 when any tests
# fail).
func set_exit_code(code):
_exit_code = code
func get_exit_code():
return _exit_code
# Usable by pre-run script to cause the run to end AFTER the run() method
# finishes. post-run script will not be ran.
func abort():
_should_abort = true
func should_abort():
return _should_abort

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 B

View file

@ -1,34 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bvo0uao7deu0q"
path="res://.godot/imported/icon.png-91b084043b8aaf2f1c906e7b9fa92969.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/gut/icon.png"
dest_files=["res://.godot/imported/icon.png-91b084043b8aaf2f1c906e7b9fa92969.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

View file

@ -1 +0,0 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m2 2a1 1 0 0 0 -1 1v2 6 2a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-7a1 1 0 0 0 -1-1h-4a1 1 0 0 1 -1-1v-1a1 1 0 0 0 -1-1z" fill="#e0e0e0"/></svg>

Before

Width:  |  Height:  |  Size: 227 B

View file

@ -1,37 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dhvl14hls3y2j"
path="res://.godot/imported/Folder.svg-caa50e6a0be9d456fd81991dfb537916.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/gut/images/Folder.svg"
dest_files=["res://.godot/imported/Folder.svg-caa50e6a0be9d456fd81991dfb537916.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=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View file

@ -1 +0,0 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -1036.4)"><path d="m6 1v1a1 1 0 0 0 -1 1v10h-1v-2h-2v2a1 1 0 0 0 .5.86523 1 1 0 0 0 .5.13477v1h7a2 2 0 0 0 2-2v-8h3v-2a2 2 0 0 0 -2-2z" fill="#e0e0e0" transform="translate(0 1036.4)"/><path d="m6 1c-1.1046 0-2 .89543-2 2v7h-2-1v1 2c0 1.1046.89543 2 2 2s2-.89543 2-2v-10c0-.55228.44772-1 1-1s1 .44772 1 1v1 1 1h1 4v-1h-4v-1-1c0-1.1046-.89543-2-2-2zm-4 10h2v2c0 .55228-.44772 1-1 1s-1-.44772-1-1z" fill="#b3b3b3" transform="translate(0 1036.4)"/><circle cx="3" cy="1048.4" fill="#e0e0e0"/></g></svg>

Before

Width:  |  Height:  |  Size: 606 B

View file

@ -1,37 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cavojn74qp7ij"
path="res://.godot/imported/Script.svg-34c66aae9c985e3e0470426acbbcda04.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/gut/images/Script.svg"
dest_files=["res://.godot/imported/Script.svg-34c66aae9c985e3e0470426acbbcda04.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=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 B

View file

@ -1,34 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bvrnfjkcmpr8s"
path="res://.godot/imported/green.png-e3a17091688e10a7013279b38edc7f8a.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/gut/images/green.png"
dest_files=["res://.godot/imported/green.png-e3a17091688e10a7013279b38edc7f8a.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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 B

View file

@ -1,34 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ba2sgost7my3x"
path="res://.godot/imported/red.png-47a557c3922e800f76686bc1a4ad0c3c.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/gut/images/red.png"
dest_files=["res://.godot/imported/red.png-47a557c3922e800f76686bc1a4ad0c3c.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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 B

View file

@ -1,34 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://o4mo2w2ftx1v"
path="res://.godot/imported/yellow.png-b3cf3d463958a169d909273d3d742052.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/gut/images/yellow.png"
dest_files=["res://.godot/imported/yellow.png-b3cf3d463958a169d909273d3d742052.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

View file

@ -1,70 +0,0 @@
var _registry = {}
func _create_reg_entry(base_path, subpath):
var to_return = {
"base_path": base_path,
"subpath": subpath,
"base_resource": load(base_path),
"full_path": str("'", base_path, "'", subpath)
}
return to_return
func _register_inners(base_path, obj, prev_inner = ""):
var const_map = obj.get_script_constant_map()
var consts = const_map.keys()
var const_idx = 0
while const_idx < consts.size():
var key = consts[const_idx]
var thing = const_map[key]
if typeof(thing) == TYPE_OBJECT:
var cur_inner = str(prev_inner, ".", key)
_registry[thing] = _create_reg_entry(base_path, cur_inner)
_register_inners(base_path, thing, cur_inner)
const_idx += 1
func register(base_script):
var base_path = base_script.resource_path
_register_inners(base_path, base_script)
func get_extends_path(inner_class):
if _registry.has(inner_class):
return _registry[inner_class].full_path
else:
return null
# returns the subpath for the inner class. This includes the leading "." in
# the path.
func get_subpath(inner_class):
if _registry.has(inner_class):
return _registry[inner_class].subpath
else:
return ""
func get_base_path(inner_class):
if _registry.has(inner_class):
return _registry[inner_class].base_path
func has(inner_class):
return _registry.has(inner_class)
func get_base_resource(inner_class):
if _registry.has(inner_class):
return _registry[inner_class].base_resource
func to_s():
var text = ""
for key in _registry:
text += str(key, ": ", _registry[key], "\n")
return text

Some files were not shown because too many files have changed in this diff Show more