scratch – Blame information for rev 120

Subversion Repositories:
Rev:
Rev Author Line No. Line
120 office 1 <?php
2  
3 /*
4 * This file is part of the Monolog package.
5 *
6 * (c) Jordi Boggiano <j.boggiano@seld.be>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11  
12 namespace Monolog\Formatter;
13  
14 /**
15 * @covers Monolog\Formatter\NormalizerFormatter
16 */
17 class NormalizerFormatterTest extends \PHPUnit_Framework_TestCase
18 {
19 public function tearDown()
20 {
21 \PHPUnit_Framework_Error_Warning::$enabled = true;
22  
23 return parent::tearDown();
24 }
25  
26 public function testFormat()
27 {
28 $formatter = new NormalizerFormatter('Y-m-d');
29 $formatted = $formatter->format(array(
30 'level_name' => 'ERROR',
31 'channel' => 'meh',
32 'message' => 'foo',
33 'datetime' => new \DateTime,
34 'extra' => array('foo' => new TestFooNorm, 'bar' => new TestBarNorm, 'baz' => array(), 'res' => fopen('php://memory', 'rb')),
35 'context' => array(
36 'foo' => 'bar',
37 'baz' => 'qux',
38 'inf' => INF,
39 '-inf' => -INF,
40 'nan' => acos(4),
41 ),
42 ));
43  
44 $this->assertEquals(array(
45 'level_name' => 'ERROR',
46 'channel' => 'meh',
47 'message' => 'foo',
48 'datetime' => date('Y-m-d'),
49 'extra' => array(
50 'foo' => '[object] (Monolog\\Formatter\\TestFooNorm: {"foo":"foo"})',
51 'bar' => '[object] (Monolog\\Formatter\\TestBarNorm: bar)',
52 'baz' => array(),
53 'res' => '[resource] (stream)',
54 ),
55 'context' => array(
56 'foo' => 'bar',
57 'baz' => 'qux',
58 'inf' => 'INF',
59 '-inf' => '-INF',
60 'nan' => 'NaN',
61 ),
62 ), $formatted);
63 }
64  
65 public function testFormatExceptions()
66 {
67 $formatter = new NormalizerFormatter('Y-m-d');
68 $e = new \LogicException('bar');
69 $e2 = new \RuntimeException('foo', 0, $e);
70 $formatted = $formatter->format(array(
71 'exception' => $e2,
72 ));
73  
74 $this->assertGreaterThan(5, count($formatted['exception']['trace']));
75 $this->assertTrue(isset($formatted['exception']['previous']));
76 unset($formatted['exception']['trace'], $formatted['exception']['previous']);
77  
78 $this->assertEquals(array(
79 'exception' => array(
80 'class' => get_class($e2),
81 'message' => $e2->getMessage(),
82 'code' => $e2->getCode(),
83 'file' => $e2->getFile().':'.$e2->getLine(),
84 ),
85 ), $formatted);
86 }
87  
88 public function testFormatSoapFaultException()
89 {
90 if (!class_exists('SoapFault')) {
91 $this->markTestSkipped('Requires the soap extension');
92 }
93  
94 $formatter = new NormalizerFormatter('Y-m-d');
95 $e = new \SoapFault('foo', 'bar', 'hello', 'world');
96 $formatted = $formatter->format(array(
97 'exception' => $e,
98 ));
99  
100 unset($formatted['exception']['trace']);
101  
102 $this->assertEquals(array(
103 'exception' => array(
104 'class' => 'SoapFault',
105 'message' => 'bar',
106 'code' => 0,
107 'file' => $e->getFile().':'.$e->getLine(),
108 'faultcode' => 'foo',
109 'faultactor' => 'hello',
110 'detail' => 'world',
111 ),
112 ), $formatted);
113 }
114  
115 public function testFormatToStringExceptionHandle()
116 {
117 $formatter = new NormalizerFormatter('Y-m-d');
118 $this->setExpectedException('RuntimeException', 'Could not convert to string');
119 $formatter->format(array(
120 'myObject' => new TestToStringError(),
121 ));
122 }
123  
124 public function testBatchFormat()
125 {
126 $formatter = new NormalizerFormatter('Y-m-d');
127 $formatted = $formatter->formatBatch(array(
128 array(
129 'level_name' => 'CRITICAL',
130 'channel' => 'test',
131 'message' => 'bar',
132 'context' => array(),
133 'datetime' => new \DateTime,
134 'extra' => array(),
135 ),
136 array(
137 'level_name' => 'WARNING',
138 'channel' => 'log',
139 'message' => 'foo',
140 'context' => array(),
141 'datetime' => new \DateTime,
142 'extra' => array(),
143 ),
144 ));
145 $this->assertEquals(array(
146 array(
147 'level_name' => 'CRITICAL',
148 'channel' => 'test',
149 'message' => 'bar',
150 'context' => array(),
151 'datetime' => date('Y-m-d'),
152 'extra' => array(),
153 ),
154 array(
155 'level_name' => 'WARNING',
156 'channel' => 'log',
157 'message' => 'foo',
158 'context' => array(),
159 'datetime' => date('Y-m-d'),
160 'extra' => array(),
161 ),
162 ), $formatted);
163 }
164  
165 /**
166 * Test issue #137
167 */
168 public function testIgnoresRecursiveObjectReferences()
169 {
170 // set up the recursion
171 $foo = new \stdClass();
172 $bar = new \stdClass();
173  
174 $foo->bar = $bar;
175 $bar->foo = $foo;
176  
177 // set an error handler to assert that the error is not raised anymore
178 $that = $this;
179 set_error_handler(function ($level, $message, $file, $line, $context) use ($that) {
180 if (error_reporting() & $level) {
181 restore_error_handler();
182 $that->fail("$message should not be raised");
183 }
184 });
185  
186 $formatter = new NormalizerFormatter();
187 $reflMethod = new \ReflectionMethod($formatter, 'toJson');
188 $reflMethod->setAccessible(true);
189 $res = $reflMethod->invoke($formatter, array($foo, $bar), true);
190  
191 restore_error_handler();
192  
193 $this->assertEquals(@json_encode(array($foo, $bar)), $res);
194 }
195  
196 public function testIgnoresInvalidTypes()
197 {
198 // set up the recursion
199 $resource = fopen(__FILE__, 'r');
200  
201 // set an error handler to assert that the error is not raised anymore
202 $that = $this;
203 set_error_handler(function ($level, $message, $file, $line, $context) use ($that) {
204 if (error_reporting() & $level) {
205 restore_error_handler();
206 $that->fail("$message should not be raised");
207 }
208 });
209  
210 $formatter = new NormalizerFormatter();
211 $reflMethod = new \ReflectionMethod($formatter, 'toJson');
212 $reflMethod->setAccessible(true);
213 $res = $reflMethod->invoke($formatter, array($resource), true);
214  
215 restore_error_handler();
216  
217 $this->assertEquals(@json_encode(array($resource)), $res);
218 }
219  
220 public function testNormalizeHandleLargeArrays()
221 {
222 $formatter = new NormalizerFormatter();
223 $largeArray = range(1, 2000);
224  
225 $res = $formatter->format(array(
226 'level_name' => 'CRITICAL',
227 'channel' => 'test',
228 'message' => 'bar',
229 'context' => array($largeArray),
230 'datetime' => new \DateTime,
231 'extra' => array(),
232 ));
233  
234 $this->assertCount(1000, $res['context'][0]);
235 $this->assertEquals('Over 1000 items (2000 total), aborting normalization', $res['context'][0]['...']);
236 }
237  
238 /**
239 * @expectedException RuntimeException
240 */
241 public function testThrowsOnInvalidEncoding()
242 {
243 if (version_compare(PHP_VERSION, '5.5.0', '<')) {
244 // Ignore the warning that will be emitted by PHP <5.5.0
245 \PHPUnit_Framework_Error_Warning::$enabled = false;
246 }
247 $formatter = new NormalizerFormatter();
248 $reflMethod = new \ReflectionMethod($formatter, 'toJson');
249 $reflMethod->setAccessible(true);
250  
251 // send an invalid unicode sequence as a object that can't be cleaned
252 $record = new \stdClass;
253 $record->message = "\xB1\x31";
254 $res = $reflMethod->invoke($formatter, $record);
255 if (PHP_VERSION_ID < 50500 && $res === '{"message":null}') {
256 throw new \RuntimeException('PHP 5.3/5.4 throw a warning and null the value instead of returning false entirely');
257 }
258 }
259  
260 public function testConvertsInvalidEncodingAsLatin9()
261 {
262 if (version_compare(PHP_VERSION, '5.5.0', '<')) {
263 // Ignore the warning that will be emitted by PHP <5.5.0
264 \PHPUnit_Framework_Error_Warning::$enabled = false;
265 }
266 $formatter = new NormalizerFormatter();
267 $reflMethod = new \ReflectionMethod($formatter, 'toJson');
268 $reflMethod->setAccessible(true);
269  
270 $res = $reflMethod->invoke($formatter, array('message' => "\xA4\xA6\xA8\xB4\xB8\xBC\xBD\xBE"));
271  
272 if (version_compare(PHP_VERSION, '5.5.0', '>=')) {
273 $this->assertSame('{"message":"€ŠšŽžŒœŸ"}', $res);
274 } else {
275 // PHP <5.5 does not return false for an element encoding failure,
276 // instead it emits a warning (possibly) and nulls the value.
277 $this->assertSame('{"message":null}', $res);
278 }
279 }
280  
281 /**
282 * @param mixed $in Input
283 * @param mixed $expect Expected output
284 * @covers Monolog\Formatter\NormalizerFormatter::detectAndCleanUtf8
285 * @dataProvider providesDetectAndCleanUtf8
286 */
287 public function testDetectAndCleanUtf8($in, $expect)
288 {
289 $formatter = new NormalizerFormatter();
290 $formatter->detectAndCleanUtf8($in);
291 $this->assertSame($expect, $in);
292 }
293  
294 public function providesDetectAndCleanUtf8()
295 {
296 $obj = new \stdClass;
297  
298 return array(
299 'null' => array(null, null),
300 'int' => array(123, 123),
301 'float' => array(123.45, 123.45),
302 'bool false' => array(false, false),
303 'bool true' => array(true, true),
304 'ascii string' => array('abcdef', 'abcdef'),
305 'latin9 string' => array("\xB1\x31\xA4\xA6\xA8\xB4\xB8\xBC\xBD\xBE\xFF", '±1€ŠšŽžŒœŸÿ'),
306 'unicode string' => array('¤¦¨´¸¼½¾€ŠšŽžŒœŸ', '¤¦¨´¸¼½¾€ŠšŽžŒœŸ'),
307 'empty array' => array(array(), array()),
308 'array' => array(array('abcdef'), array('abcdef')),
309 'object' => array($obj, $obj),
310 );
311 }
312  
313 /**
314 * @param int $code
315 * @param string $msg
316 * @dataProvider providesHandleJsonErrorFailure
317 */
318 public function testHandleJsonErrorFailure($code, $msg)
319 {
320 $formatter = new NormalizerFormatter();
321 $reflMethod = new \ReflectionMethod($formatter, 'handleJsonError');
322 $reflMethod->setAccessible(true);
323  
324 $this->setExpectedException('RuntimeException', $msg);
325 $reflMethod->invoke($formatter, $code, 'faked');
326 }
327  
328 public function providesHandleJsonErrorFailure()
329 {
330 return array(
331 'depth' => array(JSON_ERROR_DEPTH, 'Maximum stack depth exceeded'),
332 'state' => array(JSON_ERROR_STATE_MISMATCH, 'Underflow or the modes mismatch'),
333 'ctrl' => array(JSON_ERROR_CTRL_CHAR, 'Unexpected control character found'),
334 'default' => array(-1, 'Unknown error'),
335 );
336 }
337  
338 public function testExceptionTraceWithArgs()
339 {
340 if (defined('HHVM_VERSION')) {
341 $this->markTestSkipped('Not supported in HHVM since it detects errors differently');
342 }
343  
344 // This happens i.e. in React promises or Guzzle streams where stream wrappers are registered
345 // and no file or line are included in the trace because it's treated as internal function
346 set_error_handler(function ($errno, $errstr, $errfile, $errline) {
347 throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
348 });
349  
350 try {
351 // This will contain $resource and $wrappedResource as arguments in the trace item
352 $resource = fopen('php://memory', 'rw+');
353 fwrite($resource, 'test_resource');
354 $wrappedResource = new TestFooNorm;
355 $wrappedResource->foo = $resource;
356 // Just do something stupid with a resource/wrapped resource as argument
357 array_keys($wrappedResource);
358 } catch (\Exception $e) {
359 restore_error_handler();
360 }
361  
362 $formatter = new NormalizerFormatter();
363 $record = array('context' => array('exception' => $e));
364 $result = $formatter->format($record);
365  
366 $this->assertRegExp(
367 '%"resource":"\[resource\] \(stream\)"%',
368 $result['context']['exception']['trace'][0]
369 );
370  
371 if (version_compare(PHP_VERSION, '5.5.0', '>=')) {
372 $pattern = '%"wrappedResource":"\[object\] \(Monolog\\\\\\\\Formatter\\\\\\\\TestFooNorm: \)"%';
373 } else {
374 $pattern = '%\\\\"foo\\\\":null%';
375 }
376  
377 // Tests that the wrapped resource is ignored while encoding, only works for PHP <= 5.4
378 $this->assertRegExp(
379 $pattern,
380 $result['context']['exception']['trace'][0]
381 );
382 }
383 }
384  
385 class TestFooNorm
386 {
387 public $foo = 'foo';
388 }
389  
390 class TestBarNorm
391 {
392 public function __toString()
393 {
394 return 'bar';
395 }
396 }
397  
398 class TestStreamFoo
399 {
400 public $foo;
401 public $resource;
402  
403 public function __construct($resource)
404 {
405 $this->resource = $resource;
406 $this->foo = 'BAR';
407 }
408  
409 public function __toString()
410 {
411 fseek($this->resource, 0);
412  
413 return $this->foo . ' - ' . (string) stream_get_contents($this->resource);
414 }
415 }
416  
417 class TestToStringError
418 {
419 public function __toString()
420 {
421 throw new \RuntimeException('Could not convert to string');
422 }
423 }