use asupersync::observability::otel::span_semantics::{
AttributeValue, SpanConformanceConfig, TestSpan,
};
use opentelemetry::trace::SpanKind;
use opentelemetry_proto::tonic::common::v1::{AnyValue, any_value::Value as ProtoValue};
struct AttributeTestCase {
name: String,
key: &'static str,
our_value: AttributeValue,
expected_proto_value: ProtoValue,
}
fn main() {
println!("π OTLP Span Attributes Conformance Test");
println!("Verifying int/float/bool/string serialization matches protobuf spec exactly");
let test_cases = vec![
AttributeTestCase {
name: String::from("string_basic"),
key: "service.name",
our_value: AttributeValue::String("test-service".to_string()),
expected_proto_value: ProtoValue::StringValue("test-service".to_string()),
},
AttributeTestCase {
name: String::from("string_empty"),
key: "empty.string",
our_value: AttributeValue::String("".to_string()),
expected_proto_value: ProtoValue::StringValue("".to_string()),
},
AttributeTestCase {
name: String::from("string_unicode"),
key: "unicode.test",
our_value: AttributeValue::String("Hello δΈη π".to_string()),
expected_proto_value: ProtoValue::StringValue("Hello δΈη π".to_string()),
},
AttributeTestCase {
name: String::from("int_zero"),
key: "count.zero",
our_value: AttributeValue::Int(0),
expected_proto_value: ProtoValue::IntValue(0),
},
AttributeTestCase {
name: String::from("int_positive"),
key: "count.positive",
our_value: AttributeValue::Int(42),
expected_proto_value: ProtoValue::IntValue(42),
},
AttributeTestCase {
name: String::from("int_negative"),
key: "count.negative",
our_value: AttributeValue::Int(-123),
expected_proto_value: ProtoValue::IntValue(-123),
},
AttributeTestCase {
name: String::from("int_max"),
key: "count.max",
our_value: AttributeValue::Int(i64::MAX),
expected_proto_value: ProtoValue::IntValue(i64::MAX),
},
AttributeTestCase {
name: String::from("int_min"),
key: "count.min",
our_value: AttributeValue::Int(i64::MIN),
expected_proto_value: ProtoValue::IntValue(i64::MIN),
},
AttributeTestCase {
name: String::from("float_zero"),
key: "latency.zero",
our_value: AttributeValue::Float(0.0),
expected_proto_value: ProtoValue::DoubleValue(0.0),
},
AttributeTestCase {
name: String::from("float_positive"),
key: "latency.ms",
our_value: AttributeValue::Float(123.456),
expected_proto_value: ProtoValue::DoubleValue(123.456),
},
AttributeTestCase {
name: String::from("float_negative"),
key: "temperature",
our_value: AttributeValue::Float(-273.15),
expected_proto_value: ProtoValue::DoubleValue(-273.15),
},
AttributeTestCase {
name: String::from("float_pi"),
key: "math.pi",
our_value: AttributeValue::Float(std::f64::consts::PI),
expected_proto_value: ProtoValue::DoubleValue(std::f64::consts::PI),
},
AttributeTestCase {
name: String::from("float_infinity"),
key: "value.infinity",
our_value: AttributeValue::Float(f64::INFINITY),
expected_proto_value: ProtoValue::DoubleValue(f64::INFINITY),
},
AttributeTestCase {
name: String::from("bool_true"),
key: "is.enabled",
our_value: AttributeValue::Bool(true),
expected_proto_value: ProtoValue::BoolValue(true),
},
AttributeTestCase {
name: String::from("bool_false"),
key: "is.disabled",
our_value: AttributeValue::Bool(false),
expected_proto_value: ProtoValue::BoolValue(false),
},
];
println!(
"π Running {} attribute conformance tests",
test_cases.len()
);
let config = SpanConformanceConfig::default();
let mut failed_tests = Vec::new();
for test_case in &test_cases {
println!(
" Testing {}: {} = {:?}",
test_case.name, test_case.key, test_case.our_value
);
let mut span = TestSpan::new_with_config("test_span", SpanKind::Internal, &config);
span.set_attribute_value(test_case.key, test_case.our_value.clone());
let otlp_attributes = span.to_otlp_attributes();
assert_eq!(otlp_attributes.len(), 1, "Expected exactly one attribute");
let attr = &otlp_attributes[0];
assert_eq!(attr.key, test_case.key, "Attribute key mismatch");
let actual_value = attr.value.as_ref().expect("Attribute should have value");
let actual_proto_value = actual_value
.value
.as_ref()
.expect("AnyValue should have value");
if !proto_values_equal(actual_proto_value, &test_case.expected_proto_value) {
failed_tests.push((
test_case.name.clone(),
format!(
"Expected {:?}, got {:?}",
test_case.expected_proto_value, actual_proto_value
),
));
} else {
println!(" β
{}", test_case.name);
}
}
println!("\nπ Testing array attribute types");
test_array_attributes(&config, &mut failed_tests);
println!("\nπ Testing edge cases");
test_edge_cases(&config, &mut failed_tests);
println!("\nπ Testing reference implementation conformance");
test_reference_conformance(&mut failed_tests);
println!("\nπ Conformance Test Results");
if failed_tests.is_empty() {
println!("β
ALL TESTS PASSED - OTLP span attribute serialization is conformant");
println!("π― int/float/bool/string types serialize correctly per protobuf specification");
} else {
println!("β {} TESTS FAILED:", failed_tests.len());
for (test_name, error) in &failed_tests {
println!(" {} - {}", test_name, error);
}
std::process::exit(1);
}
}
fn test_array_attributes(config: &SpanConformanceConfig, failed_tests: &mut Vec<(String, String)>) {
let array_test_cases = vec![
(
"string_array",
"tags",
AttributeValue::StringArray(vec!["tag1".to_string(), "tag2".to_string()]),
),
(
"int_array",
"ports",
AttributeValue::IntArray(vec![80, 443, 8080]),
),
(
"float_array",
"coordinates",
AttributeValue::FloatArray(vec![1.23, 4.56, 7.89]),
),
(
"bool_array",
"flags",
AttributeValue::BoolArray(vec![true, false, true]),
),
];
for (name, key, value) in array_test_cases {
println!(" Testing {}: {} = {:?}", name, key, value);
let mut span = TestSpan::new_with_config("test_span", SpanKind::Internal, config);
span.set_attribute_value(key, value.clone());
let otlp_attributes = span.to_otlp_attributes();
if otlp_attributes.len() != 1 {
failed_tests.push((
name.to_string(),
"Expected exactly one attribute".to_string(),
));
continue;
}
let attr = &otlp_attributes[0];
if let Some(AnyValue {
value: Some(ProtoValue::ArrayValue(array)),
}) = &attr.value
{
match &value {
AttributeValue::StringArray(expected) => {
if array.values.len() != expected.len() {
failed_tests.push((name.to_string(), "Array length mismatch".to_string()));
continue;
}
for (i, (actual, expected)) in
array.values.iter().zip(expected.iter()).enumerate()
{
if let Some(ProtoValue::StringValue(actual_str)) = &actual.value {
if actual_str != expected {
failed_tests.push((
name.to_string(),
format!(
"Array element {} mismatch: expected {}, got {}",
i, expected, actual_str
),
));
}
} else {
failed_tests.push((
name.to_string(),
format!("Array element {} not a string", i),
));
}
}
}
AttributeValue::IntArray(expected) => {
for (i, (actual, expected)) in
array.values.iter().zip(expected.iter()).enumerate()
{
if let Some(ProtoValue::IntValue(actual_int)) = &actual.value {
if actual_int != expected {
failed_tests.push((
name.to_string(),
format!(
"Array element {} mismatch: expected {}, got {}",
i, expected, actual_int
),
));
}
} else {
failed_tests.push((
name.to_string(),
format!("Array element {} not an int", i),
));
}
}
}
AttributeValue::FloatArray(expected) => {
for (i, (actual, expected)) in
array.values.iter().zip(expected.iter()).enumerate()
{
if let Some(ProtoValue::DoubleValue(actual_float)) = &actual.value {
if (actual_float - expected).abs() > f64::EPSILON {
failed_tests.push((
name.to_string(),
format!(
"Array element {} mismatch: expected {}, got {}",
i, expected, actual_float
),
));
}
} else {
failed_tests.push((
name.to_string(),
format!("Array element {} not a double", i),
));
}
}
}
AttributeValue::BoolArray(expected) => {
for (i, (actual, expected)) in
array.values.iter().zip(expected.iter()).enumerate()
{
if let Some(ProtoValue::BoolValue(actual_bool)) = &actual.value {
if actual_bool != expected {
failed_tests.push((
name.to_string(),
format!(
"Array element {} mismatch: expected {}, got {}",
i, expected, actual_bool
),
));
}
} else {
failed_tests.push((
name.to_string(),
format!("Array element {} not a bool", i),
));
}
}
}
_ => {
failed_tests.push((name.to_string(), "Unexpected array type".to_string()));
}
}
} else {
failed_tests.push((name.to_string(), "Expected array value".to_string()));
}
if !failed_tests.iter().any(|(test_name, _)| test_name == name) {
println!(" β
{}", name);
}
}
}
fn test_edge_cases(config: &SpanConformanceConfig, failed_tests: &mut Vec<(String, String)>) {
let mut span = TestSpan::new_with_config("test_span", SpanKind::Internal, config);
span.set_float_attribute("nan_value", f64::NAN);
let otlp_attributes = span.to_otlp_attributes();
if let Some(attr) = otlp_attributes.first() {
if let Some(AnyValue {
value: Some(ProtoValue::DoubleValue(val)),
}) = &attr.value
{
if !val.is_nan() {
failed_tests.push(("float_nan".to_string(), "NaN not preserved".to_string()));
} else {
println!(" β
float_nan");
}
} else {
failed_tests.push(("float_nan".to_string(), "Expected double value".to_string()));
}
}
let mut span2 = TestSpan::new_with_config("test_span", SpanKind::Internal, config);
span2.set_float_attribute("neg_infinity", f64::NEG_INFINITY);
let otlp_attributes2 = span2.to_otlp_attributes();
if let Some(attr) = otlp_attributes2.first()
&& let Some(AnyValue {
value: Some(ProtoValue::DoubleValue(val)),
}) = &attr.value
{
if val != &f64::NEG_INFINITY {
failed_tests.push((
"float_neg_infinity".to_string(),
"Negative infinity not preserved".to_string(),
));
} else {
println!(" β
float_neg_infinity");
}
}
}
fn test_reference_conformance(failed_tests: &mut Vec<(String, String)>) {
let config = SpanConformanceConfig::default();
let mut span1 = TestSpan::new_with_config("test_span", SpanKind::Internal, &config);
span1.set_attribute("service.name", "test");
span1.set_int_attribute("count", 42);
span1.set_float_attribute("latency", 1.23);
span1.set_bool_attribute("enabled", true);
let mut span2 = TestSpan::new_with_config("test_span", SpanKind::Internal, &config);
span2.set_attribute("service.name", "test");
span2.set_int_attribute("count", 42);
span2.set_float_attribute("latency", 1.23);
span2.set_bool_attribute("enabled", true);
let attrs1 = span1.to_otlp_attributes();
let attrs2 = span2.to_otlp_attributes();
if attrs1.len() != attrs2.len() {
failed_tests.push((
"deterministic_serialization".to_string(),
"Attribute count differs between identical spans".to_string(),
));
return;
}
for (attr1, attr2) in attrs1.iter().zip(attrs2.iter()) {
if attr1.key != attr2.key {
failed_tests.push((
"deterministic_serialization".to_string(),
"Attribute key ordering differs".to_string(),
));
return;
}
if attr1.value != attr2.value {
failed_tests.push((
"deterministic_serialization".to_string(),
"Attribute value differs for identical input".to_string(),
));
return;
}
}
println!(" β
deterministic_serialization");
}
fn proto_values_equal(a: &ProtoValue, b: &ProtoValue) -> bool {
match (a, b) {
(ProtoValue::StringValue(a), ProtoValue::StringValue(b)) => a == b,
(ProtoValue::IntValue(a), ProtoValue::IntValue(b)) => a == b,
(ProtoValue::BoolValue(a), ProtoValue::BoolValue(b)) => a == b,
(ProtoValue::DoubleValue(a), ProtoValue::DoubleValue(b)) => {
if a.is_nan() && b.is_nan() {
true
} else {
(a - b).abs() < f64::EPSILON
}
}
_ => false,
}
}