]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blobdiff - vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / vendor / monolog / monolog / tests / Monolog / Formatter / NormalizerFormatterTest.php
diff --git a/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php b/vendor/monolog/monolog/tests/Monolog/Formatter/NormalizerFormatterTest.php
new file mode 100644 (file)
index 0000000..57bcdf9
--- /dev/null
@@ -0,0 +1,423 @@
+<?php
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Formatter;
+
+/**
+ * @covers Monolog\Formatter\NormalizerFormatter
+ */
+class NormalizerFormatterTest extends \PHPUnit_Framework_TestCase
+{
+    public function tearDown()
+    {
+        \PHPUnit_Framework_Error_Warning::$enabled = true;
+
+        return parent::tearDown();
+    }
+
+    public function testFormat()
+    {
+        $formatter = new NormalizerFormatter('Y-m-d');
+        $formatted = $formatter->format(array(
+            'level_name' => 'ERROR',
+            'channel' => 'meh',
+            'message' => 'foo',
+            'datetime' => new \DateTime,
+            'extra' => array('foo' => new TestFooNorm, 'bar' => new TestBarNorm, 'baz' => array(), 'res' => fopen('php://memory', 'rb')),
+            'context' => array(
+                'foo' => 'bar',
+                'baz' => 'qux',
+                'inf' => INF,
+                '-inf' => -INF,
+                'nan' => acos(4),
+            ),
+        ));
+
+        $this->assertEquals(array(
+            'level_name' => 'ERROR',
+            'channel' => 'meh',
+            'message' => 'foo',
+            'datetime' => date('Y-m-d'),
+            'extra' => array(
+                'foo' => '[object] (Monolog\\Formatter\\TestFooNorm: {"foo":"foo"})',
+                'bar' => '[object] (Monolog\\Formatter\\TestBarNorm: bar)',
+                'baz' => array(),
+                'res' => '[resource] (stream)',
+            ),
+            'context' => array(
+                'foo' => 'bar',
+                'baz' => 'qux',
+                'inf' => 'INF',
+                '-inf' => '-INF',
+                'nan' => 'NaN',
+            ),
+        ), $formatted);
+    }
+
+    public function testFormatExceptions()
+    {
+        $formatter = new NormalizerFormatter('Y-m-d');
+        $e = new \LogicException('bar');
+        $e2 = new \RuntimeException('foo', 0, $e);
+        $formatted = $formatter->format(array(
+            'exception' => $e2,
+        ));
+
+        $this->assertGreaterThan(5, count($formatted['exception']['trace']));
+        $this->assertTrue(isset($formatted['exception']['previous']));
+        unset($formatted['exception']['trace'], $formatted['exception']['previous']);
+
+        $this->assertEquals(array(
+            'exception' => array(
+                'class'   => get_class($e2),
+                'message' => $e2->getMessage(),
+                'code'    => $e2->getCode(),
+                'file'    => $e2->getFile().':'.$e2->getLine(),
+            ),
+        ), $formatted);
+    }
+
+    public function testFormatSoapFaultException()
+    {
+        if (!class_exists('SoapFault')) {
+            $this->markTestSkipped('Requires the soap extension');
+        }
+
+        $formatter = new NormalizerFormatter('Y-m-d');
+        $e = new \SoapFault('foo', 'bar', 'hello', 'world');
+        $formatted = $formatter->format(array(
+            'exception' => $e,
+        ));
+
+        unset($formatted['exception']['trace']);
+
+        $this->assertEquals(array(
+            'exception' => array(
+                'class' => 'SoapFault',
+                'message' => 'bar',
+                'code' => 0,
+                'file' => $e->getFile().':'.$e->getLine(),
+                'faultcode' => 'foo',
+                'faultactor' => 'hello',
+                'detail' => 'world',
+            ),
+        ), $formatted);
+    }
+
+    public function testFormatToStringExceptionHandle()
+    {
+        $formatter = new NormalizerFormatter('Y-m-d');
+        $this->setExpectedException('RuntimeException', 'Could not convert to string');
+        $formatter->format(array(
+            'myObject' => new TestToStringError(),
+        ));
+    }
+
+    public function testBatchFormat()
+    {
+        $formatter = new NormalizerFormatter('Y-m-d');
+        $formatted = $formatter->formatBatch(array(
+            array(
+                'level_name' => 'CRITICAL',
+                'channel' => 'test',
+                'message' => 'bar',
+                'context' => array(),
+                'datetime' => new \DateTime,
+                'extra' => array(),
+            ),
+            array(
+                'level_name' => 'WARNING',
+                'channel' => 'log',
+                'message' => 'foo',
+                'context' => array(),
+                'datetime' => new \DateTime,
+                'extra' => array(),
+            ),
+        ));
+        $this->assertEquals(array(
+            array(
+                'level_name' => 'CRITICAL',
+                'channel' => 'test',
+                'message' => 'bar',
+                'context' => array(),
+                'datetime' => date('Y-m-d'),
+                'extra' => array(),
+            ),
+            array(
+                'level_name' => 'WARNING',
+                'channel' => 'log',
+                'message' => 'foo',
+                'context' => array(),
+                'datetime' => date('Y-m-d'),
+                'extra' => array(),
+            ),
+        ), $formatted);
+    }
+
+    /**
+     * Test issue #137
+     */
+    public function testIgnoresRecursiveObjectReferences()
+    {
+        // set up the recursion
+        $foo = new \stdClass();
+        $bar = new \stdClass();
+
+        $foo->bar = $bar;
+        $bar->foo = $foo;
+
+        // set an error handler to assert that the error is not raised anymore
+        $that = $this;
+        set_error_handler(function ($level, $message, $file, $line, $context) use ($that) {
+            if (error_reporting() & $level) {
+                restore_error_handler();
+                $that->fail("$message should not be raised");
+            }
+        });
+
+        $formatter = new NormalizerFormatter();
+        $reflMethod = new \ReflectionMethod($formatter, 'toJson');
+        $reflMethod->setAccessible(true);
+        $res = $reflMethod->invoke($formatter, array($foo, $bar), true);
+
+        restore_error_handler();
+
+        $this->assertEquals(@json_encode(array($foo, $bar)), $res);
+    }
+
+    public function testIgnoresInvalidTypes()
+    {
+        // set up the recursion
+        $resource = fopen(__FILE__, 'r');
+
+        // set an error handler to assert that the error is not raised anymore
+        $that = $this;
+        set_error_handler(function ($level, $message, $file, $line, $context) use ($that) {
+            if (error_reporting() & $level) {
+                restore_error_handler();
+                $that->fail("$message should not be raised");
+            }
+        });
+
+        $formatter = new NormalizerFormatter();
+        $reflMethod = new \ReflectionMethod($formatter, 'toJson');
+        $reflMethod->setAccessible(true);
+        $res = $reflMethod->invoke($formatter, array($resource), true);
+
+        restore_error_handler();
+
+        $this->assertEquals(@json_encode(array($resource)), $res);
+    }
+
+    public function testNormalizeHandleLargeArrays()
+    {
+        $formatter = new NormalizerFormatter();
+        $largeArray = range(1, 2000);
+
+        $res = $formatter->format(array(
+            'level_name' => 'CRITICAL',
+            'channel' => 'test',
+            'message' => 'bar',
+            'context' => array($largeArray),
+            'datetime' => new \DateTime,
+            'extra' => array(),
+        ));
+
+        $this->assertCount(1000, $res['context'][0]);
+        $this->assertEquals('Over 1000 items (2000 total), aborting normalization', $res['context'][0]['...']);
+    }
+
+    /**
+     * @expectedException RuntimeException
+     */
+    public function testThrowsOnInvalidEncoding()
+    {
+        if (version_compare(PHP_VERSION, '5.5.0', '<')) {
+            // Ignore the warning that will be emitted by PHP <5.5.0
+            \PHPUnit_Framework_Error_Warning::$enabled = false;
+        }
+        $formatter = new NormalizerFormatter();
+        $reflMethod = new \ReflectionMethod($formatter, 'toJson');
+        $reflMethod->setAccessible(true);
+
+        // send an invalid unicode sequence as a object that can't be cleaned
+        $record = new \stdClass;
+        $record->message = "\xB1\x31";
+        $res = $reflMethod->invoke($formatter, $record);
+        if (PHP_VERSION_ID < 50500 && $res === '{"message":null}') {
+            throw new \RuntimeException('PHP 5.3/5.4 throw a warning and null the value instead of returning false entirely');
+        }
+    }
+
+    public function testConvertsInvalidEncodingAsLatin9()
+    {
+        if (version_compare(PHP_VERSION, '5.5.0', '<')) {
+            // Ignore the warning that will be emitted by PHP <5.5.0
+            \PHPUnit_Framework_Error_Warning::$enabled = false;
+        }
+        $formatter = new NormalizerFormatter();
+        $reflMethod = new \ReflectionMethod($formatter, 'toJson');
+        $reflMethod->setAccessible(true);
+
+        $res = $reflMethod->invoke($formatter, array('message' => "\xA4\xA6\xA8\xB4\xB8\xBC\xBD\xBE"));
+
+        if (version_compare(PHP_VERSION, '5.5.0', '>=')) {
+            $this->assertSame('{"message":"€ŠšŽžŒœŸ"}', $res);
+        } else {
+            // PHP <5.5 does not return false for an element encoding failure,
+            // instead it emits a warning (possibly) and nulls the value.
+            $this->assertSame('{"message":null}', $res);
+        }
+    }
+
+    /**
+     * @param mixed $in     Input
+     * @param mixed $expect Expected output
+     * @covers Monolog\Formatter\NormalizerFormatter::detectAndCleanUtf8
+     * @dataProvider providesDetectAndCleanUtf8
+     */
+    public function testDetectAndCleanUtf8($in, $expect)
+    {
+        $formatter = new NormalizerFormatter();
+        $formatter->detectAndCleanUtf8($in);
+        $this->assertSame($expect, $in);
+    }
+
+    public function providesDetectAndCleanUtf8()
+    {
+        $obj = new \stdClass;
+
+        return array(
+            'null' => array(null, null),
+            'int' => array(123, 123),
+            'float' => array(123.45, 123.45),
+            'bool false' => array(false, false),
+            'bool true' => array(true, true),
+            'ascii string' => array('abcdef', 'abcdef'),
+            'latin9 string' => array("\xB1\x31\xA4\xA6\xA8\xB4\xB8\xBC\xBD\xBE\xFF", '±1€ŠšŽžŒœŸÿ'),
+            'unicode string' => array('¤¦¨´¸¼½¾€ŠšŽžŒœŸ', '¤¦¨´¸¼½¾€ŠšŽžŒœŸ'),
+            'empty array' => array(array(), array()),
+            'array' => array(array('abcdef'), array('abcdef')),
+            'object' => array($obj, $obj),
+        );
+    }
+
+    /**
+     * @param int    $code
+     * @param string $msg
+     * @dataProvider providesHandleJsonErrorFailure
+     */
+    public function testHandleJsonErrorFailure($code, $msg)
+    {
+        $formatter = new NormalizerFormatter();
+        $reflMethod = new \ReflectionMethod($formatter, 'handleJsonError');
+        $reflMethod->setAccessible(true);
+
+        $this->setExpectedException('RuntimeException', $msg);
+        $reflMethod->invoke($formatter, $code, 'faked');
+    }
+
+    public function providesHandleJsonErrorFailure()
+    {
+        return array(
+            'depth' => array(JSON_ERROR_DEPTH, 'Maximum stack depth exceeded'),
+            'state' => array(JSON_ERROR_STATE_MISMATCH, 'Underflow or the modes mismatch'),
+            'ctrl' => array(JSON_ERROR_CTRL_CHAR, 'Unexpected control character found'),
+            'default' => array(-1, 'Unknown error'),
+        );
+    }
+
+    public function testExceptionTraceWithArgs()
+    {
+        if (defined('HHVM_VERSION')) {
+            $this->markTestSkipped('Not supported in HHVM since it detects errors differently');
+        }
+
+        // This happens i.e. in React promises or Guzzle streams where stream wrappers are registered
+        // and no file or line are included in the trace because it's treated as internal function
+        set_error_handler(function ($errno, $errstr, $errfile, $errline) {
+            throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
+        });
+
+        try {
+            // This will contain $resource and $wrappedResource as arguments in the trace item
+            $resource = fopen('php://memory', 'rw+');
+            fwrite($resource, 'test_resource');
+            $wrappedResource = new TestFooNorm;
+            $wrappedResource->foo = $resource;
+            // Just do something stupid with a resource/wrapped resource as argument
+            array_keys($wrappedResource);
+        } catch (\Exception $e) {
+            restore_error_handler();
+        }
+
+        $formatter = new NormalizerFormatter();
+        $record = array('context' => array('exception' => $e));
+        $result = $formatter->format($record);
+
+        $this->assertRegExp(
+            '%"resource":"\[resource\] \(stream\)"%',
+            $result['context']['exception']['trace'][0]
+        );
+
+        if (version_compare(PHP_VERSION, '5.5.0', '>=')) {
+            $pattern = '%"wrappedResource":"\[object\] \(Monolog\\\\\\\\Formatter\\\\\\\\TestFooNorm: \)"%';
+        } else {
+            $pattern = '%\\\\"foo\\\\":null%';
+        }
+
+        // Tests that the wrapped resource is ignored while encoding, only works for PHP <= 5.4
+        $this->assertRegExp(
+            $pattern,
+            $result['context']['exception']['trace'][0]
+        );
+    }
+}
+
+class TestFooNorm
+{
+    public $foo = 'foo';
+}
+
+class TestBarNorm
+{
+    public function __toString()
+    {
+        return 'bar';
+    }
+}
+
+class TestStreamFoo
+{
+    public $foo;
+    public $resource;
+
+    public function __construct($resource)
+    {
+        $this->resource = $resource;
+        $this->foo = 'BAR';
+    }
+
+    public function __toString()
+    {
+        fseek($this->resource, 0);
+
+        return $this->foo . ' - ' . (string) stream_get_contents($this->resource);
+    }
+}
+
+class TestToStringError
+{
+    public function __toString()
+    {
+        throw new \RuntimeException('Could not convert to string');
+    }
+}