Skip to content

Commit 34c2438

Browse files
committed
Improve and add tests
1 parent 6646a2a commit 34c2438

File tree

7 files changed

+225
-46
lines changed

7 files changed

+225
-46
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/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ pub struct ArtifactsBuild {
4343
pub artifact_type: ArtifactType,
4444
pub build_profile: BuildProfile,
4545
pub triple_target: Option<String>,
46+
pub panic_abort: Option<bool>,
4647
}
4748

4849
fn inner_build_artifact(c: &ArtifactsBuild) -> anyhow::Result<PathBuf> {
@@ -55,6 +56,13 @@ fn inner_build_artifact(c: &ArtifactsBuild) -> anyhow::Result<PathBuf> {
5556
ArtifactType::ExecutablePackage | ArtifactType::CDylib => build_cmd.arg("-p"),
5657
ArtifactType::Bin => build_cmd.arg("--bin"),
5758
};
59+
60+
if let Some(panic_abort) = c.panic_abort {
61+
if panic_abort {
62+
build_cmd.env("RUSTFLAGS", "-C panic=abort");
63+
}
64+
}
65+
5866
build_cmd.arg(&c.name);
5967

6068
let output = build_cmd.output().unwrap();

bin_tests/tests/crashtracker_bin_test.rs

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,88 @@ 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_segfault() {
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+
panic_abort: Some(true),
222+
..Default::default()
223+
};
224+
225+
let fixtures = setup_test_fixtures(&[&crashtracker_receiver, &crashing_app]);
226+
227+
let mut p = process::Command::new(&fixtures.artifacts[&crashing_app])
228+
.arg(format!("file://{}", fixtures.crash_profile_path.display()))
229+
.arg(fixtures.artifacts[&crashtracker_receiver].as_os_str())
230+
.arg(&fixtures.output_dir)
231+
.arg(crash_type)
232+
.spawn()
233+
.unwrap();
234+
235+
let exit_status = bin_tests::timeit!("exit after signal", {
236+
eprintln!("Waiting for exit");
237+
p.wait().unwrap()
238+
});
239+
assert!(!exit_status.success());
240+
241+
let stderr_path = format!("{0}/out.stderr", fixtures.output_dir.display());
242+
let stderr = fs::read(stderr_path)
243+
.context("reading crashtracker stderr")
244+
.unwrap();
245+
let stdout_path = format!("{0}/out.stdout", fixtures.output_dir.display());
246+
let stdout = fs::read(stdout_path)
247+
.context("reading crashtracker stdout")
248+
.unwrap();
249+
let s = String::from_utf8(stderr);
250+
assert!(
251+
matches!(
252+
s.as_deref(),
253+
Ok("") | Ok("Failed to fully receive crash. Exit state was: StackTrace([])\n")
254+
| Ok("Failed to fully receive crash. Exit state was: InternalError(\"{\\\"ip\\\": \\\"\")\n"),
255+
),
256+
"got {s:?}"
257+
);
258+
assert_eq!(Ok(""), String::from_utf8(stdout).as_deref());
259+
260+
let crash_profile = fs::read(fixtures.crash_profile_path)
261+
.context("reading crashtracker profiling payload")
262+
.unwrap();
263+
let crash_payload = serde_json::from_slice::<serde_json::Value>(&crash_profile)
264+
.context("deserializing crashtracker profiling payload to json")
265+
.unwrap();
266+
267+
let sig_info = &crash_payload["sig_info"];
268+
269+
match crash_type {
270+
"panic" => {
271+
let error = &crash_payload["error"];
272+
let expected_message = "program panicked";
273+
assert_eq!(error["message"].as_str().unwrap(), expected_message);
274+
}
275+
"segfault" => {
276+
let error = &crash_payload["error"];
277+
assert_error_message(&error["message"], sig_info);
278+
}
279+
_ => unreachable!("Invalid crash type: {crash_type}"),
280+
}
281+
}
282+
201283
// This test is disabled for now on x86_64 musl and macos
202284
// It seems that on aarch64 musl, libc has CFI which allows
203285
// unwinding passed the signal frame.
@@ -208,9 +290,6 @@ fn test_crasht_tracking_validate_callstack() {
208290
test_crash_tracking_callstack()
209291
}
210292

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

@@ -230,6 +309,7 @@ fn test_crash_tracking_callstack() {
230309
.arg(format!("file://{}", fixtures.crash_profile_path.display()))
231310
.arg(fixtures.artifacts[&crashtracker_receiver].as_os_str())
232311
.arg(&fixtures.output_dir)
312+
.arg("segfault")
233313
.spawn()
234314
.unwrap();
235315

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: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,13 @@ pub(crate) fn emit_crashreport(
143143
ucontext: *const ucontext_t,
144144
ppid: i32,
145145
) -> Result<(), EmitterError> {
146-
emit_message(pipe, message_ptr)?;
147-
emit_metadata(pipe, metadata_string)?;
148146
emit_config(pipe, config_str)?;
147+
emit_message(pipe, message_ptr)?;
149148
emit_siginfo(pipe, sig_info)?;
149+
// send metadata after the config (needed to send the ping/report)
150+
// after the message
151+
// after the siginfo (if the message is not set, we use the siginfo to generate the message)
152+
emit_metadata(pipe, metadata_string)?;
150153
emit_ucontext(pipe, ucontext)?;
151154
emit_procinfo(pipe, ppid)?;
152155
emit_counters(pipe)?;
@@ -469,4 +472,27 @@ mod tests {
469472
"crashtracker itself must be filtered away, found 'backtrace::backtrace'"
470473
);
471474
}
475+
476+
#[test]
477+
#[cfg_attr(miri, ignore)]
478+
fn test_emit_message_nullptr() {
479+
let mut buf = Vec::new();
480+
emit_message(&mut buf, std::ptr::null_mut()).expect("to work ;-)");
481+
assert!(buf.is_empty());
482+
}
483+
484+
#[test]
485+
#[cfg_attr(miri, ignore)]
486+
fn test_emit_message() {
487+
let message = "test message";
488+
let message_ptr = Box::into_raw(Box::new(message.to_string()));
489+
let mut buf = Vec::new();
490+
emit_message(&mut buf, message_ptr).expect("to work ;-)");
491+
let out = str::from_utf8(&buf).expect("to be valid UTF8");
492+
assert!(out.contains("BEGIN_MESSAGE"));
493+
assert!(out.contains("END_MESSAGE"));
494+
assert!(out.contains(message));
495+
// Clean up the allocated String
496+
unsafe { drop(Box::from_raw(message_ptr)) };
497+
}
472498
}

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)