Skip to content

Commit b9c68ca

Browse files
authored
test(ffe): run sdk tests individually (#1273)
test(ffe): run sdk tests individually Co-authored-by: bjorn.antonsson <[email protected]>
1 parent a266728 commit b9c68ca

File tree

2 files changed

+161
-51
lines changed

2 files changed

+161
-51
lines changed

datadog-ffe/build.rs

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// Copyright 2025-Present Datadog, Inc. https://www.datadoghq.com/
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use std::fs;
5+
use std::io::Write;
6+
use std::path::Path;
7+
8+
fn main() {
9+
// Tell Cargo to rerun this build script if the test data directory changes
10+
println!("cargo:rerun-if-changed=tests/data/tests");
11+
12+
let test_dir = Path::new("tests/data/tests");
13+
let out_dir = std::env::var("OUT_DIR").unwrap();
14+
let dest_path = Path::new(&out_dir).join("sdk_tests.rs");
15+
16+
let mut file = fs::File::create(dest_path).unwrap();
17+
18+
// Read all JSON files in the test directory
19+
let mut test_files = Vec::new();
20+
if let Ok(entries) = fs::read_dir(test_dir) {
21+
for entry in entries.flatten() {
22+
let path = entry.path();
23+
if path.extension().and_then(|s| s.to_str()) == Some("json") {
24+
if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
25+
test_files.push(stem.to_string());
26+
}
27+
}
28+
}
29+
}
30+
31+
// Sort for consistent output
32+
test_files.sort();
33+
34+
// Generate the entire test code
35+
writeln!(
36+
file,
37+
"// This file is automatically generated by build.rs\n"
38+
)
39+
.unwrap();
40+
41+
// Helper function
42+
writeln!(file, "// Helper function to run a single test file").unwrap();
43+
writeln!(file, "#[allow(dead_code)]").unwrap();
44+
writeln!(
45+
file,
46+
"fn run_test_file(config: &Configuration, test_file_path: &str, now: chrono::DateTime<chrono::Utc>) {{"
47+
)
48+
.unwrap();
49+
writeln!(file, " let f = File::open(test_file_path)").unwrap();
50+
writeln!(
51+
file,
52+
" .unwrap_or_else(|e| panic!(\"Failed to open test file {{}}: {{}}\", test_file_path, e));"
53+
)
54+
.unwrap();
55+
writeln!(
56+
file,
57+
" let test_cases: Vec<TestCase> = serde_json::from_reader(f)"
58+
)
59+
.unwrap();
60+
writeln!(
61+
file,
62+
" .unwrap_or_else(|e| panic!(\"Failed to parse test file {{}}: {{}}\", test_file_path, e));"
63+
)
64+
.unwrap();
65+
writeln!(file).unwrap();
66+
writeln!(file, " for test_case in test_cases {{").unwrap();
67+
writeln!(file, " let default_assignment =").unwrap();
68+
writeln!(
69+
file,
70+
" AssignmentValue::from_wire(test_case.variation_type, test_case.default_value)"
71+
)
72+
.unwrap();
73+
writeln!(file, " .unwrap();").unwrap();
74+
writeln!(file).unwrap();
75+
writeln!(
76+
file,
77+
" let targeting_key = test_case.targeting_key.clone();"
78+
)
79+
.unwrap();
80+
writeln!(
81+
file,
82+
" let subject = EvaluationContext::new(test_case.targeting_key, test_case.attributes);"
83+
)
84+
.unwrap();
85+
writeln!(file, " let result = get_assignment(").unwrap();
86+
writeln!(file, " Some(config),").unwrap();
87+
writeln!(file, " &test_case.flag,").unwrap();
88+
writeln!(file, " &subject,").unwrap();
89+
writeln!(file, " Some(test_case.variation_type),").unwrap();
90+
writeln!(file, " now,").unwrap();
91+
writeln!(file, " )").unwrap();
92+
writeln!(file, " .unwrap_or(None);").unwrap();
93+
writeln!(file).unwrap();
94+
writeln!(file, " let result_assignment = result").unwrap();
95+
writeln!(file, " .as_ref()").unwrap();
96+
writeln!(file, " .map(|assignment| &assignment.value)").unwrap();
97+
writeln!(file, " .unwrap_or(&default_assignment);").unwrap();
98+
writeln!(file, " let expected_assignment =").unwrap();
99+
writeln!(
100+
file,
101+
" AssignmentValue::from_wire(test_case.variation_type, test_case.result.value)"
102+
)
103+
.unwrap();
104+
writeln!(file, " .unwrap();").unwrap();
105+
writeln!(file).unwrap();
106+
writeln!(file, " assert_eq!(").unwrap();
107+
writeln!(file, " result_assignment, &expected_assignment,").unwrap();
108+
writeln!(
109+
file,
110+
" \"Test case failed for subject {{:?}} in file {{}}\","
111+
)
112+
.unwrap();
113+
writeln!(file, " targeting_key, test_file_path").unwrap();
114+
writeln!(file, " );").unwrap();
115+
writeln!(file, " }}").unwrap();
116+
writeln!(file, "}}").unwrap();
117+
writeln!(file).unwrap();
118+
119+
// Generate individual test functions
120+
for test_file in &test_files {
121+
let test_name = format!("evaluation_sdk_{}", test_file.replace('-', "_"));
122+
123+
writeln!(file, "#[test]").unwrap();
124+
writeln!(file, "fn {}() {{", test_name).unwrap();
125+
writeln!(
126+
file,
127+
" let _ = env_logger::builder().is_test(true).try_init();"
128+
)
129+
.unwrap();
130+
writeln!(file).unwrap();
131+
writeln!(file, " let config = UniversalFlagConfig::from_json(").unwrap();
132+
writeln!(
133+
file,
134+
" std::fs::read(\"tests/data/flags-v1.json\").unwrap()"
135+
)
136+
.unwrap();
137+
writeln!(file, " )").unwrap();
138+
writeln!(file, " .unwrap();").unwrap();
139+
writeln!(
140+
file,
141+
" let config = Configuration::from_server_response(config);"
142+
)
143+
.unwrap();
144+
writeln!(file, " let now = Utc::now();").unwrap();
145+
writeln!(file).unwrap();
146+
writeln!(
147+
file,
148+
" let test_file_path = \"tests/data/tests/{}.json\";",
149+
test_file
150+
)
151+
.unwrap();
152+
writeln!(file, " run_test_file(&config, test_file_path, now);").unwrap();
153+
writeln!(file, "}}").unwrap();
154+
writeln!(file).unwrap();
155+
}
156+
}

