-
Notifications
You must be signed in to change notification settings - Fork 108
/
Copy pathtest.gd
2373 lines (2001 loc) · 82.9 KB
/
test.gd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
class_name GutTest
extends Node
## This is the base class for your GUT test scripts.[br]
## [br]
## GUT Wiki: [url=https://gut.readthedocs.io]https://gut.readthedocs.io[/url]
## [br]
## Simple Example
## [codeblock]
## extends GutTest
##
## func before_all():
## gut.p("before_all called"
##
## func before_each():
## gut.p("before_each called")
##
## func after_each():
## gut.p("after_each called")
##
## func after_all():
## gut.p("after_all called")
##
## func test_assert_eq_letters():
## assert_eq("asdf", "asdf", "Should pass")
##
## func test_assert_eq_number_not_equal():
## assert_eq(1, 2, "Should fail. 1 != 2")
## [/codeblock]
const EDITOR_PROPERTY = PROPERTY_USAGE_SCRIPT_VARIABLE | PROPERTY_USAGE_DEFAULT
const VARIABLE_PROPERTY = PROPERTY_USAGE_SCRIPT_VARIABLE
# Convenience copy of GutUtils.DOUBLE_STRATEGY
var DOUBLE_STRATEGY = GutUtils.DOUBLE_STRATEGY
## Reference to [addons/gut/parameter_factory.gd] script.
var ParameterFactory = GutUtils.ParameterFactory
## @ignore
var CompareResult = GutUtils.CompareResult
## Reference to [GutInputFactory] class that was originally used to reference
## the Input Factory before the class_name was introduced.
var InputFactory = GutInputFactory
## Reference to [GutInputSender]. This was the way you got to the [GutInputSender]
## before it was given a [code]class_name[/code]
var InputSender = GutUtils.InputSender
# Need a reference to the instance that is running the tests. This
# is set by the gut class when it runs the test script.
var gut: GutMain = null
var _compare = GutUtils.Comparator.new()
var _disable_strict_datatype_checks = false
# Holds all the text for a test's fail/pass. This is used for testing purposes
# to see the text of a failed sub-test in test_test.gd
var _fail_pass_text = []
# Summary counts for the test.
var _summary = {
asserts = 0,
passed = 0,
failed = 0,
tests = 0,
pending = 0
}
# This is used to watch signals so we can make assertions about them.
var _signal_watcher = load('res://addons/gut/signal_watcher.gd').new()
var _lgr = GutUtils.get_logger()
var _strutils = GutUtils.Strutils.new()
var _awaiter = null
var _was_ready_called = false
# I haven't decided if we should be using _ready or not. Right now gut.gd will
# call this if _ready was not called (because it was overridden without a super
# call). Maybe gut.gd should just call _do_ready_stuff (after we rename it to
# something better). I'm leaving all this as it is until it bothers me more.
func _do_ready_stuff():
_awaiter = GutUtils.Awaiter.new()
add_child(_awaiter)
_was_ready_called = true
func _ready():
_do_ready_stuff()
func _notification(what):
# Tests are never expected to re-enter the tree. Tests are removed from the
# tree after they are run.
if(what == NOTIFICATION_EXIT_TREE):
_awaiter.queue_free()
#region Private
# ----------------
func _str(thing):
return _strutils.type2str(thing)
func _str_precision(value, precision):
var to_return = _str(value)
var format = str('%.', precision, 'f')
if(typeof(value) == TYPE_FLOAT):
to_return = format % value
elif(typeof(value) == TYPE_VECTOR2):
to_return = str('VECTOR2(', format % value.x, ', ', format %value.y, ')')
elif(typeof(value) == TYPE_VECTOR3):
to_return = str('VECTOR3(', format % value.x, ', ', format %value.y, ', ', format % value.z, ')')
return to_return
# Fail an assertion. Causes test and script to fail as well.
func _fail(text):
_summary.asserts += 1
_summary.failed += 1
_fail_pass_text.append('failed: ' + text)
if(gut):
_lgr.failed(gut.get_call_count_text() + text)
gut._fail(text)
# Pass an assertion.
func _pass(text):
_summary.asserts += 1
_summary.passed += 1
_fail_pass_text.append('passed: ' + text)
if(gut):
_lgr.passed(text)
gut._pass(text)
# Checks if the datatypes passed in match. If they do not then this will cause
# a fail to occur. If they match then TRUE is returned, FALSE if not. This is
# used in all the assertions that compare values.
func _do_datatypes_match__fail_if_not(got, expected, text):
var did_pass = true
if(!_disable_strict_datatype_checks):
var got_type = typeof(got)
var expect_type = typeof(expected)
if(got_type != expect_type and got != null and expected != null):
# If we have a mismatch between float and int (types 2 and 3) then
# print out a warning but do not fail.
if([2, 3].has(got_type) and [2, 3].has(expect_type)):
_lgr.warn(str('Warn: Float/Int comparison. Got ', _strutils.types[got_type],
' but expected ', _strutils.types[expect_type]))
elif([TYPE_STRING, TYPE_STRING_NAME].has(got_type) and [TYPE_STRING, TYPE_STRING_NAME].has(expect_type)):
pass
else:
_fail('Cannot compare ' + _strutils.types[got_type] + '[' + _str(got) + '] to ' + \
_strutils.types[expect_type] + '[' + _str(expected) + ']. ' + text)
did_pass = false
return did_pass
# Create a string that lists all the methods that were called on an spied
# instance.
func _get_desc_of_calls_to_instance(inst):
var BULLET = ' * '
var calls = gut.get_spy().get_call_list_as_string(inst)
# indent all the calls
calls = BULLET + calls.replace("\n", "\n" + BULLET)
# remove_at trailing newline and bullet
calls = calls.substr(0, calls.length() - BULLET.length() - 1)
return "Calls made on " + str(inst) + "\n" + calls
# Signal assertion helper. Do not call directly, use _can_make_signal_assertions
func _fail_if_does_not_have_signal(object, signal_name):
var did_fail = false
if(!_signal_watcher.does_object_have_signal(object, signal_name)):
_fail(str('Object ', object, ' does not have the signal [', signal_name, ']'))
did_fail = true
return did_fail
# Signal assertion helper. Do not call directly, use _can_make_signal_assertions
func _fail_if_not_watching(object):
var did_fail = false
if(!_signal_watcher.is_watching_object(object)):
_fail(str('Cannot make signal assertions because the object ', object, \
' is not being watched. Call watch_signals(some_object) to be able to make assertions about signals.'))
did_fail = true
return did_fail
# Returns text that contains original text and a list of all the signals that
# were emitted for the passed in object.
func _get_fail_msg_including_emitted_signals(text, object):
return str(text," (Signals emitted: ", _signal_watcher.get_signals_emitted(object), ")")
# This validates that parameters is an array and generates a specific error
# and a failure with a specific message
func _fail_if_parameters_not_array(parameters):
var invalid = parameters != null and typeof(parameters) != TYPE_ARRAY
if(invalid):
_lgr.error('The "parameters" parameter must be an array of expected parameter values.')
_fail('Cannot compare parameter values because an array was not passed.')
return invalid
# A bunch of common checkes used when validating a double/method pair. If
# everything is ok then an empty string is returned, otherwise the message
# is returned.
func _get_bad_double_or_method_message(inst, method_name, what_you_cant_do):
var to_return = ''
if(!GutUtils.is_double(inst)):
to_return = str("An instance of a Double was expected, you passed: ", _str(inst))
elif(!inst.has_method(method_name)):
to_return = str("You cannot ", what_you_cant_do, " [", method_name, "] because the method does not exist. ",
"This can happen if the method is virtual and not overloaded (i.e. _ready) ",
"or you have mistyped the name of the method.")
elif(!inst.__gutdbl_values.doubled_methods.has(method_name)):
to_return = str("You cannot ", what_you_cant_do, " [", method_name, "] because ",
_str(inst), ' does not overload it or it was ignored with ',
'ignore_method_when_doubling. See Doubling ',
'Strategy in the wiki for details on including non-overloaded ',
'methods in a double.')
return to_return
func _fail_if_not_double_or_does_not_have_method(inst, method_name):
var to_return = OK
var msg = _get_bad_double_or_method_message(inst, method_name, 'spy on')
if(msg != ''):
_fail(msg)
to_return = ERR_INVALID_DATA
return to_return
func _create_obj_from_type(type):
var obj = null
if type.is_class("PackedScene"):
obj = type.instantiate()
add_child(obj)
else:
obj = type.new()
return obj
# Converts a Callabe passed through inst or inst/method_name/parameters into a
# hash so that methods that interact with Spy can accept both more easily.
func _convert_spy_args(inst, method_name, parameters):
var to_return = {
'object':inst,
'method_name':method_name,
'arguments':parameters,
'invalid_message':'ok'
}
if(inst is Callable):
if(parameters != null):
to_return.invalid_message =\
"3rd parameter to assert_called not supported when using a Callable."
elif(method_name != null):
to_return.invalid_message =\
"2nd parameter to assert_called not supported when using a Callable."
else:
if(inst.get_bound_arguments_count() > 0):
to_return.arguments = inst.get_bound_arguments()
to_return.method_name = inst.get_method()
to_return.object = inst.get_object()
return to_return
func _get_typeof_string(the_type):
var to_return = ""
if(_strutils.types.has(the_type)):
to_return += str(the_type, '(', _strutils.types[the_type], ')')
else:
to_return += str(the_type)
return to_return
# Validates the singleton_name is a string and exists. Errors when conditions
# are not met. Returns true/false if singleton_name is valid or not.
func _validate_singleton_name(singleton_name):
var is_valid = true
if(typeof(singleton_name) != TYPE_STRING):
_lgr.error("double_singleton requires a Godot singleton name, you passed " + _str(singleton_name))
is_valid = false
# Sometimes they have underscores in front of them, sometimes they do not.
# The doubler is smart enought of ind the right thing, so this has to be
# that smart as well.
elif(!ClassDB.class_exists(singleton_name) and !ClassDB.class_exists('_' + singleton_name)):
var txt = str("The singleton [", singleton_name, "] could not be found. ",
"Check the GlobalScope page for a list of singletons.")
_lgr.error(txt)
is_valid = false
return is_valid
# Checks the object for 'get_' and 'set_' methods for the specified property.
# If found a warning is generated.
func _warn_for_public_accessors(obj, property_name):
var public_accessors = []
var accessor_names = [
str('get_', property_name),
str('is_', property_name),
str('set_', property_name)
]
for acc in accessor_names:
if(obj.has_method(acc)):
public_accessors.append(acc)
if(public_accessors.size() > 0):
_lgr.warn (str('Public accessors ', public_accessors, ' found for property ', property_name))
func _smart_double(thing, double_strat, partial):
var override_strat = GutUtils.nvl(double_strat, gut.get_doubler().get_strategy())
var to_return = null
if(thing is PackedScene):
if(partial):
to_return = gut.get_doubler().partial_double_scene(thing, override_strat)
else:
to_return = gut.get_doubler().double_scene(thing, override_strat)
elif(GutUtils.is_native_class(thing)):
if(partial):
to_return = gut.get_doubler().partial_double_gdnative(thing)
else:
to_return = gut.get_doubler().double_gdnative(thing)
elif(thing is GDScript):
if(partial):
to_return = gut.get_doubler().partial_double(thing, override_strat)
else:
to_return = gut.get_doubler().double(thing, override_strat)
return to_return
# This is here to aid in the transition to the new doubling sytnax. Once this
# has been established it could be removed. We must keep the is_instance check
# going forward though.
func _are_double_parameters_valid(thing, p2, p3):
var bad_msg = ""
if(p3 != null or typeof(p2) == TYPE_STRING):
bad_msg += "Doubling using a subpath is not supported. Call register_inner_class and then pass the Inner Class to double().\n"
if(typeof(thing) == TYPE_STRING):
bad_msg += "Doubling using the path to a script or scene is no longer supported. Load the script or scene and pass that to double instead.\n"
if(GutUtils.is_instance(thing)):
bad_msg += "double requires a script, you passed an instance: " + _str(thing)
if(bad_msg != ""):
_lgr.error(bad_msg)
return bad_msg == ""
# ----------------
#endregion
#region Virtual Methods
# ----------------
## Virtual Method. This is run after the script has been prepped for execution, but before `before_all` is executed. If you implement this method and return `true` or a `String` (the string is displayed in the log) then GUT will stop executing the script and mark it as risky. You might want to do this because:
## - You are porting tests from 3.x to 4.x and you don't want to comment everything out.[br]
## - Skipping tests that should not be run when in `headless` mode such as input testing that does not work in headless.[br]
## [codeblock]
## func should_skip_script():
## if DisplayServer.get_name() == "headless":
## return "Skip Input tests when running headless"
## [/codeblock]
## - If you have tests that would normally cause the debugger to break on an error, you can skip the script if the debugger is enabled so that the run is not interrupted.[br]
## [codeblock]
## func should_skip_script():
## return EngineDebugger.is_active()
## [/codeblock]
func should_skip_script():
return false
## Virtual method. Run once before anything else in the test script is run.
func before_all():
pass
## Virtual method. Run before each test is executed
func before_each():
pass
## Virtual method. Run after each test is executed.
func after_each():
pass
## Virtual method. Run after all tests have been run.
func after_all():
pass
# ----------------
#endregion
#region Misc Public
# ----------------
## Mark the current test as pending.
func pending(text=""):
_summary.pending += 1
if(gut):
_lgr.pending(text)
gut._pending(text)
## Returns true if the test is passing as of the time of this call. False if not.
func is_passing():
if(gut.get_current_test_object() != null and
!['before_all', 'after_all'].has(gut.get_current_test_object().name)):
return gut.get_current_test_object().is_passing() and \
gut.get_current_test_object().assert_count > 0
else:
_lgr.error('No current test object found. is_passing must be called inside a test.')
return null
## Returns true if the test is failing as of the time of this call. False if not.
func is_failing():
if(gut.get_current_test_object() != null and
!['before_all', 'after_all'].has(gut.get_current_test_object().name)):
return gut.get_current_test_object().is_failing()
else:
_lgr.error('No current test object found. is_failing must be called inside a test.')
return null
## Marks the test as passing. Does not override any failing asserts or calls to
## fail_test. Same as a passing assert.
func pass_test(text):
_pass(text)
## Marks the test as failing. Same as a failing assert.
func fail_test(text):
_fail(text)
## @internal
func clear_signal_watcher():
_signal_watcher.clear()
## Returns the current double strategy.
func get_double_strategy():
return gut.get_doubler().get_strategy()
## Sets the double strategy for all tests in the script. This should usually
## be done in [method before_all]. The double strtegy can be set per
## run/script/double. See [wiki]Double-Strategy[/wiki]
func set_double_strategy(double_strategy):
gut.get_doubler().set_strategy(double_strategy)
## This method will cause Gut to pause before it moves on to the next test.
## This is useful for debugging, for instance if you want to investigate the
## screen or anything else after a test has finished executing.
## [br]
## Sometimes you get lazy, and you don't remove calls to
## [code skip-lint]pause_before_teardown[/code] after you are done with them. You can
## tell GUT to ignore calls to to this method through the panel or
## the command line. Setting this in your `.gutconfig.json` file is recommended
## for CI/CD Pipelines.
func pause_before_teardown():
gut.pause_before_teardown()
## @internal
func get_logger():
return _lgr
## @internal
func set_logger(logger):
_lgr = logger
## This must be called in order to make assertions based on signals being
## emitted. __Right now, this only supports signals that are emitted with 9 or
## less parameters.__ This can be extended but nine seemed like enough for now.
## The Godot documentation suggests that the limit is four but in my testing
## I found you can pass more.
## [br]
## This must be called in each test in which you want to make signal based
## assertions in. You can call it multiple times with different objects.
## You should not call it multiple times with the same object in the same test.
## The objects that are watched are cleared after each test (specifically right
## before `teardown` is called). Under the covers, Gut will connect to all the
## signals an object has and it will track each time they fire. You can then
## use the following asserts and methods to verify things are acting correct.
func watch_signals(object):
_signal_watcher.watch_signals(object)
## This will return the number of times a signal was fired. This gives you
## the freedom to make more complicated assertions if the spirit moves you.
## This will return -1 if the signal was not fired or the object was not being
## watched, or if the object does not have the signal.
func get_signal_emit_count(object, signal_name):
return _signal_watcher.get_emit_count(object, signal_name)
## If you need to inspect the parameters in order to make more complicate
## assertions, then this will give you access to the parameters of any watched
## signal. This works the same way that
## [code skip-lint]assert_signal_emitted_with_parameters[/code] does. It takes an
## object, signal name, and an optional index. If the index is not specified
## then the parameters from the most recent emission will be returned. If the
## object is not being watched, the signal was not fired, or the object does
## not have the signal then `null` will be returned.
## [codeblock]
## class SignalObject:
## signal some_signal
## signal other_signal
##
## func test_get_signal_parameters():
## var obj = SignalObject.new()
## watch_signals(obj)
## obj.some_signal.emit(1, 2, 3)
## obj.some_signal.emit('a', 'b', 'c')
##
## # Passing
## # passes because get_signal_parameters returns the most recent emission
## # by default
## assert_eq(get_signal_parameters(obj, 'some_signal'), ['a', 'b', 'c'])
## assert_eq(get_signal_parameters(obj, 'some_signal', 0), [1, 2, 3])
## # if the signal was not fired null is returned
## assert_eq(get_signal_parameters(obj, 'other_signal'), null)
## # if the signal does not exist or isn't being watched null is returned
## assert_eq(get_signal_parameters(obj, 'signal_dne'), null)
##
## # Failing
## assert_eq(get_signal_parameters(obj, 'some_signal'), [1, 2, 3])
## assert_eq(get_signal_parameters(obj, 'some_signal', 0), ['a', 'b', 'c'])
## [/codeblock]
func get_signal_parameters(object, signal_name, index=-1):
return _signal_watcher.get_signal_parameters(object, signal_name, index)
## Get the parameters for a method call to a doubled object. By default it will
## return the most recent call. You can optionally specify an index for which
## call you want to get the parameters for.
##
## Can be called using a Callable for the first parameter instead of specifying
## an object and method name. When you do this, the seoncd parameter is used
## as the index.
##
## Returns:
## * an array of parameter values if a call the method was found
## * null when a call to the method was not found or the index specified was
## invalid.
func get_call_parameters(object, method_name_or_index = -1, idx=-1):
var to_return = null
var index = idx
if(object is Callable):
index = method_name_or_index
method_name_or_index = null
var converted = _convert_spy_args(object, method_name_or_index, null)
if(GutUtils.is_double(converted.object)):
to_return = gut.get_spy().get_call_parameters(
converted.object, converted.method_name, index)
else:
_lgr.error('You must pass a doulbed object to get_call_parameters.')
return to_return
## Returns the call count for a method with optional paramter matching.
##
## Can be called with a Callable instead of an object, method_name, and
## parameters. Bound arguments will be used to match call arguments.
func get_call_count(object, method_name=null, parameters=null):
var converted = _convert_spy_args(object, method_name, parameters)
return gut.get_spy().call_count(converted.object, converted.method_name, converted.arguments)
## 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):
gut.simulate(obj, times, delta, check_is_processing)
# ------------------------------------------------------------------------------
## Replace the node at base_node.get_node(path) with with_this. All references
## to the node via $ and get_node(...) will now return with_this. with_this will
## get all the groups that the node that was replaced had.
## [br]
## The node that was replaced is queued to be freed.
## [br]
## TODO see replace_by method, this could simplify the logic here.
# ------------------------------------------------------------------------------
func replace_node(base_node, path_or_node, with_this):
var path = path_or_node
if(typeof(path_or_node) != TYPE_STRING):
# This will cause an engine error if it fails. It always returns a
# NodePath, even if it fails. Checking the name count is the only way
# I found to check if it found something or not (after it worked I
# didn't look any farther).
path = base_node.get_path_to(path_or_node)
if(path.get_name_count() == 0):
_lgr.error('You passed an object that base_node does not have. Cannot replace node.')
return
if(!base_node.has_node(path)):
_lgr.error(str('Could not find node at path [', path, ']'))
return
var to_replace = base_node.get_node(path)
var parent = to_replace.get_parent()
var replace_name = to_replace.get_name()
parent.remove_child(to_replace)
parent.add_child(with_this)
with_this.set_name(replace_name)
with_this.set_owner(parent)
var groups = to_replace.get_groups()
for i in range(groups.size()):
with_this.add_to_group(groups[i])
to_replace.queue_free()
## Use this as the default value for the first parameter to a test to create
## a parameterized test. See also the ParameterFactory and Parameterized Tests.
## [br][br]
## [b]Example[/b]
## [codeblock]
## func test_with_parameters(p = use_parameters([1, 2, 3])):
## [/codeblock]
func use_parameters(params):
var ph = gut.parameter_handler
if(ph == null):
ph = GutUtils.ParameterHandler.new(params)
gut.parameter_handler = ph
# DO NOT use gut.gd's get_call_count_text here since it decrements the
# get_call_count value. This method increments the call count in its
# return statement.
var output = str('- params[', ph.get_call_count(), ']','(', ph.get_current_parameters(), ')')
gut.p(output, gut.LOG_LEVEL_TEST_AND_FAILURES)
return ph.next_parameters()
## @internal
## When used as the default for a test method parameter, it will cause the test
## to be run x times.
##
## I Hacked this together to test a method that was occassionally failing due to
## timing issues. I don't think it's a great idea, but you be the judge. If
## you find a good use for it, let me know and I'll make it a legit member
## of the api.
func run_x_times(x):
var ph = gut.parameter_handler
if(ph == null):
_lgr.warn(
str("This test uses run_x_times and you really should not be ",
"using it. I don't think it's a good thing, but I did find it ",
"temporarily useful so I left it in here and didn't document it. ",
"Well, you found it, might as well open up an issue and let me ",
"know why you're doing this."))
var params = []
for i in range(x):
params.append(i)
ph = GutUtils.ParameterHandler.new(params)
gut.parameter_handler = ph
return ph.next_parameters()
## Checks the passed in version string (x.x.x) against the engine version to see
## if the engine version is less than the expected version. If it is then the
## test is mareked as passed (for a lack of anything better to do). The result
## of the check is returned.
## [br][br]
## [b]Example[/b]
## [codeblock]
## if(skip_if_godot_version_lt('3.5.0')):
## return
## [/codeblock]
func skip_if_godot_version_lt(expected):
var should_skip = !GutUtils.is_godot_version_gte(expected)
if(should_skip):
_pass(str('Skipping: ', GutUtils.godot_version_string(), ' is less than ', expected))
return should_skip
## Checks if the passed in version matches the engine version. The passed in
## version can contain just the major, major.minor or major.minor.path. If
## the version is not the same then the test is marked as passed. The result of
## the check is returned.
## [br][br]
## [b]Example[/b]
## [codeblock]
## if(skip_if_godot_version_ne('3.4')):
## return
## [/codeblock]
func skip_if_godot_version_ne(expected):
var should_skip = !GutUtils.is_godot_version(expected)
if(should_skip):
_pass(str('Skipping: ', GutUtils.godot_version_string(), ' is not ', expected))
return should_skip
## Registers all the inner classes in a script with the doubler. This is required
## before you can double any inner class.
func register_inner_classes(base_script):
gut.get_doubler().inner_class_registry.register(base_script)
## Peforms a deep compare on both values, a CompareResult instnace is returned.
## The optional max_differences paramter sets the max_differences to be displayed.
func compare_deep(v1, v2, max_differences=null):
var result = _compare.deep(v1, v2)
if(max_differences != null):
result.max_differences = max_differences
return result
# ----------------
#endregion
#region Asserts
# ----------------
## Asserts that the expected value equals the value got.
## assert got == expected and prints optional text. See [wiki]Comparing-Things[/wiki]
## for information about comparing dictionaries and arrays.
## [br]
## See also: [method assert_ne], [method assert_same], [method assert_not_same]
## [codeblock]
## var one = 1
## var node1 = Node.new()
## var node2 = node1
##
## # Passing
## assert_eq(one, 1, 'one should equal one')
## assert_eq('racecar', 'racecar')
## assert_eq(node2, node1)
## assert_eq([1, 2, 3], [1, 2, 3])
## var d1_pass = {'a':1}
## var d2_pass = d1_pass
## assert_eq(d1_pass, d2_pass)
##
## # Failing
## assert_eq(1, 2) # FAIL
## assert_eq('hello', 'world')
## assert_eq(self, node1)
## assert_eq([1, 'two', 3], [1, 2, 3, 4])
## assert_eq({'a':1}, {'a':1})
## [/codeblock]
func assert_eq(got, expected, text=""):
if(_do_datatypes_match__fail_if_not(got, expected, text)):
var disp = "[" + _str(got) + "] expected to equal [" + _str(expected) + "]: " + text
var result = null
result = _compare.simple(got, expected)
if(typeof(got) in [TYPE_ARRAY, TYPE_DICTIONARY]):
disp = str(result.summary, ' ', text)
_lgr.info('Array/Dictionary compared by value. Use assert_same to compare references. Use assert_eq_deep to see diff when failing.')
if(result.are_equal):
_pass(disp)
else:
_fail(disp)
## asserts got != expected and prints optional text. See
## [wiki]Comparing-Things[/wiki] for information about comparing dictionaries
## and arrays.
##[br]
## See also: [method assert_eq], [method assert_same], [method assert_not_same]
## [codeblock]
## var two = 2
## var node1 = Node.new()
##
## # Passing
## assert_ne(two, 1, 'Two should not equal one.')
## assert_ne('hello', 'world')
## assert_ne(self, node1)
##
## # Failing
## assert_ne(two, 2)
## assert_ne('one', 'one')
## assert_ne('2', 2)
## [/codeblock]
func assert_ne(got, not_expected, text=""):
if(_do_datatypes_match__fail_if_not(got, not_expected, text)):
var disp = "[" + _str(got) + "] expected to not equal [" + _str(not_expected) + "]: " + text
var result = null
result = _compare.simple(got, not_expected)
if(typeof(got) in [TYPE_ARRAY, TYPE_DICTIONARY]):
disp = str(result.summary, ' ', text)
_lgr.info('Array/Dictionary compared by value. Use assert_not_same to compare references. Use assert_ne_deep to see diff.')
if(result.are_equal):
_fail(disp)
else:
_pass(disp)
## Asserts that [param got] is within the range of [param expected] +/- [param error_interval].
## The upper and lower bounds are included in the check. Verified to work with
## integers, floats, and Vector2. Should work with anything that can be
## added/subtracted.
##
## [codeblock]
## # Passing
## assert_almost_eq(0, 1, 1, '0 within range of 1 +/- 1')
## assert_almost_eq(2, 1, 1, '2 within range of 1 +/- 1')
## assert_almost_eq(1.2, 1.0, .5, '1.2 within range of 1 +/- .5')
## assert_almost_eq(.5, 1.0, .5, '.5 within range of 1 +/- .5')
## assert_almost_eq(Vector2(.5, 1.5), Vector2(1.0, 1.0), Vector2(.5, .5))
## assert_almost_eq(Vector2(.5, 1.5), Vector2(1.0, 1.0), Vector2(.25, .25))
##
## # Failing
## assert_almost_eq(1, 3, 1, '1 outside range of 3 +/- 1')
## assert_almost_eq(2.6, 3.0, .2, '2.6 outside range of 3 +/- .2')
## [/codeblock]
func assert_almost_eq(got, expected, error_interval, text=''):
var disp = "[" + _str_precision(got, 20) + "] expected to equal [" + _str(expected) + "] +/- [" + str(error_interval) + "]: " + text
if(_do_datatypes_match__fail_if_not(got, expected, text) and _do_datatypes_match__fail_if_not(got, error_interval, text)):
if not _is_almost_eq(got, expected, error_interval):
_fail(disp)
else:
_pass(disp)
## This is the inverse of [method assert_almost_eq]. This will pass if [param got] is
## outside the range of [param not_expected] +/- [param error_interval].
func assert_almost_ne(got, not_expected, error_interval, text=''):
var disp = "[" + _str_precision(got, 20) + "] expected to not equal [" + _str(not_expected) + "] +/- [" + str(error_interval) + "]: " + text
if(_do_datatypes_match__fail_if_not(got, not_expected, text) and _do_datatypes_match__fail_if_not(got, error_interval, text)):
if _is_almost_eq(got, not_expected, error_interval):
_fail(disp)
else:
_pass(disp)
# ------------------------------------------------------------------------------
# Helper function compares a value against a expected and a +/- range. Compares
# all components of Vector2, Vector3, and Vector4 as well.
# ------------------------------------------------------------------------------
func _is_almost_eq(got, expected, error_interval) -> bool:
var result = false
var upper = expected + error_interval
var lower = expected - error_interval
if typeof(got) in [TYPE_VECTOR2, TYPE_VECTOR3, TYPE_VECTOR4]:
result = got.clamp(lower, upper) == got
else:
result = got >= (lower) and got <= (upper)
return(result)
## assserts got > expected
## [codeblock]
## var bigger = 5
## var smaller = 0
##
## # Passing
## assert_gt(bigger, smaller, 'Bigger should be greater than smaller')
## assert_gt('b', 'a')
## assert_gt('a', 'A')
## assert_gt(1.1, 1)
##
## # Failing
## assert_gt('a', 'a')
## assert_gt(1.0, 1)
## assert_gt(smaller, bigger)
## [/codeblock]
func assert_gt(got, expected, text=""):
var disp = "[" + _str(got) + "] expected to be > than [" + _str(expected) + "]: " + text
if(_do_datatypes_match__fail_if_not(got, expected, text)):
if(got > expected):
_pass(disp)
else:
_fail(disp)
## Asserts got is greater than or equal to expected.
## [codeblock]
## var bigger = 5
## var smaller = 0
##
## # Passing
## assert_gte(bigger, smaller, 'Bigger should be greater than or equal to smaller')
## assert_gte('b', 'a')
## assert_gte('a', 'A')
## assert_gte(1.1, 1)
## assert_gte('a', 'a')
##
## # Failing
## assert_gte(0.9, 1.0)
## assert_gte(smaller, bigger)
## [/codeblock]
func assert_gte(got, expected, text=""):
var disp = "[" + _str(got) + "] expected to be >= than [" + _str(expected) + "]: " + text
if(_do_datatypes_match__fail_if_not(got, expected, text)):
if(got >= expected):
_pass(disp)
else:
_fail(disp)
## Asserts [param got] is less than [param expected]
## [codeblock]
## var bigger = 5
## var smaller = 0
##
## # Passing
## assert_lt(smaller, bigger, 'Smaller should be less than bigger')
## assert_lt('a', 'b')
## assert_lt(99, 100)
##
## # Failing
## assert_lt('z', 'x')
## assert_lt(-5, -5)
## [/codeblock]
func assert_lt(got, expected, text=""):
var disp = "[" + _str(got) + "] expected to be < than [" + _str(expected) + "]: " + text
if(_do_datatypes_match__fail_if_not(got, expected, text)):
if(got < expected):
_pass(disp)
else:
_fail(disp)
## Asserts got is less than or equal to expected
func assert_lte(got, expected, text=""):
var disp = "[" + _str(got) + "] expected to be <= than [" + _str(expected) + "]: " + text
if(_do_datatypes_match__fail_if_not(got, expected, text)):
if(got <= expected):
_pass(disp)
else:
_fail(disp)
## asserts that got is true. Does not assert truthiness, only boolean values
## will pass.
func assert_true(got, text=""):
if(typeof(got) == TYPE_BOOL):
if(got):
_pass(text)
else:
_fail(text)
else:
var msg = str("Cannot convert ", _strutils.type2str(got), " to boolean")
_fail(msg)
## Asserts that got is false. Does not assert truthiness, only boolean values
## will pass.
func assert_false(got, text=""):
if(typeof(got) == TYPE_BOOL):
if(got):
_fail(text)
else:
_pass(text)
else:
var msg = str("Cannot convert ", _strutils.type2str(got), " to boolean")
_fail(msg)
## Asserts value is between (inclusive) the two expected values.[br]
## got >= expect_low and <= expect_high
## [codeblock]
## # Passing
## assert_between(5, 0, 10, 'Five should be between 0 and 10')
## assert_between(10, 0, 10)
## assert_between(0, 0, 10)
## assert_between(2.25, 2, 4.0)
##
## # Failing
## assert_between('a', 'b', 'c')
## assert_between(1, 5, 10)
## [/codeblock]