|
| 1 | +// Copyright 2023-Present Datadog, Inc. https://www.datadoghq.com/ |
| 2 | +// SPDX-License-Identifier: Apache-2.0 |
| 3 | + |
| 4 | +#![cfg(windows)] |
| 5 | + |
| 6 | +use std::collections::HashMap; |
| 7 | +use std::io::{Read, Write}; |
| 8 | +use std::path::Path; |
| 9 | +use std::process; |
| 10 | +use std::{fs, path::PathBuf}; |
| 11 | + |
| 12 | +use anyhow::Context; |
| 13 | +use bin_tests::{build_artifacts, ArtifactType, ArtifactsBuild, BuildProfile}; |
| 14 | +use serde_json::Value; |
| 15 | + |
| 16 | +// This test is disabled for now on x86_64 musl and macos |
| 17 | +// It seems that on aarch64 musl, libc has CFI which allows |
| 18 | +// unwinding passed the signal frame. |
| 19 | +#[test] |
| 20 | +#[cfg_attr(miri, ignore)] |
| 21 | +fn test_crasht_tracking_validate_callstack() { |
| 22 | + test_crash_tracking_callstack() |
| 23 | +} |
| 24 | + |
| 25 | +fn test_crash_tracking_callstack() { |
| 26 | + let (_, crashtracker_receiver) = setup_crashtracking_crates(BuildProfile::Release); |
| 27 | + |
| 28 | + let crashing_app = ArtifactsBuild { |
| 29 | + name: "crashing_test_app".to_owned(), |
| 30 | + // compile in debug so we avoid inlining |
| 31 | + // and can check the callchain |
| 32 | + build_profile: BuildProfile::Debug, |
| 33 | + artifact_type: ArtifactType::Bin, |
| 34 | + triple_target: None, |
| 35 | + }; |
| 36 | + |
| 37 | + let fixtures = setup_test_fixtures(&[&crashtracker_receiver, &crashing_app]); |
| 38 | + |
| 39 | + let mut p = process::Command::new(&fixtures.artifacts[&crashing_app]) |
| 40 | + .arg(format!("file://{}", fixtures.crash_profile_path.display())) |
| 41 | + .arg(fixtures.artifacts[&crashtracker_receiver].as_os_str()) |
| 42 | + .arg(&fixtures.output_dir) |
| 43 | + .spawn() |
| 44 | + .unwrap(); |
| 45 | + |
| 46 | + let exit_status = bin_tests::timeit!("exit after signal", { |
| 47 | + eprintln!("Waiting for exit"); |
| 48 | + p.wait().unwrap() |
| 49 | + }); |
| 50 | + assert!(!exit_status.success()); |
| 51 | + |
| 52 | + let stderr_path = format!("{0}/out.stderr", fixtures.output_dir.display()); |
| 53 | + let stderr = fs::read(stderr_path) |
| 54 | + .context("reading crashtracker stderr") |
| 55 | + .unwrap(); |
| 56 | + let stdout_path = format!("{0}/out.stdout", fixtures.output_dir.display()); |
| 57 | + let stdout = fs::read(stdout_path) |
| 58 | + .context("reading crashtracker stdout") |
| 59 | + .unwrap(); |
| 60 | + let s = String::from_utf8(stderr); |
| 61 | + assert!( |
| 62 | + matches!( |
| 63 | + s.as_deref(), |
| 64 | + Ok("") | Ok("Failed to fully receive crash. Exit state was: StackTrace([])\n") |
| 65 | + | Ok("Failed to fully receive crash. Exit state was: InternalError(\"{\\\"ip\\\": \\\"\")\n"), |
| 66 | + ), |
| 67 | + "got {s:?}" |
| 68 | + ); |
| 69 | + assert_eq!(Ok(""), String::from_utf8(stdout).as_deref()); |
| 70 | + |
| 71 | + let crash_profile = fs::read(fixtures.crash_profile_path) |
| 72 | + .context("reading crashtracker profiling payload") |
| 73 | + .unwrap(); |
| 74 | + let crash_payload = serde_json::from_slice::<serde_json::Value>(&crash_profile) |
| 75 | + .context("deserializing crashtracker profiling payload to json") |
| 76 | + .unwrap(); |
| 77 | + |
| 78 | + // Note: in Release, we do not have the crate and module name prepended to the function name |
| 79 | + // Here we compile the crashing app in Debug. |
| 80 | + let mut expected_functions = Vec::new(); |
| 81 | + // It seems that on arm/arm64, fn3 is inlined in fn2, so not present. |
| 82 | + // Add fn3 only for x86_64 arch |
| 83 | + #[cfg(target_arch = "x86_64")] |
| 84 | + expected_functions.push("crashing_test_app::unix::fn3"); |
| 85 | + expected_functions.extend_from_slice(&[ |
| 86 | + "crashing_test_app::unix::fn2", |
| 87 | + "crashing_test_app::unix::fn1", |
| 88 | + "crashing_test_app::unix::main", |
| 89 | + "crashing_test_app::main", |
| 90 | + ]); |
| 91 | + |
| 92 | + let crashing_callstack = &crash_payload["error"]["stack"]["frames"]; |
| 93 | + assert!( |
| 94 | + crashing_callstack.as_array().unwrap().len() >= expected_functions.len(), |
| 95 | + "crashing thread callstacks does have less frames than expected. Current: {}, Expected: {}", |
| 96 | + crashing_callstack.as_array().unwrap().len(), |
| 97 | + expected_functions.len() |
| 98 | + ); |
| 99 | + |
| 100 | + let function_names: Vec<&str> = crashing_callstack |
| 101 | + .as_array() |
| 102 | + .unwrap() |
| 103 | + .iter() |
| 104 | + .map(|f| f["function"].as_str().unwrap_or("")) |
| 105 | + .collect(); |
| 106 | + |
| 107 | + for (expected, actual) in expected_functions.iter().zip(function_names.iter()) { |
| 108 | + assert_eq!(expected, actual); |
| 109 | + } |
| 110 | +} |
| 111 | + |
| 112 | +struct TestFixtures<'a> { |
| 113 | + tmpdir: tempfile::TempDir, |
| 114 | + crash_profile_path: PathBuf, |
| 115 | + crash_telemetry_path: PathBuf, |
| 116 | + output_dir: PathBuf, |
| 117 | + |
| 118 | + artifacts: HashMap<&'a ArtifactsBuild, PathBuf>, |
| 119 | +} |
| 120 | + |
| 121 | +fn setup_test_fixtures<'a>(crates: &[&'a ArtifactsBuild]) -> TestFixtures<'a> { |
| 122 | + let artifacts = build_artifacts(crates).unwrap(); |
| 123 | + |
| 124 | + let tmpdir = tempfile::TempDir::new().unwrap(); |
| 125 | + let dirpath = tmpdir.path(); |
| 126 | + TestFixtures { |
| 127 | + crash_profile_path: extend_path(dirpath, "crash"), |
| 128 | + crash_telemetry_path: extend_path(dirpath, "crash.telemetry"), |
| 129 | + output_dir: dirpath.to_path_buf(), |
| 130 | + |
| 131 | + artifacts, |
| 132 | + tmpdir, |
| 133 | + } |
| 134 | +} |
| 135 | + |
| 136 | +fn setup_crashtracking_crates( |
| 137 | + crash_tracking_receiver_profile: BuildProfile, |
| 138 | +) -> (ArtifactsBuild, ArtifactsBuild) { |
| 139 | + let crashtracker_bin = ArtifactsBuild { |
| 140 | + name: "crashtracker_bin_test".to_owned(), |
| 141 | + build_profile: crash_tracking_receiver_profile, |
| 142 | + artifact_type: ArtifactType::Bin, |
| 143 | + triple_target: None, |
| 144 | + }; |
| 145 | + let crashtracker_receiver = ArtifactsBuild { |
| 146 | + name: "crashtracker_receiver".to_owned(), |
| 147 | + build_profile: crash_tracking_receiver_profile, |
| 148 | + artifact_type: ArtifactType::Bin, |
| 149 | + triple_target: None, |
| 150 | + }; |
| 151 | + (crashtracker_bin, crashtracker_receiver) |
| 152 | +} |
| 153 | + |
| 154 | +fn extend_path<T: AsRef<Path>>(parent: &Path, path: T) -> PathBuf { |
| 155 | + let mut parent = parent.to_path_buf(); |
| 156 | + parent.push(path); |
| 157 | + parent |
| 158 | +} |
0 commit comments