@@ -60,171 +60,187 @@ public function getActualStructure(): array
6060 */
6161 private function formatDiff (array $ expected , array $ actual ): string
6262 {
63- $ output = "\n\nExpected Trace Structure: \n" ;
64- $ output .= $ this ->formatExpectedStructure ($ expected );
63+ // First, convert the fluent assertion structures to a format suitable for diffing
64+ $ expectedStructure = $ this ->convertFluentStructureToArray ($ expected );
65+ $ actualStructure = $ this ->convertFluentStructureToArray ($ actual );
6566
66- $ output .= "\n\nActual Trace Structure: \n" ;
67- $ output .= $ this ->formatActualStructure ($ actual );
67+ // Generate a PHPUnit-style diff
68+ $ output = "\n\n--- Expected Trace Structure \n" ;
69+ $ output .= "+++ Actual Trace Structure \n" ;
70+ $ output .= "@@ @@ \n" ;
71+
72+ // Generate the diff for the root level
73+ $ output .= $ this ->generateArrayDiff ($ expectedStructure , $ actualStructure );
6874
6975 return $ output ;
7076 }
7177
7278 /**
73- * Format the expected structure.
79+ * Converts the fluent assertion structure to a format suitable for diffing .
7480 *
75- * @param array $expected The expected structure
76- * @param int $indent The indentation level
77- * @return string
81+ * @param array $structure The fluent assertion structure
82+ * @return array The converted structure
7883 */
79- private function formatExpectedStructure (array $ expected , int $ indent = 0 ): string
84+ private function convertFluentStructureToArray (array $ structure ): array
8085 {
81- $ output = '' ;
82- $ indentation = str_repeat (' ' , $ indent );
86+ $ result = [];
8387
84- foreach ($ expected as $ item ) {
88+ foreach ($ structure as $ item ) {
8589 if (!isset ($ item ['type ' ])) {
8690 continue ;
8791 }
8892
8993 switch ($ item ['type ' ]) {
9094 case 'root_span ' :
91- $ output .= $ indentation . "Root Span: \"{$ item ['name ' ]}\"\n" ;
95+ $ result [] = [
96+ 'name ' => $ item ['name ' ],
97+ 'type ' => 'root ' ,
98+ ];
9299
93100 break ;
94- case 'root_span_count ' :
95- $ output .= $ indentation . "Root Span Count: {$ item ['count ' ]}\n" ;
96101
97- break ;
98- case 'span_kind ' :
99- $ output .= $ indentation . 'Kind: ' . $ this ->formatKind ($ item ['kind ' ]) . "\n" ;
102+ case 'child_span ' :
103+ $ result [] = [
104+ 'name ' => $ item ['name ' ],
105+ 'type ' => 'child ' ,
106+ ];
100107
101108 break ;
102- case 'span_attribute ' :
103- $ output .= $ indentation . "Attribute \"{$ item ['key ' ]}\": " . $ this ->formatValue ($ item ['value ' ]) . "\n" ;
104109
105- break ;
106- case ' span_status ' :
107- $ output .= $ indentation . ' Status: Code= ' . $ this -> formatValue ( $ item ['code ' ]);
108- if ( isset ( $ item [ ' description ' ])) {
109- $ output .= " , Description= \"{ $ item [ ' description ' ]}\"" ;
110- }
111- $ output .= "\n" ;
110+ case ' missing_root_span ' :
111+ $ result [] = [
112+ ' name ' => $ item ['expected_name ' ],
113+ ' type ' => ' root ' ,
114+ ' missing ' => true ,
115+ ' available ' => $ item [ ' available_root_spans ' ] ?? [],
116+ ] ;
112117
113118 break ;
114- case 'span_event ' :
115- $ output .= $ indentation . "Event: \"{$ item ['name ' ]}\"\n" ;
116119
117- break ;
118- case 'span_event_attribute ' :
119- $ output .= $ indentation . "Event Attribute \"{$ item ['key ' ]}\": " . $ this ->formatValue ($ item ['value ' ]) . "\n" ;
120+ case 'missing_child_span ' :
121+ $ result [] = [
122+ 'name ' => $ item ['expected_name ' ],
123+ 'type ' => 'child ' ,
124+ 'missing ' => true ,
125+ 'available ' => $ item ['available_spans ' ] ?? [],
126+ ];
120127
121128 break ;
122- case 'child_span ' :
123- $ output .= $ indentation . "Child Span: \"{$ item ['name ' ]}\"\n" ;
124129
125- break ;
126- case 'child_span_count ' :
127- $ output .= $ indentation . "Child Span Count: {$ item ['count ' ]}\n" ;
130+ case 'root_span_count ' :
131+ $ result [] = [
132+ 'type ' => 'count ' ,
133+ 'count ' => $ item ['count ' ],
134+ 'spans ' => $ item ['spans ' ] ?? [],
135+ ];
128136
129137 break ;
138+
139+ // Add other types as needed
130140 }
131141 }
132142
133- return $ output ;
143+ return $ result ;
134144 }
135145
136146 /**
137- * Format the actual structure .
147+ * Recursively generates a diff between two arrays .
138148 *
139- * @param array $actual The actual structure
140- * @param int $indent The indentation level
141- * @return string
149+ * @param array $expected The expected array
150+ * @param array $actual The actual array
151+ * @param int $depth The current depth for indentation
152+ * @return string The formatted diff
142153 */
143- private function formatActualStructure (array $ actual , int $ indent = 0 ): string
154+ private function generateArrayDiff (array $ expected , array $ actual , int $ depth = 0 ): string
144155 {
145156 $ output = '' ;
146- $ indentation = str_repeat (' ' , $ indent );
147-
148- foreach ($ actual as $ item ) {
149- if (!isset ($ item ['type ' ])) {
150- continue ;
151- }
152-
153- switch ($ item ['type ' ]) {
154- case 'root_span ' :
155- $ output .= $ indentation . "Root Span: \"{$ item ['name ' ]}\"\n" ;
156-
157- break ;
158- case 'missing_root_span ' :
159- $ output .= $ indentation . "Missing Root Span: \"{$ item ['expected_name ' ]}\"\n" ;
160- if (!empty ($ item ['available_root_spans ' ])) {
161- $ output .= $ indentation . ' Available Root Spans: ' . implode (', ' , array_map (function ($ name ) {
162- return "\"$ name \"" ;
163- }, $ item ['available_root_spans ' ])) . "\n" ;
164- }
165-
166- break ;
167- case 'root_span_count ' :
168- $ output .= $ indentation . "Root Span Count: {$ item ['count ' ]}\n" ;
169- if (!empty ($ item ['spans ' ])) {
170- $ output .= $ indentation . ' Root Spans: ' . implode (', ' , array_map (function ($ name ) {
171- return "\"$ name \"" ;
172- }, $ item ['spans ' ])) . "\n" ;
157+ $ indent = str_repeat (' ' , $ depth );
158+
159+ // If arrays are indexed numerically, compare them as lists
160+ if ($ this ->isIndexedArray ($ expected ) && $ this ->isIndexedArray ($ actual )) {
161+ $ output .= $ indent . "Array ( \n" ;
162+
163+ // Find the maximum index to iterate through
164+ $ maxIndex = max (count ($ expected ), count ($ actual )) - 1 ;
165+
166+ for ($ i = 0 ; $ i <= $ maxIndex ; $ i ++) {
167+ if (isset ($ expected [$ i ]) && isset ($ actual [$ i ])) {
168+ // Both arrays have this index, compare the values
169+ if (is_array ($ expected [$ i ]) && is_array ($ actual [$ i ])) {
170+ // Both values are arrays, recursively compare
171+ $ output .= $ indent . " [ $ i] => Array ( \n" ;
172+ $ output .= $ this ->generateArrayDiff ($ expected [$ i ], $ actual [$ i ], $ depth + 2 );
173+ $ output .= $ indent . " ) \n" ;
174+ } elseif ($ expected [$ i ] === $ actual [$ i ]) {
175+ // Values are the same
176+ $ output .= $ indent . " [ $ i] => " . $ this ->formatValue ($ expected [$ i ]) . "\n" ;
177+ } else {
178+ // Values are different
179+ $ output .= $ indent . "- [ $ i] => " . $ this ->formatValue ($ expected [$ i ]) . "\n" ;
180+ $ output .= $ indent . "+ [ $ i] => " . $ this ->formatValue ($ actual [$ i ]) . "\n" ;
173181 }
182+ } elseif (isset ($ expected [$ i ])) {
183+ // Only in expected
184+ $ output .= $ indent . "- [ $ i] => " . $ this ->formatValue ($ expected [$ i ]) . "\n" ;
185+ } else {
186+ // Only in actual
187+ $ output .= $ indent . "+ [ $ i] => " . $ this ->formatValue ($ actual [$ i ]) . "\n" ;
188+ }
189+ }
174190
175- break ;
176- case 'span_kind ' :
177- $ output .= $ indentation . 'Kind: ' . $ this ->formatKind ($ item ['kind ' ]) . "\n" ;
178-
179- break ;
180- case 'span_attribute ' :
181- $ output .= $ indentation . "Attribute \"{$ item ['key ' ]}\": " . $ this ->formatValue ($ item ['value ' ]) . "\n" ;
182-
183- break ;
184- case 'missing_span_attribute ' :
185- $ output .= $ indentation . "Missing Attribute: \"{$ item ['key ' ]}\"\n" ;
186-
187- break ;
188- case 'span_status ' :
189- $ output .= $ indentation . 'Status: Code= ' . $ this ->formatValue ($ item ['code ' ]);
190- if (isset ($ item ['description ' ])) {
191- $ output .= ", Description= \"{$ item ['description ' ]}\"" ;
191+ $ output .= $ indent . ") \n" ;
192+ } else {
193+ // Compare as associative arrays
194+ $ output .= $ indent . "Array ( \n" ;
195+
196+ // Get all keys from both arrays
197+ $ allKeys = array_unique (array_merge (array_keys ($ expected ), array_keys ($ actual )));
198+ sort ($ allKeys );
199+
200+ foreach ($ allKeys as $ key ) {
201+ if (isset ($ expected [$ key ]) && isset ($ actual [$ key ])) {
202+ // Both arrays have this key, compare the values
203+ if (is_array ($ expected [$ key ]) && is_array ($ actual [$ key ])) {
204+ // Both values are arrays, recursively compare
205+ $ output .= $ indent . " [' $ key'] => Array ( \n" ;
206+ $ output .= $ this ->generateArrayDiff ($ expected [$ key ], $ actual [$ key ], $ depth + 2 );
207+ $ output .= $ indent . " ) \n" ;
208+ } elseif ($ expected [$ key ] === $ actual [$ key ]) {
209+ // Values are the same
210+ $ output .= $ indent . " [' $ key'] => " . $ this ->formatValue ($ expected [$ key ]) . "\n" ;
211+ } else {
212+ // Values are different
213+ $ output .= $ indent . "- [' $ key'] => " . $ this ->formatValue ($ expected [$ key ]) . "\n" ;
214+ $ output .= $ indent . "+ [' $ key'] => " . $ this ->formatValue ($ actual [$ key ]) . "\n" ;
192215 }
193- $ output .= "\n" ;
194-
195- break ;
196- case 'span_event ' :
197- $ output .= $ indentation . "Event: \"{$ item ['name ' ]}\"\n" ;
198-
199- break ;
200- case 'missing_span_event ' :
201- $ output .= $ indentation . "Missing Event: \"{$ item ['expected_name ' ]}\"\n" ;
202-
203- break ;
204- case 'span_event_attribute ' :
205- $ output .= $ indentation . "Event Attribute \"{$ item ['key ' ]}\": " . $ this ->formatValue ($ item ['value ' ]) . "\n" ;
206-
207- break ;
208- case 'child_span ' :
209- $ output .= $ indentation . "Child Span: \"{$ item ['name ' ]}\"\n" ;
210-
211- break ;
212- case 'missing_child_span ' :
213- $ output .= $ indentation . "Missing Child Span: \"{$ item ['expected_name ' ]}\"\n" ;
216+ } elseif (isset ($ expected [$ key ])) {
217+ // Only in expected
218+ $ output .= $ indent . "- [' $ key'] => " . $ this ->formatValue ($ expected [$ key ]) . "\n" ;
219+ } else {
220+ // Only in actual
221+ $ output .= $ indent . "+ [' $ key'] => " . $ this ->formatValue ($ actual [$ key ]) . "\n" ;
222+ }
223+ }
214224
215- break ;
216- case 'unexpected_child_span ' :
217- $ output .= $ indentation . "Unexpected Child Span: \"{$ item ['name ' ]}\"\n" ;
225+ $ output .= $ indent . ") \n" ;
226+ }
218227
219- break ;
220- case 'child_span_count ' :
221- $ output .= $ indentation . "Child Span Count: {$ item ['count ' ]}\n" ;
228+ return $ output ;
229+ }
222230
223- break ;
224- }
231+ /**
232+ * Checks if an array is indexed numerically (not associative).
233+ *
234+ * @param array $array The array to check
235+ * @return bool True if the array is indexed, false if it's associative
236+ */
237+ private function isIndexedArray (array $ array ): bool
238+ {
239+ if (empty ($ array )) {
240+ return true ;
225241 }
226242
227- return $ output ;
243+ return array_keys ( $ array ) === range ( 0 , count ( $ array ) - 1 ) ;
228244 }
229245
230246 /**
@@ -242,14 +258,32 @@ private function formatValue($value): string
242258 } elseif (null === $ value ) {
243259 return 'null ' ;
244260 } elseif (is_array ($ value )) {
261+ // Check if this array looks like a status
262+ if (isset ($ value ['code ' ]) || isset ($ value ['description ' ])) {
263+ return $ this ->formatStatus ($ value );
264+ }
265+
245266 $ json = json_encode ($ value );
246267
247268 return $ json === false ? '[unable to encode] ' : $ json ;
269+ } elseif (is_int ($ value ) && $ this ->isSpanKind ($ value )) {
270+ return $ this ->formatKind ($ value );
248271 }
249272
250273 return (string ) $ value ;
251274 }
252275
276+ /**
277+ * Checks if a value is a span kind.
278+ *
279+ * @param int $value The value to check
280+ * @return bool True if the value is a span kind
281+ */
282+ private function isSpanKind (int $ value ): bool
283+ {
284+ return $ value >= 0 && $ value <= 4 ;
285+ }
286+
253287 /**
254288 * Format a span kind for display.
255289 *
@@ -268,4 +302,34 @@ private function formatKind(int $kind): string
268302
269303 return $ kinds [$ kind ] ?? "UNKNOWN_KIND( $ kind) " ;
270304 }
305+
306+ /**
307+ * Format a span status for display.
308+ *
309+ * @param array $status The span status
310+ * @return string
311+ */
312+ private function formatStatus (array $ status ): string
313+ {
314+ $ output = 'Status: Code= ' ;
315+
316+ if (isset ($ status ['code ' ])) {
317+ $ statusCodes = [
318+ 0 => 'STATUS_UNSET ' ,
319+ 1 => 'STATUS_OK ' ,
320+ 2 => 'STATUS_ERROR ' ,
321+ ];
322+
323+ $ code = $ status ['code ' ];
324+ $ output .= isset ($ statusCodes [$ code ]) ? $ statusCodes [$ code ] : "UNKNOWN_STATUS( $ code) " ;
325+ } else {
326+ $ output .= 'UNDEFINED ' ;
327+ }
328+
329+ if (isset ($ status ['description ' ]) && $ status ['description ' ]) {
330+ $ output .= ", Description= \"{$ status ['description ' ]}\"" ;
331+ }
332+
333+ return $ output ;
334+ }
271335}
0 commit comments