5
5
*/
6
6
7
7
use godot:: bind:: { godot_api, GodotClass } ;
8
+ use godot:: builtin:: ToVariant ;
9
+ use godot:: engine:: utilities:: print_rich;
8
10
use godot:: init:: { gdextension, ExtensionLibrary } ;
9
11
use godot:: sys;
10
12
use godot:: test:: itest;
11
- use std:: panic:: UnwindSafe ;
13
+ use std:: collections:: HashSet ;
14
+ use std:: panic;
12
15
13
16
mod array_test;
14
17
mod base_test;
@@ -29,17 +32,8 @@ mod utilities_test;
29
32
mod variant_test;
30
33
mod virtual_methods_test;
31
34
32
-
33
- #[ derive( Copy , Clone ) ]
34
- pub struct TestCase {
35
- name : & ' static str ,
36
- function : fn ( ) ,
37
- }
38
-
39
35
#[ must_use]
40
36
fn run_test ( test : & TestCase ) -> bool {
41
- println ! ( " -- {}" , test. name) ;
42
-
43
37
// Explicit type to prevent tests from returning a value
44
38
let success: Option < ( ) > =
45
39
godot:: private:: handle_panic ( || format ! ( " !! Test {} failed" , test. name) , test. function ) ;
@@ -49,31 +43,25 @@ fn run_test(test: &TestCase) -> bool {
49
43
50
44
sys:: plugin_registry!( __GODOT_ITEST: TestCase ) ;
51
45
52
- // fn register_classes() {
53
- // object_test::register();
54
- // gdscript_ffi_test::register();
55
- // virtual_methods_test::register();
56
- // }
57
-
58
- fn run_tests ( ) -> bool {
59
- let mut tests: Vec < TestCase > = vec ! [ ] ;
60
-
61
- sys:: plugin_foreach!( __GODOT_ITEST; |test: & TestCase | {
62
- tests. push( * test) ;
63
- } ) ;
64
-
65
- println ! ( "Collected {} tests." , tests. len( ) ) ;
66
-
67
-
68
- let mut stats = TestStats :: default ( ) ;
69
- for test in tests {
70
- stats. tests_run += 1 ;
71
- if run_test ( & test) {
72
- stats. tests_passed += 1 ;
73
- }
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 ..] ) ;
74
52
}
75
53
76
- stats. tests_run == stats. tests_passed
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 ) ;
77
65
}
78
66
79
67
// ----------------------------------------------------------------------------------------------------------------------------------------------
@@ -84,28 +72,85 @@ unsafe impl ExtensionLibrary for IntegrationTests {}
84
72
85
73
#[ derive( GodotClass , Debug ) ]
86
74
#[ class( base=Node , init) ]
87
- struct IntegrationTests { }
75
+ struct IntegrationTests {
76
+ tests_run : i64 ,
77
+ tests_passed : i64 ,
78
+ tests_skipped : i64 ,
79
+ }
88
80
89
81
#[ godot_api]
90
82
impl IntegrationTests {
83
+ // TODO could return a Stats object with properties in the future
91
84
#[ func]
92
- fn test_all ( & mut self ) -> bool {
85
+ fn test_all ( & mut self ) {
93
86
println ! ( "Run Godot integration tests..." ) ;
94
- run_tests ( )
87
+ self . run_tests ( ) ;
88
+ }
89
+
90
+ #[ func]
91
+ fn num_run ( & self ) -> i64 {
92
+ self . tests_run
93
+ }
94
+
95
+ #[ func]
96
+ fn num_passed ( & self ) -> i64 {
97
+ self . tests_passed
98
+ }
99
+
100
+ #[ func]
101
+ fn num_skipped ( & self ) -> i64 {
102
+ self . tests_skipped
103
+ }
104
+
105
+ fn run_tests ( & mut self ) {
106
+ let mut tests: Vec < TestCase > = vec ! [ ] ;
107
+
108
+ let mut all_files = HashSet :: new ( ) ;
109
+ sys:: plugin_foreach!( __GODOT_ITEST; |test: & TestCase | {
110
+ all_files. insert( test. file) ;
111
+ tests. push( * test) ;
112
+ } ) ;
113
+
114
+ println ! (
115
+ "Rust: found {} tests in {} files." ,
116
+ tests. len( ) ,
117
+ all_files. len( )
118
+ ) ;
119
+
120
+ let mut last_file = None ;
121
+ for test in tests {
122
+ let passed = run_test ( & test) ;
123
+
124
+ self . tests_run += 1 ;
125
+ if passed {
126
+ self . tests_passed += 1 ;
127
+ }
128
+
129
+ print_test ( & test, passed, & mut last_file) ;
130
+ }
95
131
}
96
132
}
97
133
98
- pub ( crate ) fn expect_panic ( context : & str , code : impl FnOnce ( ) + UnwindSafe ) {
99
- let panic = std:: panic:: catch_unwind ( code) ;
134
+ pub ( crate ) fn expect_panic ( context : & str , code : impl FnOnce ( ) + panic:: UnwindSafe ) {
135
+ // Exchange panic hook, to disable printing during expected panics
136
+ let prev_hook = panic:: take_hook ( ) ;
137
+ panic:: set_hook ( Box :: new ( |_panic_info| { } ) ) ;
138
+
139
+ // Run code that should panic, restore hook
140
+ let panic = panic:: catch_unwind ( code) ;
141
+ panic:: set_hook ( prev_hook) ;
142
+
100
143
assert ! (
101
144
panic. is_err( ) ,
102
145
"code should have panicked but did not: {context}" ,
103
146
) ;
104
147
}
105
148
106
- #[ derive( Default ) ]
107
- struct TestStats {
108
- tests_run : usize ,
109
- tests_passed : usize ,
110
- tests_skipped : usize ,
149
+ #[ derive( Copy , Clone ) ]
150
+ struct TestCase {
151
+ name : & ' static str ,
152
+ file : & ' static str ,
153
+ #[ allow( dead_code) ]
154
+ line : u32 ,
155
+ function : fn ( ) ,
111
156
}
0 commit comments