5
5
*/
6
6
7
7
use godot:: bind:: { godot_api, GodotClass } ;
8
- use godot:: builtin:: ToVariant ;
9
- use godot:: engine:: utilities:: print_rich;
10
8
use godot:: init:: { gdextension, ExtensionLibrary } ;
11
9
use godot:: sys;
12
10
use godot:: test:: itest;
@@ -32,40 +30,8 @@ mod utilities_test;
32
30
mod variant_test;
33
31
mod virtual_methods_test;
34
32
35
- #[ must_use]
36
- fn run_test ( test : & TestCase ) -> bool {
37
- // Explicit type to prevent tests from returning a value
38
- let success: Option < ( ) > =
39
- godot:: private:: handle_panic ( || format ! ( " !! Test {} failed" , test. name) , test. function ) ;
40
-
41
- success. is_some ( )
42
- }
43
-
44
- sys:: plugin_registry!( __GODOT_ITEST: TestCase ) ;
45
-
46
- fn print_test ( test : & TestCase , passed : bool , last_file : & mut Option < & ' static str > ) {
47
- // Check if we need to open a new category for a file
48
- let print_file = last_file. map_or ( true , |last_file| last_file != test. file ) ;
49
- if print_file {
50
- let sep_pos = test. file . rfind ( & [ '/' , '\\' ] ) . unwrap_or ( 0 ) ;
51
- println ! ( "\n {}:" , & test. file[ sep_pos + 1 ..] ) ;
52
- }
53
-
54
- // Print the test itself
55
- let outcome = if passed {
56
- "[color=green]ok[/color]"
57
- } else {
58
- "[color=red]FAILED[/color]"
59
- } ;
60
- let output = format ! ( " -- {} ... {}" , test. name, outcome) ;
61
- print_rich ( output. to_variant ( ) , & [ ] ) ;
62
-
63
- // State update for file-category-print
64
- * last_file = Some ( test. file ) ;
65
- }
66
-
67
33
// ----------------------------------------------------------------------------------------------------------------------------------------------
68
- // Implementation
34
+ // Entry point + main runner class
69
35
70
36
#[ gdextension( entry_point=itest_init) ]
71
37
unsafe impl ExtensionLibrary for IntegrationTests { }
@@ -119,18 +85,78 @@ impl IntegrationTests {
119
85
120
86
let mut last_file = None ;
121
87
for test in tests {
122
- let passed = run_test ( & test) ;
88
+ let outcome = run_test ( & test) ;
123
89
124
90
self . tests_run += 1 ;
125
- if passed {
126
- self . tests_passed += 1 ;
91
+ match outcome {
92
+ TestOutcome :: Passed => self . tests_passed += 1 ,
93
+ TestOutcome :: Failed => { }
94
+ TestOutcome :: Skipped => self . tests_skipped += 1 ,
127
95
}
128
96
129
- print_test ( & test, passed , & mut last_file) ;
97
+ print_test ( & test, outcome , & mut last_file) ;
130
98
}
131
99
}
132
100
}
133
101
102
+ // ----------------------------------------------------------------------------------------------------------------------------------------------
103
+ // Implementation
104
+
105
+ // Registers all the tests
106
+ sys:: plugin_registry!( __GODOT_ITEST: TestCase ) ;
107
+
108
+ // For more colors, see https://stackoverflow.com/a/54062826
109
+ // To experiment with colors, add `rand` dependency and add following code above.
110
+ // use rand::seq::SliceRandom;
111
+ // let outcome = [TestOutcome::Passed, TestOutcome::Failed, TestOutcome::Skipped];
112
+ // let outcome = outcome.choose(&mut rand::thread_rng()).unwrap();
113
+ const FMT_GREEN : & str = "\x1b [32m" ;
114
+ const FMT_YELLOW : & str = "\x1b [33m" ;
115
+ const FMT_RED : & str = "\x1b [31m" ;
116
+ const FMT_END : & str = "\x1b [0m" ;
117
+
118
+ fn run_test ( test : & TestCase ) -> TestOutcome {
119
+ if test. skipped {
120
+ return TestOutcome :: Skipped ;
121
+ }
122
+
123
+ // Explicit type to prevent tests from returning a value
124
+ let success: Option < ( ) > =
125
+ godot:: private:: handle_panic ( || format ! ( " !! Test {} failed" , test. name) , test. function ) ;
126
+
127
+ if success. is_some ( ) {
128
+ TestOutcome :: Passed
129
+ } else {
130
+ TestOutcome :: Failed
131
+ }
132
+ }
133
+
134
+ /// Prints a test name and its outcome.
135
+ ///
136
+ /// Note that this is run after a test run, so stdout/stderr output during the test will be printed before.
137
+ fn print_test ( test : & TestCase , outcome : TestOutcome , last_file : & mut Option < & ' static str > ) {
138
+ // Check if we need to open a new category for a file
139
+ let print_file = last_file. map_or ( true , |last_file| last_file != test. file ) ;
140
+ if print_file {
141
+ let sep_pos = test. file . rfind ( & [ '/' , '\\' ] ) . unwrap_or ( 0 ) ;
142
+ println ! ( "\n {}:" , & test. file[ sep_pos + 1 ..] ) ;
143
+ }
144
+
145
+ // Do not use print_rich() from Godot, because it's very slow and significantly delays test execution.
146
+ let test_name = test. name ;
147
+ let end = FMT_END ;
148
+ let ( col, outcome) = match outcome {
149
+ TestOutcome :: Passed => ( FMT_GREEN , "ok" ) ,
150
+ TestOutcome :: Failed => ( FMT_RED , "FAILED" ) ,
151
+ TestOutcome :: Skipped => ( FMT_YELLOW , "ignored" ) ,
152
+ } ;
153
+
154
+ println ! ( " -- {test_name} ... {col}{outcome}{end}" ) ;
155
+
156
+ // State update for file-category-print
157
+ * last_file = Some ( test. file ) ;
158
+ }
159
+
134
160
pub ( crate ) fn expect_panic ( context : & str , code : impl FnOnce ( ) + panic:: UnwindSafe ) {
135
161
// Exchange panic hook, to disable printing during expected panics
136
162
let prev_hook = panic:: take_hook ( ) ;
@@ -150,7 +176,15 @@ pub(crate) fn expect_panic(context: &str, code: impl FnOnce() + panic::UnwindSaf
150
176
struct TestCase {
151
177
name : & ' static str ,
152
178
file : & ' static str ,
179
+ skipped : bool ,
153
180
#[ allow( dead_code) ]
154
181
line : u32 ,
155
182
function : fn ( ) ,
156
183
}
184
+
185
+ #[ must_use]
186
+ enum TestOutcome {
187
+ Passed ,
188
+ Failed ,
189
+ Skipped ,
190
+ }
0 commit comments