@@ -123,6 +123,75 @@ public function testCaptureLabelsAThrowableWithItsMessage(): void
123123 );
124124 }
125125
126+ public function testCaptureNormalizesInvalidUtf8StringableLabel (): void
127+ {
128+ $ value = DebugValue ::capture (
129+ new class implements Stringable {
130+ /**
131+ * Returns a binary fixture label.
132+ *
133+ * @return string Binary fixture label.
134+ */
135+ public function __toString (): string
136+ {
137+ return "\xB1\x31" ;
138+ }
139+ },
140+ );
141+
142+ self ::assertSame (
143+ '(binary: base64 sTE=) ' ,
144+ $ value ->value ,
145+ 'A binary Stringable label must be represented as base64. ' ,
146+ );
147+ self ::assertJson (
148+ json_encode ($ value , JSON_THROW_ON_ERROR ),
149+ 'A binary Stringable label must remain JSON-safe. ' ,
150+ );
151+ }
152+
153+ public function testCaptureNormalizesInvalidUtf8ThrowableMessage (): void
154+ {
155+ $ value = DebugValue ::capture (new RuntimeException ("\xB1\x31" ));
156+
157+ self ::assertSame (
158+ RuntimeException::class . ': (binary: base64 sTE=) ' ,
159+ $ value ->value ,
160+ 'A binary throwable message must be represented as base64. ' ,
161+ );
162+ self ::assertJson (
163+ json_encode ($ value , JSON_THROW_ON_ERROR ),
164+ 'A binary throwable message must remain JSON-safe. ' ,
165+ );
166+ }
167+
168+ public function testCaptureStopsTraversingObjectBeyondTheNodeLimit (): void
169+ {
170+ $ object = new stdClass ();
171+
172+ for ($ index = 0 ; $ index < 10_050 ; $ index ++) {
173+ $ object ->{"property- {$ index }" } = $ index ;
174+ }
175+
176+ $ value = DebugValue ::capture ($ object );
177+ $ entries = $ value ->jsonSerialize ()['entries ' ] ?? null ;
178+
179+ self ::assertIsArray (
180+ $ entries ,
181+ 'A captured object must contain serialized entries. ' ,
182+ );
183+ self ::assertCount (
184+ 10_000 ,
185+ $ entries ,
186+ 'Object traversal must stop after recording the node-limit marker. ' ,
187+ );
188+ self ::assertStringContainsString (
189+ 'SKIPPED over 10000 nodes ' ,
190+ $ this ->flatten ($ value ),
191+ 'Object values past the node budget must be truncated. ' ,
192+ );
193+ }
194+
126195 public function testCaptureStringifiesAStringableObject (): void
127196 {
128197 $ value = DebugValue ::capture (
@@ -163,9 +232,21 @@ public function testCaptureTruncatesBeyondTheDepthLimit(): void
163232
164233 public function testCaptureTruncatesBeyondTheNodeLimit (): void
165234 {
235+ $ value = DebugValue ::capture (range (1 , 10_050 ));
236+ $ entries = $ value ->jsonSerialize ()['entries ' ] ?? null ;
237+
238+ self ::assertIsArray (
239+ $ entries ,
240+ 'A captured array must contain serialized entries. ' ,
241+ );
242+ self ::assertCount (
243+ 10_000 ,
244+ $ entries ,
245+ 'Traversal must stop after recording the node-limit marker. ' ,
246+ );
166247 self ::assertStringContainsString (
167248 'SKIPPED over 10000 nodes ' ,
168- $ this ->flatten (DebugValue :: capture ( range ( 1 , 10_050 )) ),
249+ $ this ->flatten ($ value ),
169250 'Values past the node budget must be truncated. ' ,
170251 );
171252 }
0 commit comments