4
4
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
5
*/
6
6
7
- use godot:: bind:: { godot_api, GodotClass } ;
8
7
use godot:: init:: { gdextension, ExtensionLibrary } ;
9
8
use godot:: sys;
10
- use godot:: test:: itest;
11
- use std:: collections:: HashSet ;
12
- use std:: panic;
13
9
14
10
mod array_test;
15
11
mod base_test;
@@ -30,134 +26,16 @@ mod utilities_test;
30
26
mod variant_test;
31
27
mod virtual_methods_test;
32
28
33
- // ----------------------------------------------------------------------------------------------------------------------------------------------
34
- // Entry point + main runner class
35
-
36
- #[ gdextension( entry_point=itest_init) ]
37
- unsafe impl ExtensionLibrary for IntegrationTests { }
38
-
39
- #[ derive( GodotClass , Debug ) ]
40
- #[ class( base=Node , init) ]
41
- struct IntegrationTests {
42
- tests_run : i64 ,
43
- tests_passed : i64 ,
44
- tests_skipped : i64 ,
45
- }
46
-
47
- #[ godot_api]
48
- impl IntegrationTests {
49
- // TODO could return a Stats object with properties in the future
50
- #[ func]
51
- fn test_all ( & mut self ) {
52
- println ! ( "Run Godot integration tests..." ) ;
53
- self . run_tests ( ) ;
54
- }
55
-
56
- #[ func]
57
- fn num_run ( & self ) -> i64 {
58
- self . tests_run
59
- }
60
-
61
- #[ func]
62
- fn num_passed ( & self ) -> i64 {
63
- self . tests_passed
64
- }
65
-
66
- #[ func]
67
- fn num_skipped ( & self ) -> i64 {
68
- self . tests_skipped
69
- }
70
-
71
- fn run_tests ( & mut self ) {
72
- let mut tests: Vec < TestCase > = vec ! [ ] ;
73
-
74
- let mut all_files = HashSet :: new ( ) ;
75
- sys:: plugin_foreach!( __GODOT_ITEST; |test: & TestCase | {
76
- all_files. insert( test. file) ;
77
- tests. push( * test) ;
78
- } ) ;
79
-
80
- println ! (
81
- "Rust: found {} tests in {} files." ,
82
- tests. len( ) ,
83
- all_files. len( )
84
- ) ;
85
-
86
- let mut last_file = None ;
87
- for test in tests {
88
- let outcome = run_test ( & test) ;
89
-
90
- self . tests_run += 1 ;
91
- match outcome {
92
- TestOutcome :: Passed => self . tests_passed += 1 ,
93
- TestOutcome :: Failed => { }
94
- TestOutcome :: Skipped => self . tests_skipped += 1 ,
95
- }
96
-
97
- print_test ( & test, outcome, & mut last_file) ;
98
- }
99
- }
100
- }
29
+ mod runner;
101
30
102
31
// ----------------------------------------------------------------------------------------------------------------------------------------------
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
- }
32
+ // API for test cases
133
33
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
- }
34
+ use godot:: test:: itest;
35
+
36
+ pub ( crate ) fn expect_panic ( context : & str , code : impl FnOnce ( ) + std:: panic:: UnwindSafe ) {
37
+ use std:: panic;
159
38
160
- pub ( crate ) fn expect_panic ( context : & str , code : impl FnOnce ( ) + panic:: UnwindSafe ) {
161
39
// Exchange panic hook, to disable printing during expected panics
162
40
let prev_hook = panic:: take_hook ( ) ;
163
41
panic:: set_hook ( Box :: new ( |_panic_info| { } ) ) ;
@@ -172,19 +50,37 @@ pub(crate) fn expect_panic(context: &str, code: impl FnOnce() + panic::UnwindSaf
172
50
) ;
173
51
}
174
52
53
+ // ----------------------------------------------------------------------------------------------------------------------------------------------
54
+ // Entry point + #[itest] test registration
55
+
56
+ #[ gdextension( entry_point=itest_init) ]
57
+ unsafe impl ExtensionLibrary for runner:: IntegrationTests { }
58
+
59
+ // Registers all the `#[itest]` tests.
60
+ sys:: plugin_registry!( __GODOT_ITEST: RustTestCase ) ;
61
+
62
+ /// Finds all `#[itest]` tests.
63
+ fn collect_rust_tests ( ) -> ( Vec < RustTestCase > , usize ) {
64
+ let mut all_files = std:: collections:: HashSet :: new ( ) ;
65
+ let mut tests: Vec < RustTestCase > = vec ! [ ] ;
66
+
67
+ sys:: plugin_foreach!( __GODOT_ITEST; |test: & RustTestCase | {
68
+ all_files. insert( test. file) ;
69
+ tests. push( * test) ;
70
+ } ) ;
71
+
72
+ // Sort alphabetically for deterministic run order
73
+ tests. sort_by_key ( |test| test. file ) ;
74
+
75
+ ( tests, all_files. len ( ) )
76
+ }
77
+
175
78
#[ derive( Copy , Clone ) ]
176
- struct TestCase {
79
+ struct RustTestCase {
177
80
name : & ' static str ,
178
81
file : & ' static str ,
179
82
skipped : bool ,
180
83
#[ allow( dead_code) ]
181
84
line : u32 ,
182
85
function : fn ( ) ,
183
86
}
184
-
185
- #[ must_use]
186
- enum TestOutcome {
187
- Passed ,
188
- Failed ,
189
- Skipped ,
190
- }
0 commit comments