Files
predis/tests/Predis/Protocol/Parser/Strategy/Resp3StrategyTest.php
T
Sergey Sannikov 3ffaf10e99 Return NAN for RESP3 NaN double payloads (#1718)
parseDouble() fell through to a float cast for ',nan', and
(float) 'nan' evaluates to 0.0 in PHP, so a NaN score silently became
a valid zero. Return NAN instead. The RESP3 specification also notes
that Redis before 7.2 may emit any libc representation of NaN
('-nan', 'NAN', 'nan(char-sequence)') and that clients should handle
them, so those spellings are accepted as well.

The TDigest RESP3 tests asserted 0 or null for empty sketches, which
only passed because of the collapsed 0.0 (null == 0.0 loosely); their
RESP2 counterparts already assert the string 'nan' for the same
replies. They now assert NaN.

Note for reviewers: json_encode() throws on NAN, so consumers who
serialize raw replies must handle it; the previous behavior hid NaN
behind a plausible-looking 0.0 instead.
2026-09-10 15:29:52 -07:00

219 lines
5.4 KiB
PHP

<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2026 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Protocol\Parser\Strategy;
use PredisTestCase;
class Resp3StrategyTest extends PredisTestCase
{
/**
* @var ParserStrategyInterface
*/
protected $strategy;
protected function setUp(): void
{
parent::setUp();
$this->strategy = new Resp3Strategy();
}
/**
* @group disconnected
* @return void
*/
public function testParseDataReturnsNullOnNullType(): void
{
$data = "_\r\n";
$actualResponse = $this->strategy->parseData($data);
$this->assertNull($actualResponse);
}
/**
* @group disconnected
* @return void
*/
public function testParseDataReturnsFloatOnDoubleType(): void
{
$data = ",1.23\r\n";
$actualResponse = $this->strategy->parseData($data);
$this->assertSame(1.23, $actualResponse);
}
/**
* @dataProvider infinityProvider
* @group disconnected
* @param string $data
* @return void
*/
public function testParseDataReturnsFloatInfinityOnInfinityOrNegativeInfinity(string $data, float $expectedValue): void
{
$actualResponse = $this->strategy->parseData($data);
$this->assertSame($expectedValue, $actualResponse);
}
/**
* @dataProvider nanProvider
* @group disconnected
* @param string $data
* @return void
*/
public function testParseDataReturnsFloatNanOnNanValue(string $data): void
{
$actualResponse = $this->strategy->parseData($data);
$this->assertNan($actualResponse);
}
/**
* @dataProvider booleanProvider
* @group disconnected
* @param string $data
* @param bool $expectedValue
* @return void
*/
public function testParseDataReturnsBooleanOnBooleanType(string $data, bool $expectedValue): void
{
$actualResponse = $this->strategy->parseData($data);
$this->assertSame($expectedValue, $actualResponse);
}
/**
* @group disconnected
* @return void
*/
public function testParseDataReturnsArrayOnBlobErrorType(): void
{
$data = "!21\r\nSYNTAX invalid syntax\r\n";
$actualResponse = $this->strategy->parseData($data);
$this->assertSame(['type' => 'blobError', 'value' => 21], $actualResponse);
}
/**
* @group disconnected
* @return void
*/
public function testParseDataReturnsArrayOnVerbatimStringType(): void
{
$data = "=15\r\ntxt:Some string\r\n";
$actualResponse = $this->strategy->parseData($data);
$this->assertSame(
['type' => 'verbatimString', 'value' => 15, 'offset' => Resp3Strategy::VERBATIM_STRING_EXTENSION_OFFSET],
$actualResponse
);
}
/**
* @dataProvider bigNumberProvider
* @group disconnected
* @param string $data
* @param int|float $expectedValue
* @return void
*/
public function testParseDataReturnsIntegerOrFloatOnBigNumberType(string $data, $expectedValue): void
{
$actualResponse = $this->strategy->parseData($data);
$this->assertSame($expectedValue, $actualResponse);
}
/**
* @group disconnected
* @return void
*/
public function testParseDataReturnsArrayOnMapType(): void
{
$data = "%2\r\n+first\r\n:1\r\n+second\r\n:2";
$actualResponse = $this->strategy->parseData($data);
$this->assertSame(['type' => 'map', 'value' => 2], $actualResponse);
}
/**
* @group disconnected
* @return void
*/
public function testParseDataReturnsArrayOnSetType(): void
{
$data = "~4\r\n+first\r\n:1\r\n+second\r\n:2";
$actualResponse = $this->strategy->parseData($data);
$this->assertSame(['type' => 'set', 'value' => 4], $actualResponse);
}
/**
* @group disconnected
* @return void
*/
public function testParseDataReturnsArrayOnPushType(): void
{
$data = ">4\r\n+pubsub\r\n+message\r\n+somechannel\r\n+this is the message";
$actualResponse = $this->strategy->parseData($data);
$this->assertSame(['type' => 'push', 'value' => 4], $actualResponse);
}
public function infinityProvider(): array
{
return [
'positive infinity' => [",inf\r\n", INF],
'negative infinity' => [",-inf\r\n", -INF],
];
}
public function nanProvider(): array
{
return [
'canonical nan' => [",nan\r\n"],
'negative nan' => [",-nan\r\n"],
'uppercase nan' => [",NAN\r\n"],
'nan with payload' => [",nan(ind)\r\n"],
];
}
public function booleanProvider(): array
{
return [
'true' => ["#t\r\n", true],
'false' => ["#f\r\n", false],
];
}
public function bigNumberProvider(): array
{
return [
'greater than integer limit' => [
"(3492890328409238509324850943850943825024385\r\n",
3492890328409238509324850943850943825024385,
],
'lower than integer limit' => [
"(34928903\r\n",
34928903,
],
];
}
}