@@ -174,34 +174,170 @@ impl<T: IteratorWriter<Entry>> IteratorWriter<Entry> for AtomicClangOutputWriter
174
174
/// Responsible for writing a JSON compilation database file from the given entries.
175
175
///
176
176
/// # Features
177
- /// - Writes the entries to a file.
178
177
/// - 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 ,
181
180
filter : filter_duplicates:: DuplicateFilter ,
182
181
}
183
182
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
+ }
186
200
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
+ }
189
208
209
+ impl ClangOutputWriter {
210
+ pub ( super ) fn create ( file_name : & path:: Path ) -> anyhow:: Result < Self > {
190
211
let output = fs:: File :: create ( file_name)
191
212
. map ( io:: BufWriter :: new)
192
213
. with_context ( || format ! ( "Failed to open file: {:?}" , file_name) ) ?;
193
214
194
- let filter = filter_duplicates:: DuplicateFilter :: try_from ( config. clone ( ) ) ?;
195
-
196
- Ok ( Self { output, filter } )
215
+ Ok ( Self { output } )
197
216
}
198
217
}
199
218
200
219
impl IteratorWriter < Entry > for ClangOutputWriter {
201
220
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) ?;
205
222
Ok ( ( ) )
206
223
}
207
224
}
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