diff --git a/Cargo.lock b/Cargo.lock index afa7da7f7..4047befff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1325,7 +1325,10 @@ version = "14.3.1" dependencies = [ "build_common", "data-pipeline", + "datadog-trace-utils", "ddcommon-ffi", + "httpmock", + "rmp-serde", "tinybytes", ] diff --git a/data-pipeline-ffi/Cargo.toml b/data-pipeline-ffi/Cargo.toml index f3fb0bc63..1b90e71e4 100644 --- a/data-pipeline-ffi/Cargo.toml +++ b/data-pipeline-ffi/Cargo.toml @@ -21,6 +21,11 @@ cbindgen = ["build_common/cbindgen", "ddcommon-ffi/cbindgen"] [build-dependencies] build_common = { path = "../build-common" } +[dev-dependencies] +httpmock = "0.7.0" +rmp-serde = "1.1.1" +datadog-trace-utils = { path = "../trace-utils" } + [dependencies] data-pipeline = { path = "../data-pipeline" } ddcommon-ffi = { path = "../ddcommon-ffi", default-features = false } diff --git a/data-pipeline-ffi/src/trace_exporter.rs b/data-pipeline-ffi/src/trace_exporter.rs index 357450dd9..1a8497bd2 100644 --- a/data-pipeline-ffi/src/trace_exporter.rs +++ b/data-pipeline-ffi/src/trace_exporter.rs @@ -318,6 +318,9 @@ mod tests { use super::*; use crate::error::ddog_trace_exporter_error_free; use crate::trace_exporter::AgentResponse; + use datadog_trace_utils::span_v04::Span; + use httpmock::prelude::*; + use httpmock::MockServer; use std::{borrow::Borrow, mem::MaybeUninit}; #[test] @@ -608,4 +611,121 @@ mod tests { assert_eq!(error.unwrap().code, ErrorCode::InvalidInput); } } + + #[test] + // Ignore because it seems, at least in the version we're currently using, miri can't emulate + // libc::socket function. + #[cfg_attr(miri, ignore)] + fn exporter_send_check_rate_test() { + unsafe { + let server = MockServer::start(); + + let _mock = server.mock(|when, then| { + when.method(POST) + .header("Content-type", "application/msgpack") + .path("/v0.4/traces"); + then.status(200).body( + r#"{ + "rate_by_service": { + "service:foo,env:staging": 1.0, + "service:,env:": 0.8 + } + }"#, + ); + }); + + let cfg = TraceExporterConfig { + url: Some(server.url("/")), + tracer_version: Some("0.1".to_string()), + language: Some("lang".to_string()), + language_version: Some("0.1".to_string()), + language_interpreter: Some("interpreter".to_string()), + hostname: Some("hostname".to_string()), + env: Some("env-test".to_string()), + version: Some("1.0".to_string()), + service: Some("test-service".to_string()), + input_format: TraceExporterInputFormat::V04, + output_format: TraceExporterOutputFormat::V04, + compute_stats: false, + }; + + let mut ptr: MaybeUninit> = MaybeUninit::uninit(); + let mut ret = + ddog_trace_exporter_new(NonNull::new_unchecked(&mut ptr).cast(), Some(&cfg)); + + let exporter = ptr.assume_init(); + + assert_eq!(ret, None); + + let data = rmp_serde::to_vec_named::>>(&vec![vec![]]).unwrap(); + let traces = ByteSlice::new(&data); + let mut response = AgentResponse { rate: 0.0 }; + + ret = ddog_trace_exporter_send(Some(exporter.as_ref()), traces, 0, Some(&mut response)); + assert_eq!(ret, None); + assert_eq!(response.rate, 0.8); + + ddog_trace_exporter_free(exporter); + } + } + + #[test] + // Ignore because it seems, at least in the version we're currently using, miri can't emulate + // libc::socket function. + #[cfg_attr(miri, ignore)] + fn exporter_send_empty_array_test() { + // Test added due to ensure the exporter is able to send empty arrays because some tracers + // (.NET) ping the agent with the aforementioned data type. + unsafe { + let server = MockServer::start(); + + let mock_traces = server.mock(|when, then| { + when.method(POST) + .header("Content-type", "application/msgpack") + .path("/v0.4/traces"); + then.status(200).body( + r#"{ + "rate_by_service": { + "service:foo,env:staging": 1.0, + "service:,env:": 0.8 + } + }"#, + ); + }); + + let cfg = TraceExporterConfig { + url: Some(server.url("/")), + tracer_version: Some("0.1".to_string()), + language: Some("lang".to_string()), + language_version: Some("0.1".to_string()), + language_interpreter: Some("interpreter".to_string()), + hostname: Some("hostname".to_string()), + env: Some("env-test".to_string()), + version: Some("1.0".to_string()), + service: Some("test-service".to_string()), + input_format: TraceExporterInputFormat::V04, + output_format: TraceExporterOutputFormat::V04, + compute_stats: false, + }; + + let mut ptr: MaybeUninit> = MaybeUninit::uninit(); + let mut ret = + ddog_trace_exporter_new(NonNull::new_unchecked(&mut ptr).cast(), Some(&cfg)); + + let exporter = ptr.assume_init(); + + assert_eq!(ret, None); + + let data = vec![0x90]; + let traces = ByteSlice::new(&data); + let mut response = AgentResponse { rate: 0.0 }; + + ret = ddog_trace_exporter_send(Some(exporter.as_ref()), traces, 0, Some(&mut response)); + mock_traces.assert(); + assert_eq!(ret, None); + assert_eq!(response.rate, 0.8); + + ddog_trace_exporter_free(exporter); + } + } } diff --git a/data-pipeline/src/trace_exporter/mod.rs b/data-pipeline/src/trace_exporter/mod.rs index c4edc1a37..9e3cf83d2 100644 --- a/data-pipeline/src/trace_exporter/mod.rs +++ b/data-pipeline/src/trace_exporter/mod.rs @@ -590,13 +590,6 @@ impl TraceExporter { } }; - if traces.is_empty() { - error!("No traces deserialized from the request body."); - return Err(TraceExporterError::Io(std::io::Error::from( - std::io::ErrorKind::InvalidInput, - ))); - } - let num_traces = traces.len(); self.emit_metric( @@ -1531,6 +1524,41 @@ mod tests { assert_eq!(result, AgentResponse::from(0.8)); } + #[test] + #[cfg_attr(miri, ignore)] + fn agent_response_empty_array() { + let server = MockServer::start(); + let _agent = server.mock(|_, then| { + then.status(200) + .header("content-type", "application/json") + .body( + r#"{ + "rate_by_service": { + "service:foo,env:staging": 1.0, + "service:,env:": 0.8 + } + }"#, + ); + }); + + let exporter = TraceExporterBuilder::default() + .set_url(&server.url("/")) + .set_service("foo") + .set_env("foo-env") + .set_tracer_version("v0.1") + .set_language("nodejs") + .set_language_version("1.0") + .set_language_interpreter("v8") + .build() + .unwrap(); + + let traces = vec![0x90]; + let bytes = tinybytes::Bytes::from(traces); + let result = exporter.send(bytes, 1).unwrap(); + + assert_eq!(result, AgentResponse::from(0.8)); + } + #[test] #[cfg_attr(miri, ignore)] fn builder_error() { diff --git a/ddcommon/src/entity_id/mod.rs b/ddcommon/src/entity_id/mod.rs index 5e5dea719..743b45f88 100644 --- a/ddcommon/src/entity_id/mod.rs +++ b/ddcommon/src/entity_id/mod.rs @@ -92,23 +92,3 @@ pub fn get_external_env() -> Option<&'static str> { } DD_EXTERNAL_ENV.as_deref() } - -/// Set the path to cgroup file to mock it during tests -/// # Safety -/// Must not be called in multi-threaded contexts -pub unsafe fn set_cgroup_file(_file: String) { - #[cfg(unix)] - { - unix::set_cgroup_file(_file) - } -} - -/// Set cgroup mount path to mock during tests -/// # Safety -/// Must not be called in multi-threaded contexts -pub unsafe fn set(_path: String) { - #[cfg(unix)] - { - unix::set_cgroup_mount_path(_path) - } -} diff --git a/ddcommon/src/entity_id/unix/mod.rs b/ddcommon/src/entity_id/unix/mod.rs index 7e1e687e4..0973480a9 100644 --- a/ddcommon/src/entity_id/unix/mod.rs +++ b/ddcommon/src/entity_id/unix/mod.rs @@ -15,11 +15,6 @@ const DEFAULT_CGROUP_MOUNT_PATH: &str = "/sys/fs/cgroup"; /// the base controller used to identify the cgroup v1 mount point in the cgroupMounts map. const CGROUP_V1_BASE_CONTROLLER: &str = "memory"; -/// stores overridable cgroup path - used in end-to-end testing to "stub" cgroup values -static mut TESTING_CGROUP_PATH: Option = None; -/// stores overridable cgroup mount path -static mut TESTING_CGROUP_MOUNT_PATH: Option = None; - #[derive(Debug, Clone, PartialEq)] pub enum CgroupFileParsingError { ContainerIdNotFound, @@ -58,37 +53,11 @@ fn compute_entity_id( } fn get_cgroup_path() -> &'static str { - // Safety: we assume set_cgroup_file is not called when it shouldn't - #[allow(static_mut_refs)] - unsafe { - TESTING_CGROUP_PATH - .as_deref() - .unwrap_or(DEFAULT_CGROUP_PATH) - } + DEFAULT_CGROUP_PATH } fn get_cgroup_mount_path() -> &'static str { - // Safety: we assume set_cgroup_file is not called when it shouldn't - #[allow(static_mut_refs)] - unsafe { - TESTING_CGROUP_MOUNT_PATH - .as_deref() - .unwrap_or(DEFAULT_CGROUP_MOUNT_PATH) - } -} - -/// Set the path to cgroup file to mock it during tests -/// # Safety -/// Must not be called in multi-threaded contexts -pub unsafe fn set_cgroup_file(file: String) { - TESTING_CGROUP_PATH = Some(file) -} - -/// Set cgroup mount path to mock during tests -/// # Safety -/// Must not be called in multi-threaded contexts -pub unsafe fn set_cgroup_mount_path(path: String) { - TESTING_CGROUP_MOUNT_PATH = Some(path) + DEFAULT_CGROUP_MOUNT_PATH } /// Returns the `container_id` if available in the cgroup file, otherwise returns `None` diff --git a/tinybytes/src/bytes_string.rs b/tinybytes/src/bytes_string.rs index d4a57324e..0fe0d22eb 100644 --- a/tinybytes/src/bytes_string.rs +++ b/tinybytes/src/bytes_string.rs @@ -225,14 +225,14 @@ mod tests { } #[test] - fn from_string() { + fn test_from_string() { let string = String::from("hello"); let bytes_string = BytesString::from(string); assert_eq!(bytes_string.as_str(), "hello") } #[test] - fn from_static_str() { + fn test_from_static_str() { let static_str = "hello"; let bytes_string = BytesString::from(static_str); assert_eq!(bytes_string.as_str(), "hello") @@ -245,13 +245,13 @@ mod tests { } #[test] - fn hash() { + fn test_hash() { let bytes_string = BytesString::from_slice(b"test hash").unwrap(); assert_eq!(calculate_hash(&bytes_string), calculate_hash(&"test hash")); } #[test] - fn copy_to_string() { + fn test_copy_to_string() { let bytes_string = BytesString::from("hello"); assert_eq!(bytes_string.copy_to_string(), "hello") }