fluent_test/
reporter.rs

1use crate::backend::{Assertion, TestSessionResult};
2use crate::config::Config;
3use crate::events::{AssertionEvent, EventEmitter, on_failure, on_success};
4use crate::frontend::ConsoleRenderer;
5use once_cell::sync::Lazy;
6use std::cell::RefCell;
7use std::collections::HashSet;
8use std::sync::RwLock;
9
10pub(crate) static GLOBAL_CONFIG: Lazy<RwLock<Config>> = Lazy::new(|| RwLock::new(Config::new()));
11
12thread_local! {
13    static TEST_SESSION: RefCell<TestSessionResult> = RefCell::new(TestSessionResult::default());
14    // Track already reported messages to avoid duplicates
15    static REPORTED_MESSAGES: RefCell<HashSet<String>> = RefCell::new(HashSet::new());
16    // Flag to enable/disable deduplication
17    static DEDUPLICATE_ENABLED: RefCell<bool> = const { RefCell::new(true) };
18    // Flag to enable silent mode for intermediate steps in a chain
19    static SILENT_MODE: RefCell<bool> = const { RefCell::new(false) };
20}
21
22pub struct Reporter;
23
24impl Reporter {
25    /// Initialize the reporter with event handlers
26    pub fn init() {
27        // Register success event handler
28        on_success(|result| {
29            Self::handle_success_event(result);
30        });
31
32        // Register failure event handler
33        on_failure(|result| {
34            Self::handle_failure_event(result);
35        });
36    }
37
38    /// Handle success events
39    fn handle_success_event(result: Assertion<()>) {
40        TEST_SESSION.with(|session| {
41            let mut session = session.borrow_mut();
42            session.passed_count += 1;
43        });
44
45        // Check if silent mode is enabled
46        let silent = SILENT_MODE.with(|silent| *silent.borrow());
47        if silent {
48            return;
49        }
50
51        // Check if we should deduplicate
52        let should_report = DEDUPLICATE_ENABLED.with(|enabled| {
53            if !*enabled.borrow() {
54                // Deduplication disabled, always report
55                return true;
56            }
57
58            // Only report each unique success message once
59            REPORTED_MESSAGES.with(|msgs| {
60                let key = format!("{:?}", result);
61                let mut messages = msgs.borrow_mut();
62                if !messages.contains(&key) {
63                    messages.insert(key);
64                    true
65                } else {
66                    false
67                }
68            })
69        });
70
71        if should_report {
72            let config = GLOBAL_CONFIG.read().unwrap();
73            let renderer = ConsoleRenderer::new(Config {
74                use_colors: config.use_colors,
75                use_unicode_symbols: config.use_unicode_symbols,
76                show_success_details: config.show_success_details,
77                enhanced_output: config.enhanced_output,
78            });
79            renderer.print_success(&result);
80        }
81    }
82
83    /// Handle failure events
84    fn handle_failure_event(result: Assertion<()>) {
85        TEST_SESSION.with(|session| {
86            let mut session = session.borrow_mut();
87            session.failed_count += 1;
88            session.failures.push(result.clone());
89        });
90
91        // Check if silent mode is enabled
92        let silent = SILENT_MODE.with(|silent| *silent.borrow());
93        if silent {
94            return;
95        }
96
97        // Check if we should deduplicate
98        let should_report = DEDUPLICATE_ENABLED.with(|enabled| {
99            if !*enabled.borrow() {
100                // Deduplication disabled, always report
101                return true;
102            }
103
104            // Only report each unique failure message once
105            let key = format!("{:?}", result);
106            REPORTED_MESSAGES.with(|msgs| {
107                let mut messages = msgs.borrow_mut();
108                if !messages.contains(&key) {
109                    messages.insert(key);
110                    true
111                } else {
112                    false
113                }
114            })
115        });
116
117        if should_report {
118            let config = GLOBAL_CONFIG.read().unwrap();
119            let renderer = ConsoleRenderer::new(Config {
120                use_colors: config.use_colors,
121                use_unicode_symbols: config.use_unicode_symbols,
122                show_success_details: config.show_success_details,
123                enhanced_output: config.enhanced_output,
124            });
125            renderer.print_failure(&result);
126        }
127    }
128
129    /// Clear the message cache to allow duplicated messages in different test scopes
130    pub fn reset_message_cache() {
131        REPORTED_MESSAGES.with(|msgs| {
132            msgs.borrow_mut().clear();
133        });
134    }
135
136    /// Enable deduplication of messages
137    pub fn enable_deduplication() {
138        DEDUPLICATE_ENABLED.with(|enabled| {
139            *enabled.borrow_mut() = true;
140        });
141    }
142
143    /// Disable deduplication of messages (for tests)
144    pub fn disable_deduplication() {
145        DEDUPLICATE_ENABLED.with(|enabled| {
146            *enabled.borrow_mut() = false;
147        });
148    }
149
150    /// Enable silent mode to suppress intermediate output in chains
151    pub fn enable_silent_mode() {
152        SILENT_MODE.with(|silent| {
153            *silent.borrow_mut() = true;
154        });
155    }
156
157    /// Disable silent mode to show all output
158    pub fn disable_silent_mode() {
159        SILENT_MODE.with(|silent| {
160            *silent.borrow_mut() = false;
161        });
162    }
163
164    pub fn summarize() {
165        TEST_SESSION.with(|session| {
166            let session = session.borrow();
167            let config = GLOBAL_CONFIG.read().unwrap();
168            let renderer = ConsoleRenderer::new(Config {
169                use_colors: config.use_colors,
170                use_unicode_symbols: config.use_unicode_symbols,
171                show_success_details: config.show_success_details,
172                enhanced_output: config.enhanced_output,
173            });
174            renderer.print_session_summary(&session);
175        });
176
177        // Emit session completed event
178        EventEmitter::emit(AssertionEvent::SessionCompleted);
179
180        // Clear reported messages
181        Self::reset_message_cache();
182
183        // Reset deduplication to default (enabled)
184        Self::enable_deduplication();
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191    use crate::backend::assertions::AssertionStep;
192    use crate::backend::assertions::sentence::AssertionSentence;
193
194    // Helper function to create a test assertion that won't evaluate on drop
195    fn create_test_assertion(passed: bool) -> Assertion<()> {
196        // Create a base assertion
197        let mut assertion = Assertion::new((), "test_value");
198
199        // Add a step with the appropriate pass/fail status
200        assertion.steps.push(AssertionStep {
201            sentence: AssertionSentence::new("be", if passed { "correct" } else { "incorrect" }),
202            passed,
203            logical_op: None,
204        });
205
206        // Set it as non-final to prevent Drop evaluation
207        assertion.is_final = false;
208
209        assertion
210    }
211
212    // Helper function to just record a failure in the session without actually
213    // invoking the full reporter (which would panic on failure)
214    fn record_failure(assertion: Assertion<()>) {
215        TEST_SESSION.with(|session| {
216            let mut session = session.borrow_mut();
217            session.failed_count += 1;
218            session.failures.push(assertion);
219        });
220    }
221
222    #[test]
223    fn test_reporter_deduplication_flags() {
224        // Test enabling and disabling deduplication
225        Reporter::enable_deduplication();
226        DEDUPLICATE_ENABLED.with(|enabled| {
227            assert_eq!(*enabled.borrow(), true);
228        });
229
230        Reporter::disable_deduplication();
231        DEDUPLICATE_ENABLED.with(|enabled| {
232            assert_eq!(*enabled.borrow(), false);
233        });
234
235        // Reset to default state
236        Reporter::enable_deduplication();
237    }
238
239    #[test]
240    fn test_reporter_silent_mode() {
241        // Test enabling and disabling silent mode
242        Reporter::enable_silent_mode();
243        SILENT_MODE.with(|silent| {
244            assert_eq!(*silent.borrow(), true);
245        });
246
247        Reporter::disable_silent_mode();
248        SILENT_MODE.with(|silent| {
249            assert_eq!(*silent.borrow(), false);
250        });
251    }
252
253    #[test]
254    fn test_reporter_message_cache() {
255        // Add a message to the cache
256        REPORTED_MESSAGES.with(|msgs| {
257            msgs.borrow_mut().insert("test_message".to_string());
258        });
259
260        // Verify it's in the cache
261        REPORTED_MESSAGES.with(|msgs| {
262            assert!(msgs.borrow().contains("test_message"));
263        });
264
265        // Reset the cache
266        Reporter::reset_message_cache();
267
268        // Verify it's been cleared
269        REPORTED_MESSAGES.with(|msgs| {
270            assert!(!msgs.borrow().contains("test_message"));
271        });
272    }
273
274    #[test]
275    fn test_handle_success_event() {
276        // Start with a clean session
277        TEST_SESSION.with(|session| {
278            *session.borrow_mut() = TestSessionResult::default();
279        });
280
281        // Disable deduplication for this test
282        Reporter::disable_deduplication();
283
284        // Create and handle a success event
285        let assertion = create_test_assertion(true);
286        Reporter::handle_success_event(assertion);
287
288        // Verify the pass count was incremented
289        TEST_SESSION.with(|session| {
290            let session = session.borrow();
291            assert_eq!(session.passed_count, 1);
292            assert_eq!(session.failed_count, 0);
293            assert_eq!(session.failures.len(), 0);
294        });
295
296        // Reset to default state
297        Reporter::enable_deduplication();
298        Reporter::reset_message_cache();
299    }
300
301    #[test]
302    fn test_session_tracking() {
303        // Start with a clean session
304        TEST_SESSION.with(|session| {
305            *session.borrow_mut() = TestSessionResult::default();
306        });
307
308        // Create a test assertion for failure
309        let assertion = create_test_assertion(false);
310
311        // Use our helper to directly record a failure without going through the reporter
312        record_failure(assertion.clone());
313
314        // Verify the failure count was incremented and the failure was recorded
315        TEST_SESSION.with(|session| {
316            let session = session.borrow();
317            assert_eq!(session.passed_count, 0);
318            assert_eq!(session.failed_count, 1);
319            assert_eq!(session.failures.len(), 1);
320
321            // Check that the failure matches what we sent
322            if !session.failures.is_empty() {
323                let first_failure = &session.failures[0];
324                assert_eq!(first_failure.expr_str, assertion.expr_str);
325                assert_eq!(first_failure.steps.len(), assertion.steps.len());
326                assert_eq!(first_failure.steps[0].passed, assertion.steps[0].passed);
327            }
328        });
329
330        // Clean up
331        TEST_SESSION.with(|session| {
332            *session.borrow_mut() = TestSessionResult::default();
333        });
334    }
335
336    #[test]
337    fn test_silent_mode() {
338        // Enable silent mode
339        Reporter::enable_silent_mode();
340
341        // Verify silent mode is enabled
342        SILENT_MODE.with(|silent| {
343            assert_eq!(*silent.borrow(), true);
344        });
345
346        // Test that success events still increment the counter in silent mode
347        // Start with a clean session
348        TEST_SESSION.with(|session| {
349            *session.borrow_mut() = TestSessionResult::default();
350        });
351
352        // Handle a success event in silent mode
353        Reporter::handle_success_event(create_test_assertion(true));
354
355        // Verify the pass count was incremented
356        TEST_SESSION.with(|session| {
357            let session = session.borrow();
358            assert_eq!(session.passed_count, 1);
359        });
360
361        // Disable silent mode
362        Reporter::disable_silent_mode();
363
364        // Verify silent mode is disabled
365        SILENT_MODE.with(|silent| {
366            assert_eq!(*silent.borrow(), false);
367        });
368
369        // Clean up
370        TEST_SESSION.with(|session| {
371            *session.borrow_mut() = TestSessionResult::default();
372        });
373    }
374
375    #[test]
376    fn test_deduplication() {
377        // Enable deduplication
378        Reporter::enable_deduplication();
379        Reporter::reset_message_cache();
380
381        // Start with a clean session
382        TEST_SESSION.with(|session| {
383            *session.borrow_mut() = TestSessionResult::default();
384        });
385
386        // Create an assertion and send it twice
387        let assertion = create_test_assertion(true);
388
389        // Handle the same success event twice
390        Reporter::handle_success_event(assertion.clone());
391        Reporter::handle_success_event(assertion);
392
393        // We should only count it once due to deduplication
394        REPORTED_MESSAGES.with(|msgs| {
395            assert_eq!(msgs.borrow().len(), 1);
396        });
397
398        // Verify it was still counted twice in the session
399        TEST_SESSION.with(|session| {
400            let session = session.borrow();
401            assert_eq!(session.passed_count, 2);
402        });
403
404        // Clean up
405        Reporter::reset_message_cache();
406    }
407}