@@ -174,34 +174,170 @@ impl<T: IteratorWriter<Entry>> IteratorWriter<Entry> for AtomicClangOutputWriter
174174/// Responsible for writing a JSON compilation database file from the given entries.
175175///
176176/// # Features
177- /// - Writes the entries to a file.
178177/// - Filters duplicates based on the provided configuration.
179- pub ( super ) struct ClangOutputWriter {
180- output : io :: BufWriter < fs :: File > ,
178+ pub ( super ) struct UniqueOutputWriter < T : IteratorWriter < Entry > > {
179+ writer : T ,
181180 filter : filter_duplicates:: DuplicateFilter ,
182181}
183182
184- impl TryFrom < ( & path:: Path , & config:: DuplicateFilter ) > for ClangOutputWriter {
185- type Error = anyhow:: Error ;
183+ impl < T : IteratorWriter < Entry > > UniqueOutputWriter < T > {
184+ pub ( super ) fn create ( writer : T , config : & config:: DuplicateFilter ) -> anyhow:: Result < Self > {
185+ let filter = filter_duplicates:: DuplicateFilter :: try_from ( config. clone ( ) )
186+ . with_context ( || format ! ( "Failed to create duplicate filter: {:?}" , config) ) ?;
187+
188+ Ok ( Self { writer, filter } )
189+ }
190+ }
191+
192+ impl < T : IteratorWriter < Entry > > IteratorWriter < Entry > for UniqueOutputWriter < T > {
193+ fn write ( self , entries : impl Iterator < Item = Entry > ) -> anyhow:: Result < ( ) > {
194+ let mut filter = self . filter . clone ( ) ;
195+ let filtered_entries = entries. filter ( move |entry| filter. unique ( entry) ) ;
196+
197+ self . writer . write ( filtered_entries)
198+ }
199+ }
186200
187- fn try_from ( value : ( & path:: Path , & config:: DuplicateFilter ) ) -> Result < Self , Self :: Error > {
188- let ( file_name, config) = value;
201+ /// Responsible for writing a JSON compilation database file from the given entries.
202+ ///
203+ /// # Features
204+ /// - Writes the entries to a file.
205+ pub ( super ) struct ClangOutputWriter {
206+ output : io:: BufWriter < fs:: File > ,
207+ }
189208
209+ impl ClangOutputWriter {
210+ pub ( super ) fn create ( file_name : & path:: Path ) -> anyhow:: Result < Self > {
190211 let output = fs:: File :: create ( file_name)
191212 . map ( io:: BufWriter :: new)
192213 . with_context ( || format ! ( "Failed to open file: {:?}" , file_name) ) ?;
193214
194- let filter = filter_duplicates:: DuplicateFilter :: try_from ( config. clone ( ) ) ?;
195-
196- Ok ( Self { output, filter } )
215+ Ok ( Self { output } )
197216 }
198217}
199218
200219impl IteratorWriter < Entry > for ClangOutputWriter {
201220 fn write ( self , entries : impl Iterator < Item = Entry > ) -> anyhow:: Result < ( ) > {
202- let mut filter = self . filter . clone ( ) ;
203- let filtered_entries = entries. filter ( move |entry| filter. unique ( entry) ) ;
204- JsonCompilationDatabase :: write ( self . output , filtered_entries) ?;
221+ JsonCompilationDatabase :: write ( self . output , entries) ?;
205222 Ok ( ( ) )
206223 }
207224}
225+
226+ #[ cfg( test) ]
227+ mod tests {
228+ use super :: * ;
229+ use std:: fs:: { self } ;
230+ use tempfile:: tempdir;
231+
232+ struct MockWriter ;
233+
234+ impl IteratorWriter < Entry > for MockWriter {
235+ fn write ( self , _: impl Iterator < Item = Entry > ) -> anyhow:: Result < ( ) > {
236+ Ok ( ( ) )
237+ }
238+ }
239+
240+ #[ test]
241+ fn test_atomic_clang_output_writer_success ( ) {
242+ let dir = tempdir ( ) . unwrap ( ) ;
243+ let temp_file_path = dir. path ( ) . join ( "temp_file.json" ) ;
244+ let final_file_path = dir. path ( ) . join ( "final_file.json" ) ;
245+
246+ // Create the temp file
247+ fs:: File :: create ( & temp_file_path) . unwrap ( ) ;
248+
249+ let sut = AtomicClangOutputWriter :: new ( MockWriter , & temp_file_path, & final_file_path) ;
250+ sut. write ( std:: iter:: empty ( ) ) . unwrap ( ) ;
251+
252+ // Verify the final file exists
253+ assert ! ( final_file_path. exists( ) ) ;
254+ assert ! ( !temp_file_path. exists( ) ) ;
255+ }
256+
257+ #[ test]
258+ fn test_atomic_clang_output_writer_temp_file_missing ( ) {
259+ let dir = tempdir ( ) . unwrap ( ) ;
260+ let temp_file_path = dir. path ( ) . join ( "temp_file.json" ) ;
261+ let final_file_path = dir. path ( ) . join ( "final_file.json" ) ;
262+
263+ let sut = AtomicClangOutputWriter :: new ( MockWriter , & temp_file_path, & final_file_path) ;
264+ let result = sut. write ( std:: iter:: empty ( ) ) ;
265+
266+ // Verify the operation fails
267+ assert ! ( result. is_err( ) ) ;
268+ assert ! ( !final_file_path. exists( ) ) ;
269+ }
270+
271+ #[ test]
272+ fn test_atomic_clang_output_writer_final_file_exists ( ) {
273+ let dir = tempdir ( ) . unwrap ( ) ;
274+ let temp_file_path = dir. path ( ) . join ( "temp_file.json" ) ;
275+ let final_file_path = dir. path ( ) . join ( "final_file.json" ) ;
276+
277+ // Create the temp file and final file
278+ fs:: File :: create ( & temp_file_path) . unwrap ( ) ;
279+ fs:: File :: create ( & final_file_path) . unwrap ( ) ;
280+
281+ let sut = AtomicClangOutputWriter :: new ( MockWriter , & temp_file_path, & final_file_path) ;
282+ let result = sut. write ( std:: iter:: empty ( ) ) ;
283+
284+ // Verify the operation fails
285+ assert ! ( result. is_ok( ) ) ;
286+ assert ! ( final_file_path. exists( ) ) ;
287+ assert ! ( !temp_file_path. exists( ) ) ;
288+ }
289+
290+ #[ test]
291+ fn test_append_clang_output_writer_no_original_file ( ) {
292+ let dir = tempdir ( ) . unwrap ( ) ;
293+ let file_to_append = dir. path ( ) . join ( "file_to_append.json" ) ;
294+ let result_file = dir. path ( ) . join ( "result_file.json" ) ;
295+
296+ let entries_to_write = vec ! [
297+ entry( "file1.cpp" , vec![ "clang" , "-c" ] , "/path/to/dir" , None ) ,
298+ entry( "file2.cpp" , vec![ "clang" , "-c" ] , "/path/to/dir" , None ) ,
299+ ] ;
300+
301+ let writer = ClangOutputWriter :: create ( & result_file) . unwrap ( ) ;
302+ let sut = AppendClangOutputWriter :: new ( writer, false , & file_to_append) ;
303+ sut. write ( entries_to_write. into_iter ( ) ) . unwrap ( ) ;
304+
305+ // Verify the result file contains the written entries
306+ assert ! ( result_file. exists( ) ) ;
307+ let content = fs:: read_to_string ( & result_file) . unwrap ( ) ;
308+ assert ! ( content. contains( "file1.cpp" ) ) ;
309+ assert ! ( content. contains( "file2.cpp" ) ) ;
310+ }
311+
312+ #[ test]
313+ fn test_append_clang_output_writer_with_original_file ( ) {
314+ let dir = tempdir ( ) . unwrap ( ) ;
315+ let file_to_append = dir. path ( ) . join ( "file_to_append.json" ) ;
316+ let result_file = dir. path ( ) . join ( "result_file.json" ) ;
317+
318+ // Create the original file with some entries
319+ let original_entries = vec ! [
320+ entry( "file3.cpp" , vec![ "clang" , "-c" ] , "/path/to/dir" , None ) ,
321+ entry( "file4.cpp" , vec![ "clang" , "-c" ] , "/path/to/dir" , None ) ,
322+ ] ;
323+ let writer = ClangOutputWriter :: create ( & file_to_append) . unwrap ( ) ;
324+ writer. write ( original_entries. into_iter ( ) ) . unwrap ( ) ;
325+
326+ let new_entries = vec ! [
327+ entry( "file1.cpp" , vec![ "clang" , "-c" ] , "/path/to/dir" , None ) ,
328+ entry( "file2.cpp" , vec![ "clang" , "-c" ] , "/path/to/dir" , None ) ,
329+ ] ;
330+
331+ let writer = ClangOutputWriter :: create ( & result_file) . unwrap ( ) ;
332+ let sut = AppendClangOutputWriter :: new ( writer, false , & file_to_append) ;
333+ sut. write ( new_entries. into_iter ( ) ) . unwrap ( ) ;
334+
335+ // Verify the result file contains both original and new entries
336+ assert ! ( result_file. exists( ) ) ;
337+ let content = fs:: read_to_string ( & result_file) . unwrap ( ) ;
338+ assert ! ( content. contains( "file1.cpp" ) ) ;
339+ assert ! ( content. contains( "file2.cpp" ) ) ;
340+ assert ! ( content. contains( "file3.cpp" ) ) ;
341+ assert ! ( content. contains( "file4.cpp" ) ) ;
342+ }
343+ }
0 commit comments