datadog-ffe/src/rules_based/eval/eval_assignment.rs

Lines changed: 5 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -220,11 +220,7 @@ impl Shard {
220220

221221
#[cfg(test)]
222222
mod tests {
223-
use std::{
224-
collections::HashMap,
225-
fs::{self, File},
226-
sync::Arc,
227-
};
223+
use std::{collections::HashMap, fs::File, sync::Arc};
228224

229225
use chrono::Utc;
230226
use serde::{Deserialize, Serialize};
@@ -251,50 +247,8 @@ mod tests {
251247
value: serde_json::Value,
252248
}
253249

254-
#[test]
255-
fn evaluation_sdk_test_data() {
256-
let _ = env_logger::builder().is_test(true).try_init();
257-
258-
let config =
259-
UniversalFlagConfig::from_json(std::fs::read("tests/data/flags-v1.json").unwrap())
260-
.unwrap();
261-
let config = Configuration::from_server_response(config);
262-
let now = Utc::now();
263-
264-
for entry in fs::read_dir("tests/data/tests/").unwrap() {
265-
let entry = entry.unwrap();
266-
println!("Processing test file: {:?}", entry.path());
267-
268-
let f = File::open(entry.path()).unwrap();
269-
let test_cases: Vec<TestCase> = serde_json::from_reader(f).unwrap();
270-
271-
for test_case in test_cases {
272-
let default_assignment =
273-
AssignmentValue::from_wire(test_case.variation_type, test_case.default_value)
274-
.unwrap();
275-
276-
print!("test subject {:?} ... ", test_case.targeting_key);
277-
let subject = EvaluationContext::new(test_case.targeting_key, test_case.attributes);
278-
let result = get_assignment(
279-
Some(&config),
280-
&test_case.flag,
281-
&subject,
282-
Some(test_case.variation_type),
283-
now,
284-
)
285-
.unwrap_or(None);
286-
287-
let result_assingment = result
288-
.as_ref()
289-
.map(|assignment| &assignment.value)
290-
.unwrap_or(&default_assignment);
291-
let expected_assignment =
292-
AssignmentValue::from_wire(test_case.variation_type, test_case.result.value)
293-
.unwrap();
294-
295-
assert_eq!(result_assingment, &expected_assignment);
296-
println!("ok");
297-
}
298-
}
299-
}
250+
// Include the SDK tests generated by build.rs at compile time
251+
// The build script automatically discovers all test files in tests/data/tests/
252+
// and generates a separate test function for each one
253+
include!(concat!(env!("OUT_DIR"), "/sdk_tests.rs"));
300254
}

0 commit comments

Comments
 (0)