Skip to content

Commit f4c0bdf

Browse files
committed
Bin tests
1 parent e753dad commit f4c0bdf

File tree

2 files changed

+227
-3
lines changed

2 files changed

+227
-3
lines changed

bin_tests/tests/crashtracker_bin_test.rs

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,18 @@ fn test_crash_ping_timing_and_content() {
135135
test_crash_tracking_bin(BuildProfile::Release, "donothing", "null_deref");
136136
}
137137

138+
#[test]
139+
#[cfg_attr(miri, ignore)]
140+
fn test_crash_tracking_errors_intake_upload() {
141+
test_crash_tracking_bin_with_errors_intake(BuildProfile::Release, "donothing", "null_deref");
142+
}
143+
144+
#[test]
145+
#[cfg_attr(miri, ignore)]
146+
fn test_crash_tracking_errors_intake_crash_ping() {
147+
test_crash_tracking_errors_intake_dual_upload(BuildProfile::Release, "donothing", "null_deref");
148+
}
149+
138150
// This test is disabled for now on x86_64 musl and macos
139151
// It seems that on aarch64 musl, libc has CFI which allows
140152
// unwinding passed the signal frame.
@@ -700,6 +712,214 @@ fn setup_crashtracking_crates(
700712
(crashtracker_bin, crashtracker_receiver)
701713
}
702714

