blob: af669b6dc86a1a1f430ec15cd654b8c8269e9dc9 [file]
<?php
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
declare(strict_types=1);
namespace Test\Thrift\Unit\Lib\Protocol;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Constraint\Constraint;
use PHPUnit\Framework\Attributes\DataProvider;
use Thrift\Exception\TProtocolException;
use Thrift\Protocol\TBinaryProtocol;
use Thrift\Transport\TTransport;
use Thrift\Type\TType;
class TBinaryProtocolTest extends TestCase
{
private const VERSION_MASK = 0xffff0000;
private const VERSION_1 = 0x80010000;
#[DataProvider('writeMessageBeginDataProvider')]
public function testWriteMessageBegin(
$strictWrite,
$name,
$type,
$seqid,
$writeCallsParams,
$writeCallsResults,
$expectedResult
) {
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, $strictWrite);
$transport->expects($this->exactly(count($writeCallsParams)))
->method('write')
->willReturnCallback(function (...$callArgs) use ($writeCallsParams, $writeCallsResults) {
static $iteration = 0;
$expected = $writeCallsParams[$iteration];
foreach ($expected as $i => $exp) {
if ($exp instanceof Constraint) {
$this->assertThat($callArgs[$i], $exp);
} else {
$this->assertSame($exp, $callArgs[$i]);
}
}
return $writeCallsResults[$iteration++];
});
$result = $protocol->writeMessageBegin($name, $type, $seqid);
$this->assertEquals($expectedResult, $result);
}
public static function writeMessageBeginDataProvider()
{
$type = TType::STRING;
$seqid = 555;
yield 'strictWrite=true' => [
'strictWrite' => true,
'name' => 'testName',
'type' => $type,
'seqid' => $seqid,
'writeCallsParams' => [
[pack('N', self::VERSION_1 | $type)], #writeI32
[pack('N', strlen('testName'))], #writeStringLen
['testName'], #writeString
[pack('N', $seqid)], #writeI32
],
'writeCallsResults' => [
4,
4,
8,
4,
],
'expectedResult' => 20,
];
yield 'strictWrite=false' => [
'strictWrite' => false,
'name' => 'testName',
'type' => $type,
'seqid' => $seqid,
'writeCallsParams' => [
[pack('N', strlen('testName'))], #writeStringLen
['testName'], #writeString
[pack('c', $type)], #writeByte
[pack('N', $seqid)], #writeI32
],
'writeCallsResults' => [
4,
8,
1,
4,
],
'expectedResult' => 17,
];
}
public function testWriteMessageEnd()
{
$transport = $this->createStub(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$this->assertEquals(0, $protocol->writeMessageEnd());
}
public function testWriteStructBegin()
{
$transport = $this->createStub(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$this->assertEquals(0, $protocol->writeStructBegin('testName'));
}
public function testWriteStructEnd()
{
$transport = $this->createStub(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$this->assertEquals(0, $protocol->writeStructEnd());
}
public function testWriteFieldBegin()
{
$fieldName = 'testName';
$fieldType = TType::STRING;
$fieldId = 555;
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$expectedWriteArgs = [
[pack('c', $fieldType)], #writeByte
[pack('n', $fieldId)], #writeI16
];
$writeReturns = [1, 2];
$transport
->expects($this->exactly(2))
->method('write')
->willReturnCallback(function (...$callArgs) use ($expectedWriteArgs, $writeReturns) {
static $iteration = 0;
$expected = $expectedWriteArgs[$iteration];
foreach ($expected as $i => $exp) {
if ($exp instanceof Constraint) {
$this->assertThat($callArgs[$i], $exp);
} else {
$this->assertSame($exp, $callArgs[$i]);
}
}
return $writeReturns[$iteration++];
});
$this->assertEquals(3, $protocol->writeFieldBegin($fieldName, $fieldType, $fieldId));
}
public function testWriteFieldEnd()
{
$transport = $this->createStub(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$this->assertEquals(0, $protocol->writeFieldEnd());
}
public function testWriteFieldStop()
{
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$transport
->expects($this->once())
->method('write')
->with(pack('c', TType::STOP)); #writeByte
$this->assertEquals(1, $protocol->writeFieldStop());
}
public function testWriteMapBegin()
{
$keyType = TType::I32;
$valType = TType::STRING;
$size = 99;
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$expectedWriteArgs = [
[pack('c', $keyType)], #writeByte
[pack('c', $valType)], #writeByte
[pack('N', $size)], #writeI32
];
$writeReturns = [1, 1, 4];
$transport
->expects($this->exactly(3))
->method('write')
->willReturnCallback(function (...$callArgs) use ($expectedWriteArgs, $writeReturns) {
static $iteration = 0;
$expected = $expectedWriteArgs[$iteration];
foreach ($expected as $i => $exp) {
if ($exp instanceof Constraint) {
$this->assertThat($callArgs[$i], $exp);
} else {
$this->assertSame($exp, $callArgs[$i]);
}
}
return $writeReturns[$iteration++];
});
$this->assertEquals(6, $protocol->writeMapBegin($keyType, $valType, $size));
}
public function testWriteMapEnd()
{
$transport = $this->createStub(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$this->assertEquals(0, $protocol->writeMapEnd());
}
public function testWriteListBegin()
{
$elemType = TType::I32;
$size = 99;
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$expectedWriteArgs = [
[pack('c', $elemType)], #writeByte
[pack('N', $size)], #writeI32
];
$writeReturns = [1, 4];
$transport
->expects($this->exactly(2))
->method('write')
->willReturnCallback(function (...$callArgs) use ($expectedWriteArgs, $writeReturns) {
static $iteration = 0;
$expected = $expectedWriteArgs[$iteration];
foreach ($expected as $i => $exp) {
if ($exp instanceof Constraint) {
$this->assertThat($callArgs[$i], $exp);
} else {
$this->assertSame($exp, $callArgs[$i]);
}
}
return $writeReturns[$iteration++];
});
$this->assertEquals(5, $protocol->writeListBegin($elemType, $size));
}
public function testWriteListEnd()
{
$transport = $this->createStub(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$this->assertEquals(0, $protocol->writeListEnd());
}
public function testWriteSetBegin()
{
$elemType = TType::I32;
$size = 99;
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$expectedWriteArgs = [
[pack('c', $elemType)], #writeByte
[pack('N', $size)], #writeI32
];
$writeReturns = [1, 4];
$transport
->expects($this->exactly(2))
->method('write')
->willReturnCallback(function (...$callArgs) use ($expectedWriteArgs, $writeReturns) {
static $iteration = 0;
$expected = $expectedWriteArgs[$iteration];
foreach ($expected as $i => $exp) {
if ($exp instanceof Constraint) {
$this->assertThat($callArgs[$i], $exp);
} else {
$this->assertSame($exp, $callArgs[$i]);
}
}
return $writeReturns[$iteration++];
});
$this->assertEquals(5, $protocol->writeSetBegin($elemType, $size));
}
public function testWriteSetEnd()
{
$transport = $this->createStub(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$this->assertEquals(0, $protocol->writeSetEnd());
}
public function testWriteBool()
{
$value = true;
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$transport
->expects($this->once())
->method('write')
->with(pack('c', (int)$value)); #writeByte
$this->assertEquals(1, $protocol->writeBool($value));
}
public function testWriteByte()
{
$value = 1;
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$transport
->expects($this->once())
->method('write')
->with(pack('c', $value)); #writeByte
$this->assertEquals(1, $protocol->writeByte($value));
}
public function testWriteI16()
{
$value = 1;
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$transport
->expects($this->once())
->method('write')
->with(pack('n', $value)); #writeI16
$this->assertEquals(2, $protocol->writeI16($value));
}
public function testWriteI32()
{
$value = 1;
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$transport
->expects($this->once())
->method('write')
->with(pack('N', $value)); #writeI32
$this->assertEquals(4, $protocol->writeI32($value));
}
public function testWriteI64For32BitArchitecture()
{
if (PHP_INT_SIZE !== 4) {
$this->markTestSkipped('Test is only for 32 bit architecture');
}
$value = 1;
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$neg = $value < 0;
if ($neg) {
$value *= -1;
}
$hi = (int)($value / 4294967296);
$lo = (int)$value;
if ($neg) {
$hi = ~$hi;
$lo = ~$lo;
if (($lo & (int)0xffffffff) == (int)0xffffffff) {
$lo = 0;
$hi++;
} else {
$lo++;
}
}
$transport
->expects($this->once())
->method('write')
->with(pack('N2', $hi, $lo)); #writeI64
$this->assertEquals(8, $protocol->writeI64($value));
}
public function testWriteI64For64BitArchitecture()
{
if (PHP_INT_SIZE == 4) {
$this->markTestSkipped('Test is only for 64 bit architecture');
}
$value = 1;
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$hi = $value >> 32;
$lo = $value & 0xFFFFFFFF;
$transport
->expects($this->once())
->method('write')
->with(pack('N2', $hi, $lo)); #writeI64
$this->assertEquals(8, $protocol->writeI64($value));
}
public function testWriteDouble()
{
$value = 1;
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$transport
->expects($this->once())
->method('write')
->with(strrev(pack('d', $value))); #writeDouble
$this->assertEquals(8, $protocol->writeDouble($value));
}
public function testWriteString()
{
$value = 'string';
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$expectedWriteArgs = [
[pack('N', strlen($value))], #writeI32,
[$value], #write,
];
$writeReturns = [4, 6];
$transport
->expects($this->exactly(2))
->method('write')
->willReturnCallback(function (...$callArgs) use ($expectedWriteArgs, $writeReturns) {
static $iteration = 0;
$expected = $expectedWriteArgs[$iteration];
foreach ($expected as $i => $exp) {
if ($exp instanceof Constraint) {
$this->assertThat($callArgs[$i], $exp);
} else {
$this->assertSame($exp, $callArgs[$i]);
}
}
return $writeReturns[$iteration++];
});
$this->assertEquals(10, $protocol->writeString($value));
}
public function testWriteUuid()
{
$uuid = '01234567-89ab-cdef-0123-456789abcdef';
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$transport
->expects($this->once())
->method('write')
->with(hex2bin('0123456789abcdef0123456789abcdef'));
$this->assertEquals(16, $protocol->writeUuid($uuid));
}
public function testReadUuid()
{
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$transport
->expects($this->once())
->method('readAll')
->with(16)
->willReturn(hex2bin('0123456789abcdef0123456789abcdef'));
$this->assertEquals(16, $protocol->readUuid($value));
$this->assertEquals('01234567-89ab-cdef-0123-456789abcdef', $value);
}
#[DataProvider('readMessageBeginDataProvider')]
public function testReadMessageBegin(
$strictRead,
$readCallsParams,
$readCallsResults,
$expectedReadLengthResult,
$expectedName,
$expectedType,
$expectedSeqid,
$expectedException = null,
$expectedExceptionMessage = null,
$expectedExceptionCode = null
) {
if ($expectedException) {
$this->expectException($expectedException);
$this->expectExceptionMessage($expectedExceptionMessage);
$this->expectExceptionCode($expectedExceptionCode);
}
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, $strictRead, true);
$transport->expects($this->exactly(count($readCallsParams)))
->method('readAll')
->willReturnCallback(function (...$callArgs) use ($readCallsParams, $readCallsResults) {
static $iteration = 0;
$expected = $readCallsParams[$iteration];
foreach ($expected as $i => $exp) {
if ($exp instanceof Constraint) {
$this->assertThat($callArgs[$i], $exp);
} else {
$this->assertSame($exp, $callArgs[$i]);
}
}
return $readCallsResults[$iteration++];
});
$result = $protocol->readMessageBegin($name, $type, $seqid);
$this->assertEquals($expectedReadLengthResult, $result);
$this->assertEquals($expectedName, $name);
$this->assertEquals($expectedType, $type);
$this->assertEquals($expectedSeqid, $seqid);
}
public static function readMessageBeginDataProvider()
{
yield 'strictRead=true' => [
'strictRead' => true,
'readCallsParams' => [
[4], #readI32
[4], #readStringLen
[8], #readString
[4], #readI32
],
'readCallsResults' => [
pack('N', 0x80010000 | TType::STRING), #version
pack('N', strlen('testName')),
'testName',
pack('N', 555),
],
'expectedReadLengthResult' => 20,
'expectedName' => 'testName',
'expectedType' => TType::STRING,
'expectedSeqid' => 555,
];
yield 'incorrect version' => [
'strictRead' => true,
'readCallsParams' => [
[4], #readI32
],
'readCallsResults' => [
pack('N', 0x80000000 | TType::STRING), #version
],
'expectedReadLengthResult' => 4,
'expectedName' => '',
'expectedType' => 0,
'expectedSeqid' => 0,
'expectedException' => TProtocolException::class,
'expectedExceptionMessage' => 'Bad version identifier: -2147483637',
'expectedExceptionCode' => TProtocolException::BAD_VERSION,
];
yield 'strictRead=false' => [
'strictRead' => false,
'readCallsParams' => [
[4], #readStringLen
[8], #readString
[1], #readByte
[4], #readI32
],
'readCallsResults' => [
pack('N', strlen('testName')),
'testName',
pack('c', TType::STRING),
pack('N', 555),
],
'expectedReadLengthResult' => 17,
'expectedName' => 'testName',
'expectedType' => TType::STRING,
'expectedSeqid' => 555,
];
yield 'strictRead=true without version' => [
'strictRead' => true,
'readCallsParams' => [
[4], #readStringLen
],
'readCallsResults' => [
pack('N', strlen('testName')),
],
'expectedReadLengthResult' => 17,
'expectedName' => 'testName',
'expectedType' => TType::STRING,
'expectedSeqid' => 555,
'expectedException' => TProtocolException::class,
'expectedExceptionMessage' => 'No version identifier, old protocol client?',
'expectedExceptionCode' => TProtocolException::BAD_VERSION,
];
}
public function testReadMessageEnd()
{
$transport = $this->createStub(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$this->assertEquals(0, $protocol->readMessageEnd());
}
public function testReadStructBegin()
{
$transport = $this->createStub(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$this->assertEquals(0, $protocol->readStructBegin($name));
$this->assertEquals('', $name);
}
public function testReadStructEnd()
{
$transport = $this->createStub(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$this->assertEquals(0, $protocol->readStructEnd());
}
#[DataProvider('readFieldBeginDataProvider')]
public function testReadFieldBegin(
$storedFieldType,
$readCallsParams,
$readCallsResults,
$expectedResult,
$expectedName,
$expectedFieldType,
$expectedFieldId
) {
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$transport
->expects($this->exactly(count($readCallsParams)))
->method('readAll')
->willReturnCallback(function (...$callArgs) use ($readCallsParams, $readCallsResults) {
static $iteration = 0;
$expected = $readCallsParams[$iteration];
foreach ($expected as $i => $exp) {
if ($exp instanceof Constraint) {
$this->assertThat($callArgs[$i], $exp);
} else {
$this->assertSame($exp, $callArgs[$i]);
}
}
return $readCallsResults[$iteration++];
});
$this->assertEquals($expectedResult, $protocol->readFieldBegin($name, $fieldType, $fieldId));
$this->assertEquals($expectedName, $name);
$this->assertEquals($expectedFieldType, $fieldType);
$this->assertEquals($expectedFieldId, $fieldId);
}
public static function readFieldBeginDataProvider()
{
yield 'default' => [
'storedFieldType' => TType::STRING,
'readCallsParams' => [
[1], #readByte
[2], #readI16
],
'readCallsResults' => [
pack('c', TType::STRING),
pack('n', 555),
],
'expectedResult' => 3,
'expectedName' => '',
'expectedFieldType' => TType::STRING,
'expectedFieldId' => 555,
];
yield 'stop field' => [
'storedFieldType' => TType::STOP,
'readCallsParams' => [
[1], #readByte
],
'readCallsResults' => [
pack('c', TType::STOP),
],
'expectedResult' => 1,
'expectedName' => '',
'expectedFieldType' => 0,
'expectedFieldId' => 0,
];
}
public function testReadFieldEnd()
{
$transport = $this->createStub(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$this->assertEquals(0, $protocol->readFieldEnd());
}
public function testReadMapBegin()
{
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$expectedReadAllArgs = [
[1], #readByte
[1], #readByte
[4], #readI32
];
$readAllReturns = [
pack('c', TType::I32),
pack('c', TType::STRING),
pack('N', 555),
];
$transport
->expects($this->exactly(3))
->method('readAll')
->willReturnCallback(function (...$callArgs) use ($expectedReadAllArgs, $readAllReturns) {
static $iteration = 0;
$expected = $expectedReadAllArgs[$iteration];
foreach ($expected as $i => $exp) {
if ($exp instanceof Constraint) {
$this->assertThat($callArgs[$i], $exp);
} else {
$this->assertSame($exp, $callArgs[$i]);
}
}
return $readAllReturns[$iteration++];
});
$this->assertEquals(6, $protocol->readMapBegin($keyType, $valType, $size));
$this->assertEquals(TType::I32, $keyType);
$this->assertEquals(TType::STRING, $valType);
$this->assertEquals(555, $size);
}
public function testReadMapEnd()
{
$transport = $this->createStub(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$this->assertEquals(0, $protocol->readMapEnd());
}
public function testReadListBegin()
{
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$expectedReadAllArgs = [
[1], #readByte
[4], #readI32
];
$readAllReturns = [
pack('c', TType::I32),
pack('N', 555),
];
$transport
->expects($this->exactly(2))
->method('readAll')
->willReturnCallback(function (...$callArgs) use ($expectedReadAllArgs, $readAllReturns) {
static $iteration = 0;
$expected = $expectedReadAllArgs[$iteration];
foreach ($expected as $i => $exp) {
if ($exp instanceof Constraint) {
$this->assertThat($callArgs[$i], $exp);
} else {
$this->assertSame($exp, $callArgs[$i]);
}
}
return $readAllReturns[$iteration++];
});
$this->assertEquals(5, $protocol->readListBegin($elemType, $size));
$this->assertEquals(TType::I32, $elemType);
$this->assertEquals(555, $size);
}
public function testReadListEnd()
{
$transport = $this->createStub(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$this->assertEquals(0, $protocol->readListEnd());
}
public function testReadSetBegin()
{
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$expectedReadAllArgs = [
[1], #readByte
[4], #readI32
];
$readAllReturns = [
pack('c', TType::I32),
pack('N', 555),
];
$transport
->expects($this->exactly(2))
->method('readAll')
->willReturnCallback(function (...$callArgs) use ($expectedReadAllArgs, $readAllReturns) {
static $iteration = 0;
$expected = $expectedReadAllArgs[$iteration];
foreach ($expected as $i => $exp) {
if ($exp instanceof Constraint) {
$this->assertThat($callArgs[$i], $exp);
} else {
$this->assertSame($exp, $callArgs[$i]);
}
}
return $readAllReturns[$iteration++];
});
$this->assertEquals(5, $protocol->readSetBegin($elemType, $size));
$this->assertEquals(TType::I32, $elemType);
$this->assertEquals(555, $size);
}
public function testReadSetEnd()
{
$transport = $this->createStub(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$this->assertEquals(0, $protocol->readSetEnd());
}
public function testReadBool()
{
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$transport
->expects($this->once())
->method('readAll')
->with(1) #readByte
->willReturn(pack('c', 1));
$this->assertEquals(1, $protocol->readBool($value));
$this->assertTrue($value);
}
public function testReadByte()
{
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$transport
->expects($this->once())
->method('readAll')
->with(1) #readByte
->willReturn(pack('c', 1));
$this->assertEquals(1, $protocol->readByte($value));
$this->assertEquals(1, $value);
}
#[DataProvider('readI16DataProvider')]
public function testReadI16(
$storedValue,
$expectedValue
) {
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$transport
->expects($this->once())
->method('readAll')
->with(2) #readI16
->willReturn(pack('n', $storedValue));
$this->assertEquals(2, $protocol->readI16($value));
$this->assertEquals($expectedValue, $value);
}
public static function readI16DataProvider()
{
yield 'positive' => [1, 1];
yield 'negative' => [-1, -1];
}
#[DataProvider('readI16DataProvider')]
public function testReadI32(
$storedValue,
$expectedValue
) {
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$transport
->expects($this->once())
->method('readAll')
->with(4) #readI32
->willReturn(pack('N', $storedValue));
$this->assertEquals(4, $protocol->readI32($value));
$this->assertEquals($expectedValue, $value);
}
public function readI32DataProvider()
{
yield 'positive' => [1, 1];
yield 'negative' => [-1, -1];
}
#[DataProvider('readI64For32BitArchitectureDataProvider')]
public function testReadI64For32BitArchitecture(
$storedValue,
$expectedValue
) {
if (PHP_INT_SIZE !== 4) {
$this->markTestSkipped('Test is only for 32 bit architecture');
}
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$neg = $storedValue < 0;
if ($neg) {
$storedValue *= -1;
}
$hi = (int)($storedValue / 4294967296);
$lo = (int)$storedValue;
if ($neg) {
$hi = ~$hi;
$lo = ~$lo;
if (($lo & (int)0xffffffff) == (int)0xffffffff) {
$lo = 0;
$hi++;
} else {
$lo++;
}
}
$transport
->expects($this->once())
->method('write')
->with(pack('N2', $hi, $lo)); #writeI64
$this->assertEquals(8, $protocol->readI64($value));
$this->assertEquals($expectedValue, $value);
}
public static function readI64For32BitArchitectureDataProvider()
{
$storedValueRepresent = function ($value) {
// PHP_INT_MIN (-2^63) cannot be safely negated:
// -PHP_INT_MIN overflows the 64-bit signed integer range.
if ($value === PHP_INT_MIN) {
return pack('N2', 0x80000000, 0x00000000);
}
$neg = $value < 0;
if ($neg) {
$value *= -1;
}
$hi = (int)($value / 4294967296);
$lo = (int)$value;
if ($neg) {
$hi = ~$hi;
$lo = ~$lo;
if (($lo & (int)0xffffffff) == (int)0xffffffff) {
$lo = 0;
$hi++;
} else {
$lo++;
}
}
return pack('N2', $hi, $lo);
};
yield 'positive' => [
'storedValue' => $storedValueRepresent(1),
'expectedValue' => 1,
];
yield 'max' => [
'storedValue' => $storedValueRepresent(PHP_INT_MAX),
'expectedValue' => PHP_INT_MAX,
];
yield 'min' => [
'storedValue' => $storedValueRepresent(PHP_INT_MIN),
'expectedValue' => PHP_INT_MIN,
];
yield 'negative' => [
'storedValue' => $storedValueRepresent(-1),
'expectedValue' => -1,
];
}
#[DataProvider('readI64For64BitArchitectureDataProvider')]
public function testReadI64For64BitArchitecture(
$storedValue,
$expectedValue
) {
if (PHP_INT_SIZE == 4) {
$this->markTestSkipped('Test is only for 64 bit architecture');
}
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$transport
->expects($this->once())
->method('readAll')
->with(8) #readI64
->willReturn($storedValue);
$this->assertEquals(8, $protocol->readI64($value));
$this->assertEquals($expectedValue, $value);
}
public static function readI64For64BitArchitectureDataProvider()
{
$storedValueRepresent = function ($value) {
$hi = $value >> 32;
$lo = $value & 0xFFFFFFFF;
return pack('N2', $hi, $lo);
};
yield 'positive' => [
'storedValue' => $storedValueRepresent(1),
'expectedValue' => 1,
];
yield 'max' => [
'storedValue' => $storedValueRepresent(PHP_INT_MAX),
'expectedValue' => PHP_INT_MAX,
];
yield 'min' => [
'storedValue' => $storedValueRepresent(PHP_INT_MIN),
'expectedValue' => PHP_INT_MIN,
];
yield 'negative' => [
'storedValue' => $storedValueRepresent(-1),
'expectedValue' => -1,
];
}
public function testReadDouble()
{
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$transport
->expects($this->once())
->method('readAll')
->with(8) #readDouble
->willReturn(strrev(pack('d', 789)));
$this->assertEquals(8, $protocol->readDouble($value));
$this->assertEquals(789, $value);
}
#[DataProvider('readStringDataProvider')]
public function testReadString(
$readCallsParams,
$readCallsResults,
$expectedLength,
$expectedValue
) {
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$transport
->expects($this->exactly(count($readCallsParams)))
->method('readAll')
->willReturnCallback(function (...$callArgs) use ($readCallsParams, $readCallsResults) {
static $iteration = 0;
$expected = $readCallsParams[$iteration];
foreach ($expected as $i => $exp) {
if ($exp instanceof Constraint) {
$this->assertThat($callArgs[$i], $exp);
} else {
$this->assertSame($exp, $callArgs[$i]);
}
}
return $readCallsResults[$iteration++];
});
$this->assertEquals($expectedLength, $protocol->readString($value));
$this->assertEquals($expectedValue, $value);
}
public static function readStringDataProvider()
{
$storedValue = '';
yield 'empty' => [
'readCallsParams' => [
[4]
],
'readCallsResults' => [
pack('N', strlen($storedValue))
],
'expectedLength' => 4,
'expectedValue' => '',
];
$storedValue = 'string';
yield 'non-empty' => [
'readCallsParams' => [
[4],
[strlen($storedValue)]
],
'readCallsResults' => [
pack('N', strlen($storedValue)),
$storedValue
],
'expectedLength' => 10,
'expectedValue' => 'string',
];
}
public function testReadMapBeginRejectsNegativeSize()
{
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$readAllReturns = [
pack('c', TType::I32),
pack('c', TType::STRING),
pack('N', 0xffffffff),
];
$transport->method('readAll')
->willReturnCallback(function () use ($readAllReturns) {
static $iteration = 0;
return $readAllReturns[$iteration++];
});
$this->expectException(TProtocolException::class);
$this->expectExceptionCode(TProtocolException::NEGATIVE_SIZE);
$protocol->readMapBegin($keyType, $valType, $size);
}
public function testReadListBeginRejectsNegativeSize()
{
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$readAllReturns = [
pack('c', TType::I32),
pack('N', 0xffffffff),
];
$transport->method('readAll')
->willReturnCallback(function () use ($readAllReturns) {
static $iteration = 0;
return $readAllReturns[$iteration++];
});
$this->expectException(TProtocolException::class);
$this->expectExceptionCode(TProtocolException::NEGATIVE_SIZE);
$protocol->readListBegin($elemType, $size);
}
public function testReadSetBeginRejectsNegativeSize()
{
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$readAllReturns = [
pack('c', TType::I32),
pack('N', 0xffffffff),
];
$transport->method('readAll')
->willReturnCallback(function () use ($readAllReturns) {
static $iteration = 0;
return $readAllReturns[$iteration++];
});
$this->expectException(TProtocolException::class);
$this->expectExceptionCode(TProtocolException::NEGATIVE_SIZE);
$protocol->readSetBegin($elemType, $size);
}
public function testReadStringRejectsNegativeSize()
{
$transport = $this->createMock(TTransport::class);
$protocol = new TBinaryProtocol($transport, false, false);
$transport->method('readAll')
->willReturn(pack('N', 0xffffffff));
$this->expectException(TProtocolException::class);
$this->expectExceptionCode(TProtocolException::NEGATIVE_SIZE);
$protocol->readString($value);
}
}