|
23 | 23 | //! The newtype pattern is a zero-cost abstraction - it has the same memory layout and performance |
24 | 24 | //! characteristics as the wrapped type, but provides type safety benefits. |
25 | 25 | //! |
| 26 | +//! ## Buffering Behavior |
| 27 | +//! |
| 28 | +//! Output is line-buffered by default. Messages are typically flushed automatically |
| 29 | +//! after each newline. For cases where immediate output is critical (e.g., before |
| 30 | +//! long-running operations), call `flush()` explicitly: |
| 31 | +//! |
| 32 | +//! ```rust,ignore |
| 33 | +//! output.progress("Starting long operation..."); |
| 34 | +//! output.flush()?; // Ensure message appears before operation starts |
| 35 | +//! perform_long_operation(); |
| 36 | +//! ``` |
| 37 | +//! |
26 | 38 | //! ## Example Usage |
27 | 39 | //! |
28 | 40 | //! ```rust |
@@ -1083,6 +1095,32 @@ impl UserOutput { |
1083 | 1095 | } |
1084 | 1096 | } |
1085 | 1097 |
|
| 1098 | + /// Flush all pending output to stdout and stderr |
| 1099 | + /// |
| 1100 | + /// This is typically not needed as writes are line-buffered by default, |
| 1101 | + /// but can be useful for ensuring output appears immediately before |
| 1102 | + /// long-running operations or in test scenarios. |
| 1103 | + /// |
| 1104 | + /// # Errors |
| 1105 | + /// |
| 1106 | + /// Returns an error if flushing either stream fails. |
| 1107 | + /// |
| 1108 | + /// # Examples |
| 1109 | + /// |
| 1110 | + /// ```rust |
| 1111 | + /// use torrust_tracker_deployer_lib::presentation::user_output::{UserOutput, VerbosityLevel}; |
| 1112 | + /// |
| 1113 | + /// let mut output = UserOutput::new(VerbosityLevel::Normal); |
| 1114 | + /// output.progress("Starting long operation..."); |
| 1115 | + /// output.flush().expect("Failed to flush output"); |
| 1116 | + /// // Now perform long operation... |
| 1117 | + /// ``` |
| 1118 | + pub fn flush(&mut self) -> std::io::Result<()> { |
| 1119 | + self.stdout.0.flush()?; |
| 1120 | + self.stderr.0.flush()?; |
| 1121 | + Ok(()) |
| 1122 | + } |
| 1123 | + |
1086 | 1124 | /// Display progress message to stderr (Normal level and above) |
1087 | 1125 | /// |
1088 | 1126 | /// Progress messages go to stderr following cargo/docker patterns. |
@@ -2744,4 +2782,106 @@ mod tests { |
2744 | 2782 | assert!(json.is_ok(), "Invalid JSON in stdout"); |
2745 | 2783 | } |
2746 | 2784 | } |
| 2785 | + |
| 2786 | + // ============================================================================ |
| 2787 | + // Buffering Tests |
| 2788 | + // ============================================================================ |
| 2789 | + |
| 2790 | + mod buffering { |
| 2791 | + use super::super::*; |
| 2792 | + use crate::presentation::user_output::test_support::TestUserOutput; |
| 2793 | + |
| 2794 | + #[test] |
| 2795 | + fn it_should_flush_all_writers() { |
| 2796 | + let mut test_output = TestUserOutput::new(VerbosityLevel::Normal); |
| 2797 | + test_output.output.progress("Test message"); |
| 2798 | + |
| 2799 | + // Flush should succeed |
| 2800 | + test_output.output.flush().expect("Flush should succeed"); |
| 2801 | + |
| 2802 | + // Verify output is present (flushed) |
| 2803 | + assert!(!test_output.stderr().is_empty()); |
| 2804 | + assert!(test_output.stderr().contains("Test message")); |
| 2805 | + } |
| 2806 | + |
| 2807 | + #[test] |
| 2808 | + fn it_should_be_safe_to_flush_multiple_times() { |
| 2809 | + let mut test_output = TestUserOutput::new(VerbosityLevel::Normal); |
| 2810 | + test_output.output.progress("Test message"); |
| 2811 | + |
| 2812 | + // Multiple flushes should be safe |
| 2813 | + test_output |
| 2814 | + .output |
| 2815 | + .flush() |
| 2816 | + .expect("First flush should succeed"); |
| 2817 | + test_output |
| 2818 | + .output |
| 2819 | + .flush() |
| 2820 | + .expect("Second flush should succeed"); |
| 2821 | + test_output |
| 2822 | + .output |
| 2823 | + .flush() |
| 2824 | + .expect("Third flush should succeed"); |
| 2825 | + |
| 2826 | + // Output should still be present |
| 2827 | + assert!(!test_output.stderr().is_empty()); |
| 2828 | + } |
| 2829 | + |
| 2830 | + #[test] |
| 2831 | + fn it_should_flush_empty_buffers_safely() { |
| 2832 | + let mut test_output = TestUserOutput::new(VerbosityLevel::Normal); |
| 2833 | + |
| 2834 | + // Flushing with no output should be safe |
| 2835 | + test_output |
| 2836 | + .output |
| 2837 | + .flush() |
| 2838 | + .expect("Flushing empty buffers should succeed"); |
| 2839 | + |
| 2840 | + // No output should be present |
| 2841 | + assert_eq!(test_output.stderr(), ""); |
| 2842 | + assert_eq!(test_output.stdout(), ""); |
| 2843 | + } |
| 2844 | + |
| 2845 | + #[test] |
| 2846 | + fn it_should_flush_both_stdout_and_stderr() { |
| 2847 | + let mut test_output = TestUserOutput::new(VerbosityLevel::Normal); |
| 2848 | + |
| 2849 | + // Write to both channels |
| 2850 | + test_output.output.progress("Progress message"); |
| 2851 | + test_output.output.result("Result data"); |
| 2852 | + |
| 2853 | + // Flush should handle both channels |
| 2854 | + test_output |
| 2855 | + .output |
| 2856 | + .flush() |
| 2857 | + .expect("Flush should succeed for both channels"); |
| 2858 | + |
| 2859 | + // Verify both outputs are present |
| 2860 | + assert!(test_output.stderr().contains("Progress message")); |
| 2861 | + assert!(test_output.stdout().contains("Result data")); |
| 2862 | + } |
| 2863 | + |
| 2864 | + #[test] |
| 2865 | + fn it_should_work_with_sequential_flush_calls() { |
| 2866 | + let mut test_output = TestUserOutput::new(VerbosityLevel::Normal); |
| 2867 | + |
| 2868 | + // Write, flush, write, flush pattern |
| 2869 | + test_output.output.progress("Message 1"); |
| 2870 | + test_output |
| 2871 | + .output |
| 2872 | + .flush() |
| 2873 | + .expect("First flush should succeed"); |
| 2874 | + |
| 2875 | + test_output.output.progress("Message 2"); |
| 2876 | + test_output |
| 2877 | + .output |
| 2878 | + .flush() |
| 2879 | + .expect("Second flush should succeed"); |
| 2880 | + |
| 2881 | + // Both messages should be present |
| 2882 | + let stderr = test_output.stderr(); |
| 2883 | + assert!(stderr.contains("Message 1")); |
| 2884 | + assert!(stderr.contains("Message 2")); |
| 2885 | + } |
| 2886 | + } |
2747 | 2887 | } |
0 commit comments