Skip to content

Commit 66d351b

Browse files
committed
rust: add testing method for ouput writers
1 parent 1e78597 commit 66d351b

File tree

2 files changed

+155
-18
lines changed

2 files changed

+155
-18
lines changed

rust/bear/src/output/clang/mod.rs

Lines changed: 149 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -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

200219
impl 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+
}

rust/bear/src/output/mod.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ mod json;
77
use crate::{args, config, semantic};
88
use anyhow::Context;
99
use clang::{
10-
AppendClangOutputWriter, AtomicClangOutputWriter, ClangOutputWriter, FormattedClangOutputWriter,
10+
AppendClangOutputWriter, AtomicClangOutputWriter, ClangOutputWriter,
11+
FormattedClangOutputWriter, UniqueOutputWriter,
1112
};
1213
use formats::{FileFormat, JsonSemanticDatabase};
1314
use std::{fs, io, path};
@@ -23,7 +24,7 @@ pub enum OutputWriter {
2324
#[allow(private_interfaces)]
2425
Clang(
2526
FormattedClangOutputWriter<
26-
AppendClangOutputWriter<AtomicClangOutputWriter<ClangOutputWriter>>,
27+
AppendClangOutputWriter<AtomicClangOutputWriter<UniqueOutputWriter<ClangOutputWriter>>>,
2728
>,
2829
),
2930
#[allow(private_interfaces)]
@@ -40,10 +41,10 @@ impl TryFrom<(&args::BuildSemantic, &config::Output)> for OutputWriter {
4041
let final_file_name = path::Path::new(&args.file_name);
4142
let temp_file_name = final_file_name.with_extension("tmp");
4243

43-
let base_writer =
44-
ClangOutputWriter::try_from((temp_file_name.as_path(), duplicates))?;
44+
let base_writer = ClangOutputWriter::create(&temp_file_name)?;
45+
let unique_writer = UniqueOutputWriter::create(base_writer, duplicates)?;
4546
let atomic_writer =
46-
AtomicClangOutputWriter::new(base_writer, &temp_file_name, final_file_name);
47+
AtomicClangOutputWriter::new(unique_writer, &temp_file_name, final_file_name);
4748
let append_writer =
4849
AppendClangOutputWriter::new(atomic_writer, args.append, final_file_name);
4950
let formatted_writer = FormattedClangOutputWriter::new(append_writer);

0 commit comments

Comments
 (0)