Skip to content

Commit 86da0b0

Browse files
committed
Improve and add tests
1 parent 6646a2a commit 86da0b0

File tree

6 files changed

+212
-45
lines changed

6 files changed

+212
-45
lines changed

bin_tests/src/bin/crashing_test_app.rs

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,23 @@ mod unix {
2323

2424
const TEST_COLLECTOR_TIMEOUT: Duration = Duration::from_secs(10);
2525

26-
#[inline(never)]
27-
unsafe fn fn3() {
26+
enum CrashType {
27+
Segfault,
28+
Panic,
29+
}
30+
31+
impl std::str::FromStr for CrashType {
32+
type Err = anyhow::Error;
33+
fn from_str(s: &str) -> Result<Self, Self::Err> {
34+
match s {
35+
"segfault" => Ok(CrashType::Segfault),
36+
"panic" => Ok(CrashType::Panic),
37+
_ => anyhow::bail!("Invalid crash type: {s}"),
38+
}
39+
}
40+
}
41+
42+
unsafe fn cause_segfault() {
2843
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
2944
{
3045
std::arch::asm!("mov eax, [0]", options(nostack));
@@ -37,13 +52,25 @@ mod unix {
3752
}
3853

3954
#[inline(never)]
40-
fn fn2() {
41-
unsafe { fn3() }
55+
fn fn3(crash_type: CrashType) {
56+
match crash_type {
57+
CrashType::Segfault => {
58+
unsafe { cause_segfault() };
59+
}
60+
CrashType::Panic => {
61+
panic!("program panicked");
62+
}
63+
}
64+
}
65+
66+
#[inline(never)]
67+
fn fn2(crash_type: CrashType) {
68+
fn3(crash_type);
4269
}
4370

4471
#[inline(never)]
45-
fn fn1() {
46-
fn2()
72+
fn fn1(crash_type: CrashType) {
73+
fn2(crash_type);
4774
}
4875

4976
#[inline(never)]
@@ -53,6 +80,7 @@ mod unix {
5380
let output_url = args.next().context("Unexpected number of arguments 1")?;
5481
let receiver_binary = args.next().context("Unexpected number of arguments 2")?;
5582
let output_dir = args.next().context("Unexpected number of arguments 3")?;
83+
let crash_type = args.next().context("Unexpected number of arguments 4")?;
5684
anyhow::ensure!(args.next().is_none(), "unexpected extra arguments");
5785

5886
let stderr_filename = format!("{output_dir}/out.stderr");
@@ -100,7 +128,7 @@ mod unix {
100128
metadata,
101129
)?;
102130

103-
fn1();
131+
fn1(crash_type.parse().context("Invalid crash type")?);
104132
Ok(())
105133
}
106134
}

bin_tests/tests/crashtracker_bin_test.rs

Lines changed: 82 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,87 @@ fn test_crash_tracking_errors_intake_uds_socket() {
198198
);
199199
}
200200

201+
#[test]
202+
#[cfg_attr(miri, ignore)]
203+
fn test_crash_tracking_bin_panic() {
204+
test_crash_tracking_app("panic");
205+
}
206+
207+
#[test]
208+
#[cfg_attr(miri, ignore)]
209+
fn test_crash_tracking_bin_panic() {
210+
test_crash_tracking_app("segfault");
211+
}
212+
213+
fn test_crash_tracking_app(crash_type: &str) {
214+
let (_, crashtracker_receiver) = setup_crashtracking_crates(BuildProfile::Release);
215+
216+
let crashing_app = ArtifactsBuild {
217+
name: "crashing_test_app".to_owned(),
218+
build_profile: BuildProfile::Debug,
219+
artifact_type: ArtifactType::Bin,
220+
triple_target: None,
221+
..Default::default()
222+
};
223+
224+
let fixtures = setup_test_fixtures(&[&crashtracker_receiver, &crashing_app]);
225+
226+
let mut p = process::Command::new(&fixtures.artifacts[&crashing_app])
227+
.arg(format!("file://{}", fixtures.crash_profile_path.display()))
228+
.arg(fixtures.artifacts[&crashtracker_receiver].as_os_str())
229+
.arg(&fixtures.output_dir)
230+
.arg(crash_type)
231+
.spawn()
232+
.unwrap();
233+
234+
let exit_status = bin_tests::timeit!("exit after signal", {
235+
eprintln!("Waiting for exit");
236+
p.wait().unwrap()
237+
});
238+
assert!(!exit_status.success());
239+
240+
let stderr_path = format!("{0}/out.stderr", fixtures.output_dir.display());
241+
let stderr = fs::read(stderr_path)
242+
.context("reading crashtracker stderr")
243+
.unwrap();
244+
let stdout_path = format!("{0}/out.stdout", fixtures.output_dir.display());
245+
let stdout = fs::read(stdout_path)
246+
.context("reading crashtracker stdout")
247+
.unwrap();
248+
let s = String::from_utf8(stderr);
249+
assert!(
250+
matches!(
251+
s.as_deref(),
252+
Ok("") | Ok("Failed to fully receive crash. Exit state was: StackTrace([])\n")
253+
| Ok("Failed to fully receive crash. Exit state was: InternalError(\"{\\\"ip\\\": \\\"\")\n"),
254+
),
255+
"got {s:?}"
256+
);
257+
assert_eq!(Ok(""), String::from_utf8(stdout).as_deref());
258+
259+
let crash_profile = fs::read(fixtures.crash_profile_path)
260+
.context("reading crashtracker profiling payload")
261+
.unwrap();
262+
let crash_payload = serde_json::from_slice::<serde_json::Value>(&crash_profile)
263+
.context("deserializing crashtracker profiling payload to json")
264+
.unwrap();
265+
266+
let sig_info = &crash_payload["sig_info"];
267+
268+
match crash_type {
269+
"panic" => {
270+
let error = &crash_payload["error"];
271+
let expected_message = "program panicked";
272+
assert_eq!(error["message"].as_str().unwrap(), expected_message);
273+
}
274+
"segfault" => {
275+
let error = &crash_payload["error"];
276+
assert_error_message(&error["message"], sig_info);
277+
}
278+
_ => unreachable!("Invalid crash type: {crash_type}"),
279+
}
280+
}
281+
201282
// This test is disabled for now on x86_64 musl and macos
202283
// It seems that on aarch64 musl, libc has CFI which allows
203284
// unwinding passed the signal frame.
@@ -208,9 +289,6 @@ fn test_crasht_tracking_validate_callstack() {
208289
test_crash_tracking_callstack()
209290
}
210291

211-
#[test]
212-
#[cfg(not(any(all(target_arch = "x86_64", target_env = "musl"), target_os = "macos")))]
213-
#[cfg_attr(miri, ignore)]
214292
fn test_crash_tracking_callstack() {
215293
let (_, crashtracker_receiver) = setup_crashtracking_crates(BuildProfile::Release);
216294

@@ -230,6 +308,7 @@ fn test_crash_tracking_callstack() {
230308
.arg(format!("file://{}", fixtures.crash_profile_path.display()))
231309
.arg(fixtures.artifacts[&crashtracker_receiver].as_os_str())
232310
.arg(&fixtures.output_dir)
311+
.arg("segfault")
233312
.spawn()
234313
.unwrap();
235314

libdd-crashtracker/src/collector/crash_handler.rs

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -97,32 +97,19 @@ pub fn register_panic_hook() -> anyhow::Result<()> {
9797
let old_hook = panic::take_hook();
9898
let old_hook_ptr = Box::into_raw(Box::new(old_hook));
9999
PREVIOUS_PANIC_HOOK.swap(old_hook_ptr, SeqCst);
100-
panic::set_hook(Box::new(panic_hook));
101-
Ok(())
102-
}
103-
104-
/// The panic hook function.
105-
///
106-
/// This function is used to update the metadata with the panic message
107-
/// and call the previous hook.
108-
/// PRECONDITIONS:
109-
/// None
110-
/// SAFETY:
111-
/// Crash-tracking functions are not guaranteed to be reentrant.
112-
/// No other crash-handler functions should be called concurrently.
113-
/// ATOMICITY:
114-
/// This function uses a load on an atomic pointer.
115-
fn panic_hook(panic_info: &PanicHookInfo<'_>) {
116-
if let Some(s) = panic_info.payload().downcast_ref::<&str>() {
117-
let message_ptr = PANIC_MESSAGE.swap(Box::into_raw(Box::new(s.to_string())), SeqCst);
118-
// message_ptr should be null, but just in case.
119-
if !message_ptr.is_null() {
120-
unsafe {
121-
std::mem::drop(Box::from_raw(message_ptr));
100+
panic::set_hook(Box::new(|panic_info| {
101+
if let Some(s) = panic_info.payload().downcast_ref::<&str>() {
102+
let message_ptr = PANIC_MESSAGE.swap(Box::into_raw(Box::new(s.to_string())), SeqCst);
103+
// message_ptr should be null, but just in case.
104+
if !message_ptr.is_null() {
105+
unsafe {
106+
std::mem::drop(Box::from_raw(message_ptr));
107+
}
122108
}
123109
}
124-
}
125-
call_previous_panic_hook(panic_info);
110+
call_previous_panic_hook(panic_info);
111+
}));
112+
Ok(())
126113
}
127114

128115
/// Call the previous panic hook.

libdd-crashtracker/src/collector/emitters.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,9 @@ pub(crate) fn emit_crashreport(
144144
ppid: i32,
145145
) -> Result<(), EmitterError> {
146146
emit_message(pipe, message_ptr)?;
147+
emit_siginfo(pipe, sig_info)?;
147148
emit_metadata(pipe, metadata_string)?;
148149
emit_config(pipe, config_str)?;
149-
emit_siginfo(pipe, sig_info)?;
150150
emit_ucontext(pipe, ucontext)?;
151151
emit_procinfo(pipe, ppid)?;
152152
emit_counters(pipe)?;
@@ -469,4 +469,27 @@ mod tests {
469469
"crashtracker itself must be filtered away, found 'backtrace::backtrace'"
470470
);
471471
}
472+
473+
#[test]
474+
#[cfg_attr(miri, ignore)]
475+
fn test_emit_message_nullptr() {
476+
let mut buf = Vec::new();
477+
emit_message(&mut buf, std::ptr::null_mut()).expect("to work ;-)");
478+
assert!(buf.is_empty());
479+
}
480+
481+
#[test]
482+
#[cfg_attr(miri, ignore)]
483+
fn test_emit_message() {
484+
let message = "test message";
485+
let message_ptr = Box::into_raw(Box::new(message.to_string()));
486+
let mut buf = Vec::new();
487+
emit_message(&mut buf, message_ptr).expect("to work ;-)");
488+
let out = str::from_utf8(&buf).expect("to be valid UTF8");
489+
assert!(out.contains("BEGIN_MESSAGE"));
490+
assert!(out.contains("END_MESSAGE"));
491+
assert!(out.contains(message));
492+
// Clean up the allocated String
493+
unsafe { drop(Box::from_raw(message_ptr)) };
494+
}
472495
}

libdd-crashtracker/src/crash_info/builder.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,13 +398,17 @@ impl CrashInfoBuilder {
398398
/// This method requires that the builder has a UUID and metadata set.
399399
/// Siginfo is optional for platforms that don't support it (like Windows)
400400
pub fn build_crash_ping(&self) -> anyhow::Result<CrashPing> {
401+
let message = self.error.message.clone();
401402
let sig_info = self.sig_info.clone();
402403
let metadata = self.metadata.clone().context("metadata is required")?;
403404

404405
let mut builder = CrashPingBuilder::new(self.uuid).with_metadata(metadata);
405406
if let Some(sig_info) = sig_info {
406407
builder = builder.with_sig_info(sig_info);
407408
}
409+
if let Some(message) = message {
410+
builder = builder.with_custom_message(message);
411+
}
408412
builder.build()
409413
}
410414

libdd-crashtracker/src/crash_info/telemetry.rs

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,16 @@ impl CrashPingBuilder {
6363
let sig_info = self.sig_info;
6464
let metadata = self.metadata.context("metadata is required")?;
6565

66-
let message = self.custom_message.unwrap_or_else(|| {
67-
if let Some(ref sig_info) = sig_info {
68-
format!(
69-
"Crashtracker crash ping: crash processing started - Process terminated with {:?} ({:?})",
70-
sig_info.si_code_human_readable, sig_info.si_signo_human_readable
71-
)
72-
} else {
73-
"Crashtracker crash ping: crash processing started - Process terminated".to_string()
74-
}
75-
});
66+
let message = if let Some(custom_message) = self.custom_message {
67+
format!("Crashtracker crash ping: crash processing started - {custom_message}")
68+
} else if let Some(ref sig_info) = sig_info {
69+
format!(
70+
"Crashtracker crash ping: crash processing started - Process terminated with {:?} ({:?})",
71+
sig_info.si_code_human_readable, sig_info.si_signo_human_readable
72+
)
73+
} else {
74+
"Crashtracker crash ping: crash processing started - Process terminated".to_string()
75+
};
7676

7777
Ok(CrashPing {
7878
crash_uuid: crash_uuid.to_string(),
@@ -802,6 +802,52 @@ mod tests {
802802
assert_eq!(crash_ping.siginfo(), Some(&sig_info));
803803
}
804804

805+
#[test]
806+
#[cfg_attr(miri, ignore)]
807+
fn test_crash_ping_with_message_generated_from_sig_info() {
808+
let sig_info = crate::SigInfo::test_instance(99);
809+
let metadata = Metadata::test_instance(2);
810+
811+
// Build crash ping through CrashInfoBuilder
812+
let mut crash_info_builder = CrashInfoBuilder::new();
813+
crash_info_builder.with_sig_info(sig_info.clone()).unwrap();
814+
crash_info_builder.with_metadata(metadata.clone()).unwrap();
815+
let crash_ping = crash_info_builder.build_crash_ping().unwrap();
816+
817+
assert!(!crash_ping.crash_uuid().is_empty());
818+
assert!(Uuid::parse_str(crash_ping.crash_uuid()).is_ok());
819+
assert_eq!(crash_ping.message(), format!(
820+
"Crashtracker crash ping: crash processing started - Process terminated with {:?} ({:?})",
821+
sig_info.si_code_human_readable, sig_info.si_signo_human_readable
822+
));
823+
assert_eq!(crash_ping.metadata(), &metadata);
824+
assert_eq!(crash_ping.siginfo(), Some(&sig_info));
825+
}
826+
827+
#[test]
828+
#[cfg_attr(miri, ignore)]
829+
fn test_crash_ping_with_custom_message() {
830+
let sig_info = crate::SigInfo::test_instance(99);
831+
let metadata = Metadata::test_instance(2);
832+
833+
// Build crash ping through CrashInfoBuilder
834+
let mut crash_info_builder = CrashInfoBuilder::new();
835+
crash_info_builder.with_sig_info(sig_info.clone()).unwrap();
836+
crash_info_builder.with_metadata(metadata.clone()).unwrap();
837+
crash_info_builder
838+
.with_message("my process panicked".to_string())
839+
.unwrap();
840+
let crash_ping = crash_info_builder.build_crash_ping().unwrap();
841+
842+
assert!(!crash_ping.crash_uuid().is_empty());
843+
assert!(Uuid::parse_str(crash_ping.crash_uuid()).is_ok());
844+
assert!(crash_ping
845+
.message()
846+
.contains("crash processing started - my process panicked"));
847+
assert_eq!(crash_ping.metadata(), &metadata);
848+
assert_eq!(crash_ping.siginfo(), Some(&sig_info));
849+
}
850+
805851
#[tokio::test]
806852
#[cfg_attr(miri, ignore)]
807853
async fn test_crash_ping_telemetry_upload_all_fields() -> anyhow::Result<()> {

0 commit comments

Comments
 (0)