715+
fn test_crash_tracking_bin_with_errors_intake(
716+
crash_tracking_receiver_profile: BuildProfile,
717+
mode: &str,
718+
crash_typ: &str,
719+
) {
720+
let (crashtracker_bin, crashtracker_receiver) =
721+
setup_crashtracking_crates(crash_tracking_receiver_profile);
722+
let fixtures = setup_test_fixtures(&[&crashtracker_receiver, &crashtracker_bin]);
723+
724+
let mut p = process::Command::new(&fixtures.artifacts[&crashtracker_bin])
725+
.arg(format!("file://{}", fixtures.crash_profile_path.display()))
726+
.arg(fixtures.artifacts[&crashtracker_receiver].as_os_str())
727+
.arg(&fixtures.output_dir)
728+
.arg(mode)
729+
.arg(crash_typ)
730+
.spawn()
731+
.unwrap();
732+
733+
let exit_status = bin_tests::timeit!("exit after signal", {
734+
eprintln!("Waiting for exit");
735+
p.wait().unwrap()
736+
});
737+
738+
match crash_typ {
739+
"kill_sigabrt" | "kill_sigill" | "null_deref" | "raise_sigabrt" | "raise_sigill" => {
740+
assert!(!exit_status.success())
741+
}
742+
"kill_sigbus" | "kill_sigsegv" | "raise_sigbus" | "raise_sigsegv" => {
743+
assert!(exit_status.success())
744+
}
745+
_ => unreachable!("{crash_typ} shouldn't happen"),
746+
}
747+
748+
// Check that errors intake file was created
749+
let errors_intake_path = fixtures.crash_profile_path.with_extension("errors");
750+
assert!(
751+
errors_intake_path.exists(),
752+
"Errors intake file should be created at {}",
753+
errors_intake_path.display()
754+
);
755+
756+
// Read and validate errors intake payload
757+
let errors_intake_content = fs::read(&errors_intake_path)
758+
.context("reading errors intake payload")
759+
.unwrap();
760+
let errors_payload = serde_json::from_slice::<serde_json::Value>(&errors_intake_content)
761+
.context("deserializing errors intake payload to json")
762+
.unwrap();
763+
764+
// Validate errors intake payload structure
765+
assert_errors_intake_payload(&errors_payload, crash_typ);
766+
767+
// Also validate telemetry still works (dual upload)
768+
let crash_telemetry = fs::read(&fixtures.crash_telemetry_path)
769+
.context("reading crashtracker telemetry payload")
770+
.unwrap();
771+
assert_telemetry_message(&crash_telemetry, crash_typ);
772+
}
773+
774+
fn test_crash_tracking_errors_intake_dual_upload(
775+
crash_tracking_receiver_profile: BuildProfile,
776+
mode: &str,
777+
crash_typ: &str,
778+
) {
779+
let (crashtracker_bin, crashtracker_receiver) =
780+
setup_crashtracking_crates(crash_tracking_receiver_profile);
781+
let fixtures = setup_test_fixtures(&[&crashtracker_receiver, &crashtracker_bin]);
782+
783+
let mut p = process::Command::new(&fixtures.artifacts[&crashtracker_bin])
784+
.arg(format!("file://{}", fixtures.crash_profile_path.display()))
785+
.arg(fixtures.artifacts[&crashtracker_receiver].as_os_str())
786+
.arg(&fixtures.output_dir)
787+
.arg(mode)
788+
.arg(crash_typ)
789+
.spawn()
790+
.unwrap();
791+
792+
let exit_status = bin_tests::timeit!("exit after signal", {
793+
eprintln!("Waiting for exit");
794+
p.wait().unwrap()
795+
});
796+
797+
match crash_typ {
798+
"kill_sigabrt" | "kill_sigill" | "null_deref" | "raise_sigabrt" | "raise_sigill" => {
799+
assert!(!exit_status.success())
800+
}
801+
"kill_sigbus" | "kill_sigsegv" | "raise_sigbus" | "raise_sigsegv" => {
802+
assert!(exit_status.success())
803+
}
804+
_ => unreachable!("{crash_typ} shouldn't happen"),
805+
}
806+
807+
// Check that errors intake file was created
808+
let errors_intake_path = fixtures.crash_profile_path.with_extension("errors");
809+
assert!(
810+
errors_intake_path.exists(),
811+
"Errors intake file should be created at {}",
812+
errors_intake_path.display()
813+
);
814+
815+
// Read and validate errors intake payload
816+
let errors_intake_content = fs::read(&errors_intake_path)
817+
.context("reading errors intake payload")
818+
.unwrap();
819+
820+
// The errors intake might contain multiple JSON objects (crash ping + crash report)
821+
// Try to parse as a single JSON first, if that fails, try line by line
822+
if let Ok(single_payload) = serde_json::from_slice::<serde_json::Value>(&errors_intake_content)
823+
{
824+
// Single JSON payload - validate it
825+
assert_errors_intake_payload(&single_payload, crash_typ);
826+
} else {
827+
// Multiple JSON objects - parse line by line
828+
let content_str = String::from_utf8(errors_intake_content).unwrap();
829+
let lines: Vec<&str> = content_str.lines().collect();
830+
assert!(!lines.is_empty(), "Errors intake file should not be empty");
831+
832+
let mut _found_crash_ping = false;
833+
let mut found_crash_report = false;
834+
835+
for line in lines {
836+
if line.trim().is_empty() {
837+
continue;
838+
}
839+
840+
let payload: serde_json::Value = serde_json::from_str(line)
841+
.context("parsing errors intake payload line")
842+
.unwrap();
843+
844+
assert_errors_intake_payload(&payload, crash_typ);
845+
846+
// Check which type this is
847+
let ddtags = payload["ddtags"].as_str().unwrap();
848+
if ddtags.contains("is_crash_ping:true") {
849+
_found_crash_ping = true;
850+
} else {
851+
found_crash_report = true;
852+
}
853+
}
854+
855+
// In dual upload mode, we expect at least the crash report
856+
// Crash ping might not always be sent (e.g., file endpoints skip it)
857+
assert!(
858+
found_crash_report,
859+
"Should have found crash report in errors intake"
860+
);
861+
}
862+
863+
// Also validate telemetry still works (dual upload)
864+
let crash_telemetry = fs::read(&fixtures.crash_telemetry_path)
865+
.context("reading crashtracker telemetry payload")
866+
.unwrap();
867+
assert_telemetry_message(&crash_telemetry, crash_typ);
868+
}
869+
870+
fn assert_errors_intake_payload(payload: &Value, crash_typ: &str) {
871+
// Validate basic structure
872+
assert_eq!(payload["ddsource"], "crashtracker");
873+
assert!(payload["timestamp"].is_number());
874+
assert!(payload["ddtags"].is_string());
875+
876+
let ddtags = payload["ddtags"].as_str().unwrap();
877+
assert!(ddtags.contains("service:foo"));
878+
assert!(ddtags.contains("uuid:"));
879+
880+
let error = &payload["error"];
881+
assert_eq!(error["source_type"], "Crashtracking");
882+
assert!(error["type"].is_string()); // Note: "error_type" field is serialized as "type"
883+
assert!(error["message"].is_string());
884+
885+
// Check if this is a crash ping or crash report
886+
if ddtags.contains("is_crash_ping:true") {
887+
assert_eq!(error["is_crash"], false);
888+
assert!(error["stack"].is_null());
889+
} else {
890+
assert_eq!(error["is_crash"], true);
891+
}
892+
893+
// Check signal-specific values
894+
match crash_typ {
895+
"null_deref" => {
896+
assert_eq!(error["type"], "SIGSEGV");
897+
assert!(error["message"]
898+
.as_str()
899+
.unwrap()
900+
.contains("Process terminated"));
901+
assert!(error["message"].as_str().unwrap().contains("SIGSEGV"));
902+
}
903+
"kill_sigabrt" | "raise_sigabrt" => {
904+
assert_eq!(error["type"], "SIGABRT");
905+
assert!(error["message"].as_str().unwrap().contains("SIGABRT"));
906+
}
907+
"kill_sigill" | "raise_sigill" => {
908+
assert_eq!(error["type"], "SIGILL");
909+
assert!(error["message"].as_str().unwrap().contains("SIGILL"));
910+
}
911+
"kill_sigbus" | "raise_sigbus" => {
912+
assert_eq!(error["type"], "SIGBUS");
913+
assert!(error["message"].as_str().unwrap().contains("SIGBUS"));
914+
}
915+
"kill_sigsegv" | "raise_sigsegv" => {
916+
assert_eq!(error["type"], "SIGSEGV");
917+
assert!(error["message"].as_str().unwrap().contains("SIGSEGV"));
918+
}
919+
_ => panic!("Unexpected crash_typ: {crash_typ}"),
920+
}
921+
}
922+
703923
fn extend_path<T: AsRef<Path>>(parent: &Path, path: T) -> PathBuf {
704924
let mut parent = parent.to_path_buf();
705925
parent.push(path);

datadog-crashtracker/src/crash_info/errors_intake.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -378,9 +378,14 @@ pub struct ErrorsIntakeUploader {
378378
impl ErrorsIntakeUploader {
379379
pub fn new(
380380
_crashtracker_metadata: &Metadata,
381-
_endpoint: &Option<Endpoint>,
381+
endpoint: &Option<Endpoint>,
382382
) -> anyhow::Result<Self> {
383-
let cfg = ErrorsIntakeConfig::from_env();
383+
let mut cfg = ErrorsIntakeConfig::from_env();
384+
385+
if let Some(endpoint) = endpoint {
386+
cfg.set_endpoint(endpoint.clone())?;
387+
}
388+
384389
Ok(Self { cfg })
385390
}
386391

@@ -401,7 +406,6 @@ impl ErrorsIntakeUploader {
401406

402407
async fn send_payload(&self, payload: &ErrorsIntakePayload) -> anyhow::Result<()> {
403408
let Some(endpoint) = self.cfg.endpoint() else {
404-
// No endpoint configured - this is fine, errors intake is optional
405409
return Ok(());
406410
};
407411

0 commit comments

Comments
 (0)