diff --git a/ChangeLog-7.3.md b/ChangeLog-7.3.md index 23d4a4c485a..ce2efcccf2e 100644 --- a/ChangeLog-7.3.md +++ b/ChangeLog-7.3.md @@ -2,6 +2,13 @@ All notable changes of the PHPUnit 7.3 release series are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles. +## [7.3.3] - 2018-09-01 + +### Fixed + +* Fixed [#3265](https://github.com/sebastianbergmann/phpunit/pull/3265): Slashes are unnecessarily escaped in prettified JSON +* Fixed [#3267](https://github.com/sebastianbergmann/phpunit/pull/3267): `%` not escaped correctly for `StringMatchesFormat` constraint + ## [7.3.2] - 2018-08-22 ### Fixed @@ -45,6 +52,7 @@ All notable changes of the PHPUnit 7.3 release series are documented in this fil * Fixed [#3222](https://github.com/sebastianbergmann/phpunit/pull/3222): Priority of `@covers` and `@coversNothing` is wrong * Fixed [#3225](https://github.com/sebastianbergmann/phpunit/issues/3225): `coverage-php` missing from `phpunit.xsd` +[7.3.3]: https://github.com/sebastianbergmann/phpunit/compare/7.3.2...7.3.3 [7.3.2]: https://github.com/sebastianbergmann/phpunit/compare/7.3.1...7.3.2 [7.3.1]: https://github.com/sebastianbergmann/phpunit/compare/7.3.0...7.3.1 [7.3.0]: https://github.com/sebastianbergmann/phpunit/compare/7.2...7.3.0 diff --git a/src/Framework/Constraint/StringMatchesFormatDescription.php b/src/Framework/Constraint/StringMatchesFormatDescription.php index 5b3e5700551..0fec82d2ce9 100644 --- a/src/Framework/Constraint/StringMatchesFormatDescription.php +++ b/src/Framework/Constraint/StringMatchesFormatDescription.php @@ -75,38 +75,24 @@ protected function additionalFailureDescription($other): string private function createPatternFromFormat(string $string): string { - $string = \preg_replace( + $string = \strtr( + \preg_quote($string, '/'), [ - '/(? '%', + '%e' => '\\' . \DIRECTORY_SEPARATOR, + '%s' => '[^\r\n]+', + '%S' => '[^\r\n]*', + '%a' => '.+', + '%A' => '.*', + '%w' => '\s*', + '%i' => '[+-]?\d+', + '%d' => '\d+', + '%x' => '[0-9a-fA-F]+', + '%f' => '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?', + '%c' => '.' + ] ); - $string = \str_replace('%%', '%', $string); - return '/^' . $string . '$/s'; } diff --git a/src/Util/Json.php b/src/Util/Json.php index 1cb2c3b243f..c8811911e03 100644 --- a/src/Util/Json.php +++ b/src/Util/Json.php @@ -28,7 +28,7 @@ public static function prettify(string $json): string ); } - return \json_encode($decodedJson, \JSON_PRETTY_PRINT); + return \json_encode($decodedJson, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES); } /* diff --git a/tests/Framework/Constraint/StringMatchesFormatDescriptionTest.php b/tests/Framework/Constraint/StringMatchesFormatDescriptionTest.php index 55c2785636c..45071603167 100644 --- a/tests/Framework/Constraint/StringMatchesFormatDescriptionTest.php +++ b/tests/Framework/Constraint/StringMatchesFormatDescriptionTest.php @@ -13,13 +13,16 @@ class StringMatchesFormatDescriptionTest extends ConstraintTestCase { - public function testConstraintStringMatchesCharacter(): void + public function testConstraintStringMatchesDirectorySeparator(): void { - $constraint = new StringMatchesFormatDescription('*%c*'); + $constraint = new StringMatchesFormatDescription('*%e*'); $this->assertFalse($constraint->evaluate('**', '', true)); - $this->assertTrue($constraint->evaluate('***', '', true)); - $this->assertEquals('matches PCRE pattern "/^\*.\*$/s"', $constraint->toString()); + $this->assertFalse($constraint->evaluate('*a*', '', true)); + + $this->assertTrue($constraint->evaluate('*' . \DIRECTORY_SEPARATOR . '*', '', true)); + + $this->assertEquals('matches PCRE pattern "/^\*\\' . \DIRECTORY_SEPARATOR . '\*$/s"', $constraint->toString()); $this->assertCount(1, $constraint); } @@ -28,17 +31,87 @@ public function testConstraintStringMatchesString(): void $constraint = new StringMatchesFormatDescription('*%s*'); $this->assertFalse($constraint->evaluate('**', '', true)); + $this->assertFalse($constraint->evaluate("*\n*", '', true)); + $this->assertTrue($constraint->evaluate('***', '', true)); + $this->assertTrue($constraint->evaluate('*foo 123 bar*', '', true)); + $this->assertEquals('matches PCRE pattern "/^\*[^\r\n]+\*$/s"', $constraint->toString()); $this->assertCount(1, $constraint); } + public function testConstraintStringMatchesOptionalString(): void + { + $constraint = new StringMatchesFormatDescription('*%S*'); + + $this->assertFalse($constraint->evaluate('*', '', true)); + $this->assertFalse($constraint->evaluate("*\n*", '', true)); + + $this->assertTrue($constraint->evaluate('***', '', true)); + $this->assertTrue($constraint->evaluate('*foo 123 bar*', '', true)); + $this->assertTrue($constraint->evaluate('**', '', true)); + + $this->assertEquals('matches PCRE pattern "/^\*[^\r\n]*\*$/s"', $constraint->toString()); + $this->assertCount(1, $constraint); + } + + public function testConstraintStringMatchesAnything(): void + { + $constraint = new StringMatchesFormatDescription('*%a*'); + + $this->assertFalse($constraint->evaluate('**', '', true)); + + $this->assertTrue($constraint->evaluate('***', '', true)); + $this->assertTrue($constraint->evaluate('*foo 123 bar*', '', true)); + $this->assertTrue($constraint->evaluate("*\n*", '', true)); + + $this->assertEquals('matches PCRE pattern "/^\*.+\*$/s"', $constraint->toString()); + $this->assertCount(1, $constraint); + } + + public function testConstraintStringMatchesOptionalAnything(): void + { + $constraint = new StringMatchesFormatDescription('*%A*'); + + $this->assertFalse($constraint->evaluate('*', '', true)); + + $this->assertTrue($constraint->evaluate('***', '', true)); + $this->assertTrue($constraint->evaluate('*foo 123 bar*', '', true)); + $this->assertTrue($constraint->evaluate("*\n*", '', true)); + $this->assertTrue($constraint->evaluate('**', '', true)); + + $this->assertEquals('matches PCRE pattern "/^\*.*\*$/s"', $constraint->toString()); + $this->assertCount(1, $constraint); + } + + public function testConstraintStringMatchesWhitespace(): void + { + $constraint = new StringMatchesFormatDescription('*%w*'); + + $this->assertFalse($constraint->evaluate('*', '', true)); + $this->assertFalse($constraint->evaluate('*a*', '', true)); + + $this->assertTrue($constraint->evaluate('* *', '', true)); + $this->assertTrue($constraint->evaluate("*\t\n*", '', true)); + $this->assertTrue($constraint->evaluate('**', '', true)); + + $this->assertEquals('matches PCRE pattern "/^\*\s*\*$/s"', $constraint->toString()); + $this->assertCount(1, $constraint); + } + public function testConstraintStringMatchesInteger(): void { $constraint = new StringMatchesFormatDescription('*%i*'); $this->assertFalse($constraint->evaluate('**', '', true)); + $this->assertFalse($constraint->evaluate('*a*', '', true)); + $this->assertFalse($constraint->evaluate('*1.0*', '', true)); + $this->assertTrue($constraint->evaluate('*0*', '', true)); + $this->assertTrue($constraint->evaluate('*12*', '', true)); + $this->assertTrue($constraint->evaluate('*-1*', '', true)); + $this->assertTrue($constraint->evaluate('*+2*', '', true)); + $this->assertEquals('matches PCRE pattern "/^\*[+-]?\d+\*$/s"', $constraint->toString()); $this->assertCount(1, $constraint); } @@ -48,7 +121,14 @@ public function testConstraintStringMatchesUnsignedInt(): void $constraint = new StringMatchesFormatDescription('*%d*'); $this->assertFalse($constraint->evaluate('**', '', true)); + $this->assertFalse($constraint->evaluate('*a*', '', true)); + $this->assertFalse($constraint->evaluate('*1.0*', '', true)); + $this->assertFalse($constraint->evaluate('*-1*', '', true)); + $this->assertFalse($constraint->evaluate('*+2*', '', true)); + $this->assertTrue($constraint->evaluate('*0*', '', true)); + $this->assertTrue($constraint->evaluate('*12*', '', true)); + $this->assertEquals('matches PCRE pattern "/^\*\d+\*$/s"', $constraint->toString()); $this->assertCount(1, $constraint); } @@ -58,7 +138,17 @@ public function testConstraintStringMatchesHexadecimal(): void $constraint = new StringMatchesFormatDescription('*%x*'); $this->assertFalse($constraint->evaluate('**', '', true)); + $this->assertFalse($constraint->evaluate('***', '', true)); + $this->assertFalse($constraint->evaluate('*g*', '', true)); + $this->assertFalse($constraint->evaluate('*1.0*', '', true)); + $this->assertFalse($constraint->evaluate('*-1*', '', true)); + $this->assertFalse($constraint->evaluate('*+2*', '', true)); + $this->assertTrue($constraint->evaluate('*0f0f0f*', '', true)); + $this->assertTrue($constraint->evaluate('*0*', '', true)); + $this->assertTrue($constraint->evaluate('*12*', '', true)); + $this->assertTrue($constraint->evaluate('*a*', '', true)); + $this->assertEquals('matches PCRE pattern "/^\*[0-9a-fA-F]+\*$/s"', $constraint->toString()); $this->assertCount(1, $constraint); } @@ -68,11 +158,91 @@ public function testConstraintStringMatchesFloat(): void $constraint = new StringMatchesFormatDescription('*%f*'); $this->assertFalse($constraint->evaluate('**', '', true)); + $this->assertFalse($constraint->evaluate('***', '', true)); + $this->assertFalse($constraint->evaluate('*a*', '', true)); + $this->assertTrue($constraint->evaluate('*1.0*', '', true)); + $this->assertTrue($constraint->evaluate('*0*', '', true)); + $this->assertTrue($constraint->evaluate('*12*', '', true)); + $this->assertTrue($constraint->evaluate('*.1*', '', true)); + $this->assertTrue($constraint->evaluate('*1.*', '', true)); + $this->assertTrue($constraint->evaluate('*2e3*', '', true)); + $this->assertTrue($constraint->evaluate('*-2.34e-56*', '', true)); + $this->assertTrue($constraint->evaluate('*+2.34e+56*', '', true)); + $this->assertEquals('matches PCRE pattern "/^\*[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?\*$/s"', $constraint->toString()); $this->assertCount(1, $constraint); } + public function testConstraintStringMatchesCharacter(): void + { + $constraint = new StringMatchesFormatDescription('*%c*'); + + $this->assertFalse($constraint->evaluate('**', '', true)); + $this->assertFalse($constraint->evaluate('*ab*', '', true)); + + $this->assertTrue($constraint->evaluate('***', '', true)); + $this->assertTrue($constraint->evaluate('*a*', '', true)); + $this->assertTrue($constraint->evaluate('*g*', '', true)); + $this->assertTrue($constraint->evaluate('*0*', '', true)); + $this->assertTrue($constraint->evaluate('*2*', '', true)); + $this->assertTrue($constraint->evaluate('* *', '', true)); + $this->assertTrue($constraint->evaluate("*\n*", '', true)); + + $this->assertEquals('matches PCRE pattern "/^\*.\*$/s"', $constraint->toString()); + $this->assertCount(1, $constraint); + } + + public function testConstraintStringMatchesEscapedPercent(): void + { + $constraint = new StringMatchesFormatDescription('%%,%%e,%%s,%%S,%%a,%%A,%%w,%%i,%%d,%%x,%%f,%%c,%%Z,%%%%,%%'); + + $this->assertFalse($constraint->evaluate('%%,%' . \DIRECTORY_SEPARATOR . ',%*,%*,%*,%*,% ,%0,%0,%0f0f0f,%1.0,%*,%%Z,%%%%,%%', '', true)); + $this->assertTrue($constraint->evaluate('%,%e,%s,%S,%a,%A,%w,%i,%d,%x,%f,%c,%Z,%%,%', '', true)); + $this->assertEquals('matches PCRE pattern "/^%,%e,%s,%S,%a,%A,%w,%i,%d,%x,%f,%c,%Z,%%,%$/s"', $constraint->toString()); + $this->assertCount(1, $constraint); + } + + public function testConstraintStringMatchesEscapedPercentThenPlaceholder(): void + { + $constraint = new StringMatchesFormatDescription('%%%e,%%%s,%%%S,%%%a,%%%A,%%%w,%%%i,%%%d,%%%x,%%%f,%%%c'); + + $this->assertFalse($constraint->evaluate('%%e,%%s,%%S,%%a,%%A,%%w,%%i,%%d,%%x,%%f,%%c', '', true)); + $this->assertTrue($constraint->evaluate('%' . \DIRECTORY_SEPARATOR . ',%*,%*,%*,%*,% ,%0,%0,%0f0f0f,%1.0,%*', '', true)); + $this->assertEquals('matches PCRE pattern "/^%\\' . \DIRECTORY_SEPARATOR . ',%[^\r\n]+,%[^\r\n]*,%.+,%.*,%\s*,%[+-]?\d+,%\d+,%[0-9a-fA-F]+,%[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?,%.$/s"', $constraint->toString()); + $this->assertCount(1, $constraint); + } + + public function testConstraintStringMatchesSlash(): void + { + $constraint = new StringMatchesFormatDescription('/'); + + $this->assertFalse($constraint->evaluate('\\/', '', true)); + $this->assertTrue($constraint->evaluate('/', '', true)); + $this->assertEquals('matches PCRE pattern "/^\\/$/s"', $constraint->toString()); + $this->assertCount(1, $constraint); + } + + public function testConstraintStringMatchesBackslash(): void + { + $constraint = new StringMatchesFormatDescription('\\'); + + $this->assertFalse($constraint->evaluate('\\\\', '', true)); + $this->assertTrue($constraint->evaluate('\\', '', true)); + $this->assertEquals('matches PCRE pattern "/^\\\\$/s"', $constraint->toString()); + $this->assertCount(1, $constraint); + } + + public function testConstraintStringMatchesBackslashSlash(): void + { + $constraint = new StringMatchesFormatDescription('\\/'); + + $this->assertFalse($constraint->evaluate('/', '', true)); + $this->assertTrue($constraint->evaluate('\\/', '', true)); + $this->assertEquals('matches PCRE pattern "/^\\\\\\/$/s"', $constraint->toString()); + $this->assertCount(1, $constraint); + } + public function testConstraintStringMatchesNewline(): void { $constraint = new StringMatchesFormatDescription("\r\n"); diff --git a/tests/Util/JsonTest.php b/tests/Util/JsonTest.php index 04eaf7aaeb0..406d765f716 100644 --- a/tests/Util/JsonTest.php +++ b/tests/Util/JsonTest.php @@ -60,6 +60,7 @@ public function prettifyProvider() { return [ ['{"name":"John","age": "5"}', "{\n \"name\": \"John\",\n \"age\": \"5\"\n}"], + ['{"url":"https://www.example.com/"}', "{\n \"url\": \"https://www.example.com/\"\n}"], ]; }