Skip to content

Commit c65c6cf

Browse files
committed
chore(config): create a replacement for config smoke tests
Add a generated config smoke test in the agent-data-plane-config crate that mostly replicates the functionality of config_registry smoke tests (which have to be deleted one-by-one). Each value that is declared in the overlay as MIGRATED_TO_CONFIG_SYSTEM will run through a config system smoke test that asserts setting the key to a non-default value results in a non-default SalukiConfiguration.
1 parent 6da287e commit c65c6cf

5 files changed

Lines changed: 173 additions & 0 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/agent-data-plane-config-system/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ saluki-error = { workspace = true }
1919
serde_json = { workspace = true }
2020
stringtheory = { workspace = true }
2121

22+
[build-dependencies]
23+
datadog-agent-config-overlay-model = { workspace = true }
24+
indexmap = { workspace = true }
25+
2226
[dev-dependencies]
27+
agent-data-plane-config = { workspace = true }
2328
figment = { workspace = true }
29+
saluki-config-tools = { workspace = true }
30+
serde_json = { workspace = true }
2431
tokio = { workspace = true, features = ["macros", "rt"] }
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
use std::fmt::Write as _;
2+
use std::path::PathBuf;
3+
4+
use datadog_agent_config_overlay_model::schema_gen::{self, FieldInfo, FieldType};
5+
use datadog_agent_config_overlay_model::smoke_test_support::ConfigurationStruct;
6+
use datadog_agent_config_overlay_model::{Files, KnownEntry, SchemaOverlay, TestSupport};
7+
use indexmap::IndexMap;
8+
9+
fn main() {
10+
let files = Files::default();
11+
12+
let schema_dir = files.schema.parent().expect("schema file must have a parent directory");
13+
println!("cargo:rerun-if-changed={}", schema_dir.display());
14+
println!("cargo:rerun-if-changed={}", files.overlay.display());
15+
println!("cargo:rerun-if-changed=build.rs");
16+
17+
let schema_path = files.schema.clone();
18+
let overlay = SchemaOverlay::load(files).unwrap_or_else(|e| panic!("{e}"));
19+
let schema_map = schema_gen::load_schema(&schema_path);
20+
21+
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
22+
generate_smoke_tests(&overlay, &schema_map, &out_dir);
23+
}
24+
25+
fn generate_smoke_tests(overlay: &SchemaOverlay, schema_map: &IndexMap<String, FieldInfo>, out_dir: &std::path::Path) {
26+
let mut keys: Vec<(&str, &TestSupport, &FieldInfo)> = Vec::new();
27+
28+
for (yaml_path, entry) in &overlay.inventory {
29+
let ts = match entry {
30+
KnownEntry::Full(f) => &f.test_support,
31+
KnownEntry::Partial(p) => &p.test_support,
32+
_ => continue,
33+
};
34+
if !ts.used_by.contains(&ConfigurationStruct::MigratedToConfigSystem) {
35+
continue;
36+
}
37+
let field_info = schema_map
38+
.get(yaml_path.as_str())
39+
.unwrap_or_else(|| panic!("MigratedToConfigSystem key '{}' not found in schema", yaml_path));
40+
keys.push((yaml_path.as_str(), ts, field_info));
41+
}
42+
43+
keys.sort_by_key(|(path, _, _)| *path);
44+
45+
let mut out = String::new();
46+
writeln!(out, "// @generated by build.rs from schema_overlay.yaml — DO NOT EDIT").unwrap();
47+
writeln!(out).unwrap();
48+
49+
for (yaml_path, ts, field_info) in &keys {
50+
let fn_name = yaml_path.replace('.', "_");
51+
let test_value = resolve_test_value(yaml_path, ts, field_info);
52+
53+
writeln!(out, "#[tokio::test]").unwrap();
54+
writeln!(out, "async fn smoke_{fn_name}() {{").unwrap();
55+
writeln!(out, " assert_key_propagates(\"{yaml_path}\", {test_value}).await;").unwrap();
56+
writeln!(out, "}}").unwrap();
57+
writeln!(out).unwrap();
58+
}
59+
60+
let path = out_dir.join("translation_smoke_tests.rs");
61+
std::fs::write(&path, out).unwrap_or_else(|e| panic!("cannot write {}: {}", path.display(), e));
62+
}
63+
64+
fn resolve_test_value(yaml_path: &str, ts: &TestSupport, field_info: &FieldInfo) -> String {
65+
if let Some(ref test_json) = ts.test_json {
66+
return format_json_literal(test_json);
67+
}
68+
69+
let effective_type = ts
70+
.value_type_override
71+
.as_ref()
72+
.map_or(&field_info.value_type, |vt| match vt {
73+
datadog_agent_config_overlay_model::ValueType::Boolean => &FieldType::Bool,
74+
datadog_agent_config_overlay_model::ValueType::Integer => &FieldType::Integer,
75+
datadog_agent_config_overlay_model::ValueType::Float => &FieldType::Float,
76+
datadog_agent_config_overlay_model::ValueType::String => &FieldType::String,
77+
datadog_agent_config_overlay_model::ValueType::StringList => &FieldType::StringList,
78+
});
79+
80+
match effective_type {
81+
FieldType::Bool => {
82+
let default_is_true = field_info
83+
.default
84+
.as_deref()
85+
.map(|d| d.trim() == "true")
86+
.unwrap_or(false);
87+
if default_is_true {
88+
"serde_json::json!(false)".to_string()
89+
} else {
90+
"serde_json::json!(true)".to_string()
91+
}
92+
}
93+
FieldType::Integer => "serde_json::json!(42i64)".to_string(),
94+
FieldType::Float => "serde_json::json!(1.5f64)".to_string(),
95+
FieldType::String => "serde_json::json!(\"http://smoke-test.example.com:3128\")".to_string(),
96+
FieldType::StringList => {
97+
"serde_json::json!([\"smoke-host-1.example.com\", \"smoke-host-2.example.com\"])".to_string()
98+
}
99+
FieldType::Unknown => {
100+
panic!("MigratedToConfigSystem key '{yaml_path}' has unknown type and no test_json override");
101+
}
102+
}
103+
}
104+
105+
fn format_json_literal(raw: &str) -> String {
106+
if raw.contains('"') {
107+
format!("serde_json::from_str::<serde_json::Value>(r#\"{raw}\"#).unwrap()")
108+
} else {
109+
format!("serde_json::json!({raw})")
110+
}
111+
}

