Skip to content

Commit 0405dd9

Browse files
committed
Replace Godot's print_rich() with Rust's own println! (slow through FFI)
1 parent 86ac069 commit 0405dd9

File tree

2 files changed

+74
-39
lines changed

2 files changed

+74
-39
lines changed

godot-macros/src/itest.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ pub fn transform(input: TokenStream) -> Result<TokenStream, Error> {
5454

5555
::godot::sys::plugin_add!(__GODOT_ITEST in crate; crate::TestCase {
5656
name: #test_name_str,
57+
skipped: false,
5758
file: std::file!(),
5859
line: std::line!(),
5960
function: #test_name,

itest/rust/src/lib.rs

Lines changed: 73 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
*/
66

77
use godot::bind::{godot_api, GodotClass};
8-
use godot::builtin::ToVariant;
9-
use godot::engine::utilities::print_rich;
108
use godot::init::{gdextension, ExtensionLibrary};
119
use godot::sys;
1210
use godot::test::itest;
@@ -32,40 +30,8 @@ mod utilities_test;
3230
mod variant_test;
3331
mod virtual_methods_test;
3432

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-
6733
// ----------------------------------------------------------------------------------------------------------------------------------------------
68-
// Implementation
34+
// Entry point + main runner class
6935

7036
#[gdextension(entry_point=itest_init)]
7137
unsafe impl ExtensionLibrary for IntegrationTests {}
@@ -119,18 +85,78 @@ impl IntegrationTests {
11985

12086
let mut last_file = None;
12187
for test in tests {
122-
let passed = run_test(&test);
88+
let outcome = run_test(&test);
12389

12490
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,
12795
}
12896

129-
print_test(&test, passed, &mut last_file);
97+
print_test(&test, outcome, &mut last_file);
13098
}
13199
}
132100
}
133101

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+
134160
pub(crate) fn expect_panic(context: &str, code: impl FnOnce() + panic::UnwindSafe) {
135161
// Exchange panic hook, to disable printing during expected panics
136162
let prev_hook = panic::take_hook();
@@ -150,7 +176,15 @@ pub(crate) fn expect_panic(context: &str, code: impl FnOnce() + panic::UnwindSaf
150176
struct TestCase {
151177
name: &'static str,
152178
file: &'static str,
179+
skipped: bool,
153180
#[allow(dead_code)]
154181
line: u32,
155182
function: fn(),
156183
}
184+
185+
#[must_use]
186+
enum TestOutcome {
187+
Passed,
188+
Failed,
189+
Skipped,
190+
}

0 commit comments

Comments
 (0)