Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "gitsubmodule"
directory: "/"
schedule:
interval: "weekly"
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
path = libdd-libunwind-sys/libunwind
url = https://github.com/DataDog/libunwind.git
branch = kevin/v1.8.1-custom-2
[submodule "datadog-ffe/ffe-system-test-data"]
path = datadog-ffe/ffe-system-test-data
url = git@github.com:DataDog/ffe-system-test-data.git
branch = leo.romanovsky/add-canonical-fixtures
4 changes: 2 additions & 2 deletions datadog-ffe/benches/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use datadog_ffe::rules_based::{
};

fn load_configuration_bytes() -> Vec<u8> {
fs::read("tests/data/flags-v1.json").expect("Failed to read flags-v1.json")
fs::read("ffe-system-test-data/ufc-config.json").expect("Failed to read ufc-config.json")
}

#[derive(Debug, Serialize, Deserialize)]
Expand All @@ -33,7 +33,7 @@ struct TestResult {
fn load_test_cases() -> Vec<TestCase> {
let mut test_cases = Vec::new();

if let Ok(entries) = fs::read_dir("tests/data/tests") {
if let Ok(entries) = fs::read_dir("ffe-system-test-data/evaluation-cases") {
for entry in entries.flatten() {
if let Some(path_str) = entry.path().to_str() {
if path_str.ends_with(".json") {
Expand Down
1 change: 1 addition & 0 deletions datadog-ffe/ffe-system-test-data
Submodule ffe-system-test-data added at 956ffe
52 changes: 46 additions & 6 deletions datadog-ffe/src/rules_based/eval/eval_assignment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ impl Allocation {
};

// Determine the reason for assignment
let reason = if !self.rules.is_empty() || self.start_at.is_some() || self.end_at.is_some() {
let reason = if !self.rules.is_empty() {
AssignmentReason::TargetingMatch
} else if self.splits.len() == 1 && self.splits[0].shards.is_empty() {
AssignmentReason::Static
Expand Down Expand Up @@ -211,8 +211,9 @@ mod tests {
use serde::{Deserialize, Serialize};

use crate::rules_based::{
error::EvaluationError,
eval::get_assignment,
ufc::{AssignmentValue, UniversalFlagConfig},
ufc::{AssignmentReason, AssignmentValue, UniversalFlagConfig},
Attribute, Configuration, EvaluationContext, FlagType, Str,
};

Expand All @@ -230,20 +231,34 @@ mod tests {
#[derive(Debug, Serialize, Deserialize)]
struct TestResult {
value: Arc<serde_json::value::RawValue>,
#[serde(default)]
reason: Option<String>,
}

/// Known reason overrides where Rust's shard optimization produces a different
/// (but equally valid) reason than the canonical Go-derived fixtures.
///
/// Rust collapses insignificant shards (single split covering 100% of traffic)
/// to STATIC, whereas Go reports SPLIT. Both are correct interpretations.
/// Key: (flag, targeting_key) -> expected_reason for Rust.
fn known_reason_overrides() -> HashMap<(&'static str, &'static str), &'static str> {
HashMap::from([(("empty_string_flag", "bob"), "STATIC")])
}

#[test]
#[cfg_attr(miri, ignore)] // this test is way too slow on miri
fn evaluation_sdk_test_data() {
let _ = env_logger::builder().is_test(true).try_init();

let config =
UniversalFlagConfig::from_json(std::fs::read("tests/data/flags-v1.json").unwrap())
.unwrap();
let config = UniversalFlagConfig::from_json(
std::fs::read("ffe-system-test-data/ufc-config.json").unwrap(),
)
.unwrap();
let config = Configuration::from_server_response(config);
let now = Utc::now();
let reason_overrides = known_reason_overrides();

for entry in fs::read_dir("tests/data/tests/").unwrap() {
for entry in fs::read_dir("ffe-system-test-data/evaluation-cases/").unwrap() {
let entry = entry.unwrap();
println!("Processing test file: {:?}", entry.path());

Expand Down Expand Up @@ -278,6 +293,31 @@ mod tests {
.unwrap();

assert_eq!(result_assingment, &expected_assignment);

if let Some(expected_reason) = &test_case.result.reason {
let actual_reason = match &result {
Ok(assignment) => match assignment.reason {
AssignmentReason::TargetingMatch => "TARGETING_MATCH",
AssignmentReason::Split => "SPLIT",
AssignmentReason::Static => "STATIC",
},
Err(EvaluationError::FlagDisabled) => "DISABLED",
Err(_) => "DEFAULT",
};
let tk = subject.targeting_key().map(|s| s.as_ref()).unwrap_or("");
let effective_expected = reason_overrides
.get(&(test_case.flag.as_str(), tk))
.copied()
.unwrap_or(expected_reason.as_str());
assert_eq!(
actual_reason,
effective_expected,
"reason mismatch for flag '{}' targeting_key '{:?}'",
test_case.flag,
subject.targeting_key()
);
}

println!("ok");
}
}
Expand Down
2 changes: 1 addition & 1 deletion datadog-ffe/src/rules_based/ufc/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ mod tests {
#[test]
#[cfg_attr(miri, ignore)] // this test is way too slow on miri
fn parse_flags_v1() {
let json_content = std::fs::read_to_string("tests/data/flags-v1.json").unwrap();
let json_content = std::fs::read_to_string("ffe-system-test-data/ufc-config.json").unwrap();
let ufc: UniversalFlagConfigWire = serde_json::from_str(&json_content).unwrap();

let failures = ufc
Expand Down
Loading
Loading