lib/agent-data-plane-config-system/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,6 @@ mod system;
5353
pub(crate) mod translate;
5454

5555
pub use self::system::{ConfigurationSystem, ConfigurationSystemLoader};
56+
57+
#[cfg(test)]
58+
mod smoke_test;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//! Tests to verify that changes to keys in a `GenericConfiguration` reach `SalukiConfiguration`.
2+
//!
3+
//! Each test checks a single key by setting a non default value in a `GenericConfiguration` map
4+
//! then running that map through `DatadogConfiguration` deserialization and witness trait
5+
//! translation. The resulting `SalukiConfiguration` is checked against default construction to
6+
//! ensure that it's value has been perturbed. This ensures that none of the witness trait function
7+
//! implementations are inert.
8+
use saluki_config_tools::ConfigurationLoader;
9+
use serde_json::json;
10+
11+
use crate::system::ConfigurationSystem;
12+
13+
/// Create a `GenericConfiguration` from the root JSON value.
14+
async fn generic_from(value: serde_json::Value) -> saluki_config_tools::GenericConfiguration {
15+
ConfigurationLoader::default()
16+
.add_providers([figment::providers::Serialized::defaults(value)])
17+
.into_generic()
18+
.await
19+
.expect("build generic configuration")
20+
}
21+
22+
/// Drive deserialization and translation.
23+
async fn translate_from(value: serde_json::Value) -> agent_data_plane_config::SalukiConfiguration {
24+
let config = generic_from(value).await;
25+
let system = ConfigurationSystem::load(config)
26+
.start()
27+
.await
28+
.expect("translation must succeed");
29+
system.saluki()
30+
}
31+
32+
/// Check that the `SalukiConfiguration` object is not default constructed after deserialization
33+
/// and translation.
34+
async fn assert_key_propagates(yaml_path: &str, test_value: serde_json::Value) {
35+
let default = translate_from(json!({})).await;
36+
37+
// Set up a JSON object holding the test value at our key-path.
38+
let mut map = json!({});
39+
saluki_config_tools::upsert(&mut map, yaml_path, test_value);
40+
let with_value = translate_from(map).await;
41+
42+
assert_ne!(
43+
default, with_value,
44+
"key '{}' did not change SalukiConfiguration — \
45+
is the test value the same as the default, or is translation not wired?",
46+
yaml_path,
47+
);
48+
}
49+
50+
include!(concat!(env!("OUT_DIR"), "/translation_smoke_tests.rs"));

0 commit comments

Comments
 (0)