1+
12"""
23Simple trace assertion - just check that expected spans exist with required attributes.
34Much simpler than the tree-based approach.
45"""
5-
66import json
77from typing import List , Dict , Any
88
9-
109def load_traces (traces_file : str ) -> List [Dict [str , Any ]]:
1110 """Load traces from a JSONL file."""
1211 traces = []
@@ -16,27 +15,32 @@ def load_traces(traces_file: str) -> List[Dict[str, Any]]:
1615 traces .append (json .loads (line ))
1716 return traces
1817
19-
2018def load_expected_traces (expected_file : str ) -> List [Dict [str , Any ]]:
2119 """Load expected trace definitions from a JSON file."""
2220 with open (expected_file , 'r' , encoding = 'utf-8' ) as f :
2321 data = json .load (f )
2422 return data .get ('required_spans' , [])
2523
26-
2724def get_attributes (span : Dict [str , Any ]) -> Dict [str , Any ]:
28- """Parse attributes from a span."""
25+ """
26+ Parse attributes from a span.
27+ Supports both formats:
28+ - Old format: 'Attributes' as a JSON string
29+ - New format: 'attributes' as a dict
30+ """
31+ # New format: attributes is already a dict
32+ if 'attributes' in span and isinstance (span ['attributes' ], dict ):
33+ return span ['attributes' ]
34+ # Old format: Attributes is a JSON string
2935 attributes_str = span .get ('Attributes' , '{}' )
3036 try :
3137 return json .loads (attributes_str )
3238 except json .JSONDecodeError :
3339 return {}
3440
35-
3641def matches_value (expected_value : Any , actual_value : Any ) -> bool :
3742 """
3843 Check if an actual value matches the expected value.
39-
4044 Supports:
4145 - List of possible values: ["value1", "value2"]
4246 - Wildcard: "*" (any value accepted)
@@ -45,32 +49,38 @@ def matches_value(expected_value: Any, actual_value: Any) -> bool:
4549 # Wildcard - accept any value
4650 if expected_value == "*" :
4751 return True
48-
4952 # List of possible values
5053 if isinstance (expected_value , list ):
5154 return actual_value in expected_value
52-
5355 # Exact match
5456 return expected_value == actual_value
5557
56-
5758def matches_expected (span : Dict [str , Any ], expected : Dict [str , Any ]) -> bool :
58- """Check if a span matches the expected definition."""
59+ """
60+ Check if a span matches the expected definition.
61+ Supports both formats:
62+ - Old format: 'Name', 'SpanType' fields
63+ - New format: 'name', 'attributes.span_type' fields
64+ """
5965 # Check name - can be a string or list of possible names
6066 expected_name = expected .get ('name' )
61- actual_name = span . get ( ' Name' )
62-
67+ # Support both old format ( Name) and new format (name )
68+ actual_name = span . get ( 'name' ) or span . get ( 'Name' )
6369 if isinstance (expected_name , list ):
6470 if actual_name not in expected_name :
6571 return False
6672 elif expected_name != actual_name :
6773 return False
68-
6974 # Check span type if specified
7075 if 'span_type' in expected :
71- if span .get ('SpanType' ) != expected ['span_type' ]:
76+ # Old format: SpanType field
77+ # New format: attributes.span_type field
78+ actual_span_type = span .get ('SpanType' )
79+ if not actual_span_type :
80+ actual_attrs = get_attributes (span )
81+ actual_span_type = actual_attrs .get ('span_type' )
82+ if actual_span_type != expected ['span_type' ]:
7283 return False
73-
7484 # Check attributes if specified
7585 if 'attributes' in expected :
7686 actual_attrs = get_attributes (span )
@@ -80,29 +90,22 @@ def matches_expected(span: Dict[str, Any], expected: Dict[str, Any]) -> bool:
8090 # Use flexible value matching
8191 if not matches_value (expected_value , actual_attrs [key ]):
8292 return False
83-
8493 return True
8594
86-
8795def assert_traces (traces_file : str , expected_file : str ) -> None :
8896 """
8997 Assert that all expected traces exist in the traces file.
90-
9198 Args:
9299 traces_file: Path to the traces.jsonl file
93100 expected_file: Path to the expected_traces.json file
94-
95101 Raises:
96102 AssertionError: If any expected trace is not found
97103 """
98104 traces = load_traces (traces_file )
99105 expected_spans = load_expected_traces (expected_file )
100-
101106 print (f"Loaded { len (traces )} traces from { traces_file } " )
102107 print (f"Checking { len (expected_spans )} expected spans..." )
103-
104108 missing_spans = []
105-
106109 for expected in expected_spans :
107110 # Find a matching span
108111 found = False
@@ -111,20 +114,12 @@ def assert_traces(traces_file: str, expected_file: str) -> None:
111114 found = True
112115 print (f"✓ Found span: { expected ['name' ]} " )
113116 break
114-
115117 if not found :
116118 missing_spans .append (expected ['name' ])
117119 print (f"✗ Missing span: { expected ['name' ]} " )
118-
119120 if missing_spans :
120121 raise AssertionError (
121122 f"Missing expected spans: { ', ' .join (missing_spans )} \n "
122123 f"Expected { len (expected_spans )} spans, found { len (expected_spans ) - len (missing_spans )} "
123124 )
124-
125125 print (f"\n ✓ All { len (expected_spans )} expected spans found!" )
126-
127-
128- if __name__ == "__main__" :
129- # Example usage
130- assert_traces (".uipath/traces.jsonl" , "expected_traces.json" )
0 commit comments