@@ -82,198 +82,4 @@ fn migrate_legacy_ocsf_logs(directory: &Path) -> io::Result<()> {
8282}
8383
8484#[ cfg( test) ]
85- mod tests {
86- use super :: * ;
87- use std:: collections:: BTreeMap ;
88- use std:: ffi:: OsString ;
89- use std:: io:: Write as _;
90- use std:: time:: { Duration , Instant } ;
91-
92- fn history ( directory : & Path , format : Format ) -> BTreeMap < OsString , Vec < u8 > > {
93- let ( prefix, suffix) = format. filename_parts ( ) ;
94- fs:: read_dir ( directory)
95- . unwrap ( )
96- . map ( Result :: unwrap)
97- . filter ( |entry| {
98- let name = entry. file_name ( ) ;
99- let name = name. to_string_lossy ( ) ;
100- name. starts_with ( & format ! ( "{prefix}." ) ) && name. ends_with ( & format ! ( ".{suffix}" ) )
101- } )
102- . map ( |entry| ( entry. file_name ( ) , fs:: read ( entry. path ( ) ) . unwrap ( ) ) )
103- . collect ( )
104- }
105-
106- fn seed_history ( directory : & Path , format : Format , minutely : bool ) {
107- let ( prefix, suffix) = format. filename_parts ( ) ;
108- for day in 1 ..=3 {
109- let date = format ! ( "2000-01-{day:02}" ) ;
110- let date = if minutely {
111- format ! ( "{date}-00-00" )
112- } else {
113- date
114- } ;
115- let filename = format ! ( "{prefix}.{date}.{suffix}" ) ;
116- fs:: write ( directory. join ( & filename) , filename. as_bytes ( ) ) . unwrap ( ) ;
117- }
118- }
119-
120- #[ test]
121- fn suffixes_separate_retention_before_timestamp_selection ( ) {
122- // These checks precede both metadata.created() and date parsing in
123- // tracing-appender, so separation does not depend on the filesystem.
124- let ( text_prefix, text_suffix) = Format :: Shorthand . filename_parts ( ) ;
125- let ( json_prefix, json_suffix) = Format :: Ocsf . filename_parts ( ) ;
126- let text = format ! ( "{text_prefix}.2000-01-01.{text_suffix}" ) ;
127- let json = format ! ( "{json_prefix}.2000-01-01.{json_suffix}" ) ;
128- assert ! ( !json. ends_with( text_suffix) ) ;
129- assert ! ( !text. ends_with( json_suffix) ) ;
130- }
131-
132- #[ test]
133- fn daily_initialization_retains_each_format_independently ( ) {
134- for formats in [
135- [ Format :: Shorthand , Format :: Ocsf ] ,
136- [ Format :: Ocsf , Format :: Shorthand ] ,
137- ] {
138- let directory = tempfile:: tempdir ( ) . unwrap ( ) ;
139- for format in formats {
140- seed_history ( directory. path ( ) , format, false ) ;
141- }
142- eprintln ! (
143- "log filesystem exposes creation timestamps: {}" ,
144- fs:: metadata( directory. path( ) . join( "openshell.2000-01-01.log" ) )
145- . unwrap( )
146- . created( )
147- . is_ok( )
148- ) ;
149- for ( format, other) in [ ( formats[ 0 ] , formats[ 1 ] ) , ( formats[ 1 ] , formats[ 0 ] ) ] {
150- let other_history = history ( directory. path ( ) , other) ;
151- let mut writer = appender ( directory. path ( ) , format, Rotation :: DAILY ) . unwrap ( ) ;
152- writer. write_all ( b"new event\n " ) . unwrap ( ) ;
153- writer. flush ( ) . unwrap ( ) ;
154- assert_eq ! ( history( directory. path( ) , other) , other_history) ;
155- assert_eq ! ( history( directory. path( ) , format) . len( ) , 3 ) ;
156- }
157- }
158- }
159-
160- #[ test]
161- fn legacy_jsonl_history_is_migrated_before_shorthand_pruning ( ) {
162- let directory = tempfile:: tempdir ( ) . unwrap ( ) ;
163- seed_history ( directory. path ( ) , Format :: Shorthand , false ) ;
164- for day in 1 ..=3 {
165- fs:: write (
166- directory
167- . path ( )
168- . join ( format ! ( "openshell-ocsf.2000-01-{day:02}.log" ) ) ,
169- format ! ( "legacy event {day}\n " ) ,
170- )
171- . unwrap ( ) ;
172- }
173- let _writer = appender ( directory. path ( ) , Format :: Shorthand , Rotation :: DAILY ) . unwrap ( ) ;
174- assert_eq ! ( history( directory. path( ) , Format :: Shorthand ) . len( ) , 3 ) ;
175- assert_eq ! ( history( directory. path( ) , Format :: Ocsf ) . len( ) , 3 ) ;
176- for day in 1 ..=3 {
177- let legacy = directory
178- . path ( )
179- . join ( format ! ( "openshell-ocsf.2000-01-{day:02}.log" ) ) ;
180- assert ! ( !legacy. exists( ) ) ;
181- assert_eq ! (
182- fs:: read_to_string( legacy. with_extension( "jsonl" ) ) . unwrap( ) ,
183- format!( "legacy event {day}\n " )
184- ) ;
185- }
186- // Successful migration is idempotent on subsequent starts.
187- migrate_legacy_ocsf_logs ( directory. path ( ) ) . unwrap ( ) ;
188- }
189-
190- #[ test]
191- fn migration_collision_preserves_both_files_and_prevents_pruning ( ) {
192- let directory = tempfile:: tempdir ( ) . unwrap ( ) ;
193- seed_history ( directory. path ( ) , Format :: Shorthand , false ) ;
194- let before = history ( directory. path ( ) , Format :: Shorthand ) ;
195- let legacy = directory. path ( ) . join ( "openshell-ocsf.2000-01-01.log" ) ;
196- let current = legacy. with_extension ( "jsonl" ) ;
197- fs:: write ( & legacy, b"legacy security records" ) . unwrap ( ) ;
198- fs:: write ( & current, b"new security records" ) . unwrap ( ) ;
199- for format in [ Format :: Shorthand , Format :: Ocsf ] {
200- let error = appender ( directory. path ( ) , format, Rotation :: DAILY ) . unwrap_err ( ) ;
201- assert_eq ! ( error. kind( ) , io:: ErrorKind :: AlreadyExists ) ;
202- assert_eq ! ( history( directory. path( ) , Format :: Shorthand ) , before) ;
203- assert_eq ! ( fs:: read( & legacy) . unwrap( ) , b"legacy security records" ) ;
204- assert_eq ! ( fs:: read( & current) . unwrap( ) , b"new security records" ) ;
205- }
206- }
207-
208- #[ test]
209- fn migration_leaves_unrelated_files_and_directories_untouched ( ) {
210- let directory = tempfile:: tempdir ( ) . unwrap ( ) ;
211- let names = [
212- "openshell.2000-01-01.log" ,
213- "openshell-ocsf.2000-01-01.jsonl" ,
214- "openshell-ocsf.2000-01-01.log.bak" ,
215- "openshell-ocsf.2000-01-01.LOG" ,
216- "other.2000-01-01.log" ,
217- ] ;
218- for name in names {
219- fs:: write ( directory. path ( ) . join ( name) , name. as_bytes ( ) ) . unwrap ( ) ;
220- }
221- let nested = directory. path ( ) . join ( "openshell-ocsf.2000-01-02.log" ) ;
222- fs:: create_dir ( & nested) . unwrap ( ) ;
223- migrate_legacy_ocsf_logs ( directory. path ( ) ) . unwrap ( ) ;
224- for name in names {
225- assert_eq ! (
226- fs:: read( directory. path( ) . join( name) ) . unwrap( ) ,
227- name. as_bytes( )
228- ) ;
229- }
230- assert ! ( nested. is_dir( ) ) ;
231- }
232-
233- #[ test]
234- fn missing_log_directory_is_created ( ) {
235- let directory = tempfile:: tempdir ( ) . unwrap ( ) ;
236- let logs = directory. path ( ) . join ( "logs" ) ;
237- let _writer = appender ( & logs, Format :: Shorthand , Rotation :: DAILY ) . unwrap ( ) ;
238- assert_eq ! ( history( & logs, Format :: Shorthand ) . len( ) , 1 ) ;
239- }
240-
241- fn write_until_rollover (
242- writer : & mut RollingFileAppender ,
243- directory : & Path ,
244- format : Format ,
245- deadline : Instant ,
246- ) {
247- let before = history ( directory, format) ;
248- loop {
249- writer. write_all ( b"new event\n " ) . unwrap ( ) ;
250- writer. flush ( ) . unwrap ( ) ;
251- let current = history ( directory, format) ;
252- if current. keys ( ) . ne ( before. keys ( ) ) {
253- assert_eq ! ( current. len( ) , 3 ) ;
254- return ;
255- }
256- assert ! ( Instant :: now( ) < deadline, "no rollover within 65 seconds" ) ;
257- std:: thread:: sleep ( Duration :: from_millis ( 100 ) ) ;
258- }
259- }
260-
261- #[ test]
262- fn rollover_retains_each_format_independently ( ) {
263- // MINUTELY and DAILY share the same pruning path. The dependency's
264- // mock clock is private, so cross a real minute boundary for this test.
265- let directory = tempfile:: tempdir ( ) . unwrap ( ) ;
266- for format in [ Format :: Shorthand , Format :: Ocsf ] {
267- seed_history ( directory. path ( ) , format, true ) ;
268- }
269- let mut text = appender ( directory. path ( ) , Format :: Shorthand , Rotation :: MINUTELY ) . unwrap ( ) ;
270- let mut json = appender ( directory. path ( ) , Format :: Ocsf , Rotation :: MINUTELY ) . unwrap ( ) ;
271- let deadline = Instant :: now ( ) + Duration :: from_secs ( 65 ) ;
272- let before = history ( directory. path ( ) , Format :: Ocsf ) ;
273- write_until_rollover ( & mut text, directory. path ( ) , Format :: Shorthand , deadline) ;
274- assert_eq ! ( history( directory. path( ) , Format :: Ocsf ) , before) ;
275- let before = history ( directory. path ( ) , Format :: Shorthand ) ;
276- write_until_rollover ( & mut json, directory. path ( ) , Format :: Ocsf , deadline) ;
277- assert_eq ! ( history( directory. path( ) , Format :: Shorthand ) , before) ;
278- }
279- }
85+ mod tests;
0 commit comments