Skip to content

Commit e25e5b4

Browse files
committed
Add integration test for --log
1 parent 125d361 commit e25e5b4

2 files changed

Lines changed: 143 additions & 0 deletions

File tree

candumpr/src/sink.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ impl Output {
4949
std::fs::create_dir_all(parent)?;
5050
}
5151
let file = std::fs::File::create(&path)?;
52+
tracing::info!(path = %path.display(), "created log file");
5253
Ok(Box::new(std::io::BufWriter::with_capacity(
5354
flush_threshold_bytes,
5455
FileWriter::new(file),

candumpr/tests/file_output.rs

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
use std::os::unix::io::AsFd;
2+
use std::path::{Path, PathBuf};
3+
use std::time::{Duration, Instant};
4+
5+
use candumpr::can::{self, LinuxCanFrame};
6+
use pretty_assertions::assert_eq;
7+
use vcan_fixture::VcanHarness;
8+
use vcan_fixture::prelude::*;
9+
10+
#[ctor::ctor]
11+
fn setup() {
12+
tracing_subscriber::fmt()
13+
.with_test_writer()
14+
.with_ansi(true)
15+
.init();
16+
vcan_fixture::enter_namespace();
17+
}
18+
19+
fn entries(dir: &Path) -> Vec<PathBuf> {
20+
let mut paths: Vec<_> = std::fs::read_dir(dir)
21+
.unwrap()
22+
.map(|e| e.unwrap().path())
23+
.collect();
24+
paths.sort();
25+
paths
26+
}
27+
28+
/// Wait for the log file to appear, and return everything in `dir` once it has.
29+
#[track_caller]
30+
fn wait_for_log_file(dir: &Path) -> Vec<PathBuf> {
31+
let deadline = Instant::now() + Duration::from_secs(5);
32+
loop {
33+
let paths = entries(dir);
34+
if !paths.is_empty() {
35+
return paths;
36+
}
37+
assert!(
38+
Instant::now() < deadline,
39+
"candumpr created no log file in {dir:?}"
40+
);
41+
std::thread::sleep(Duration::from_millis(50));
42+
}
43+
}
44+
45+
#[test]
46+
#[cfg_attr(feature = "ci", ignore = "requires vcan")]
47+
fn logs_one_interface_to_a_scheme_named_file() {
48+
let vcans = VcanHarness::new(1).unwrap();
49+
let iface = &vcans.names()[0];
50+
let dir = tempfile::TempDir::new().unwrap();
51+
52+
let child = tool!("candumpr")
53+
.args(["-l", "--timestamp", "zero"])
54+
.arg(iface)
55+
.current_dir(dir.path())
56+
.spawn_piped()
57+
.unwrap();
58+
59+
// Give enough time for candumpr to create its socket and start receiving
60+
std::thread::sleep(Duration::from_millis(200));
61+
62+
let tx = can::open_can_raw_blocking(iface).unwrap();
63+
can::send_frame(tx.as_fd(), &LinuxCanFrame::new(0x123, &[0xAB])).unwrap();
64+
65+
let paths = wait_for_log_file(dir.path()); // panics on timeout
66+
67+
child.signal(libc::SIGTERM).unwrap();
68+
let output = child.captured_output().unwrap();
69+
assert!(
70+
output.status.success(),
71+
"expected a clean exit, got {}",
72+
output.status
73+
);
74+
75+
assert_eq!(paths.len(), 1, "expected exactly one log file: {paths:?}");
76+
let name = paths[0].file_name().unwrap().to_str().unwrap();
77+
// The timestamp is the frame's arrival time, so only the fixed parts are predictable.
78+
let prefix = format!("i0000_{iface}_");
79+
assert!(
80+
name.starts_with(&prefix) && name.ends_with(".log"),
81+
"expected a {prefix}*.log file, got {name}"
82+
);
83+
84+
// Deterministic: --timestamp zero renders the first frame at 0.0, and only one frame is sent.
85+
assert_eq!(
86+
std::fs::read_to_string(&paths[0]).unwrap(),
87+
format!("(000.000000) {iface} 123#AB\n")
88+
);
89+
assert_eq!(String::from_utf8_lossy(&output.stdout), "");
90+
let stderr = String::from_utf8_lossy(&output.stderr);
91+
assert!(
92+
stderr.contains("opened CAN socket"),
93+
"expected the startup line, got:\n{stderr}"
94+
);
95+
// Use two .contains() to skip over the tracing fmt ANSI sequences
96+
assert!(
97+
stderr.contains("created log file") && stderr.contains(&format!("./{name}")),
98+
"expected the created path logged at INFO, got:\n{stderr}"
99+
);
100+
}
101+
102+
#[test]
103+
#[cfg_attr(feature = "ci", ignore = "requires vcan")]
104+
fn interleaves_multiple_interfaces_to_one_file() {
105+
let vcans = VcanHarness::new(2).unwrap();
106+
let iface1 = &vcans.names()[0];
107+
let iface2 = &vcans.names()[1];
108+
let dir = tempfile::TempDir::new().unwrap();
109+
110+
let child = tool!("candumpr")
111+
.arg("--output=foo.bar")
112+
.arg(iface1)
113+
.arg(iface2)
114+
.current_dir(dir.path())
115+
.spawn_piped()
116+
.unwrap();
117+
118+
// Give enough time for candumpr to create its socket and start receiving
119+
std::thread::sleep(Duration::from_millis(200));
120+
121+
let tx1 = can::open_can_raw_blocking(iface1).unwrap();
122+
let tx2 = can::open_can_raw_blocking(iface2).unwrap();
123+
can::send_frame(tx1.as_fd(), &LinuxCanFrame::new(0x123, &[0xAB])).unwrap();
124+
can::send_frame(tx2.as_fd(), &LinuxCanFrame::new(0x456, &[0xCD])).unwrap();
125+
126+
let paths = wait_for_log_file(dir.path()); // panics on timeout
127+
128+
child.signal(libc::SIGTERM).unwrap();
129+
let output = child.captured_output().unwrap();
130+
assert!(
131+
output.status.success(),
132+
"expected a clean exit, got {}",
133+
output.status
134+
);
135+
136+
assert_eq!(paths.len(), 1, "expected exactly one log file: {paths:?}");
137+
let name = paths[0].file_name().unwrap().to_str().unwrap();
138+
assert_eq!(name, "foo.bar");
139+
let log = std::fs::read_to_string(&paths[0]).unwrap();
140+
assert!(log.contains(&format!("{iface1} 123#AB\n")));
141+
assert!(log.contains(&format!("{iface2} 456#CD\n")));
142+
}

0 commit comments

Comments
 (0)