| <?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\Attributes\DataProvider; |
| use Thrift\Exception\TException; |
| use Thrift\Protocol\TSimpleJSONProtocol; |
| use Thrift\Protocol\SimpleJSON\CollectionMapKeyException; |
| use Thrift\Transport\TMemoryBuffer; |
| use Thrift\Transport\TTransport; |
| use Thrift\Type\TType; |
| |
| class TSimpleJSONProtocolTest extends TestCase |
| { |
| /** |
| * Reading methods. |
| * |
| * simplejson is not meant to be read back into thrift |
| * - see http://wiki.apache.org/thrift/ThriftUsageJava |
| * - use JSON instead |
| * |
| */ |
| /** |
| * All read methods throw TException("Not implemented") in |
| * TSimpleJSONProtocol. The dispatch arity (count of by-ref output |
| * params, including nullable scalars) is all we need to invoke the |
| * typed signatures correctly before the exception propagates. |
| */ |
| #[DataProvider('readDataProvider')] |
| public function testRead(string $methodName, int $argCount): void |
| { |
| $this->expectException(TException::class); |
| $this->expectExceptionMessage("Not implemented"); |
| |
| $transport = $this->createStub(TTransport::class); |
| $protocol = new TSimpleJSONProtocol($transport); |
| |
| $args = array_fill(0, $argCount, null); |
| $protocol->$methodName(...$args); |
| } |
| |
| public static function readDataProvider(): \Generator |
| { |
| yield 'readMessageBegin' => ['readMessageBegin', 3]; |
| yield 'readMessageEnd' => ['readMessageEnd', 0]; |
| yield 'readStructBegin' => ['readStructBegin', 1]; |
| yield 'readStructEnd' => ['readStructEnd', 0]; |
| yield 'readFieldBegin' => ['readFieldBegin', 3]; |
| yield 'readFieldEnd' => ['readFieldEnd', 0]; |
| yield 'readMapBegin' => ['readMapBegin', 3]; |
| yield 'readMapEnd' => ['readMapEnd', 0]; |
| yield 'readListBegin' => ['readListBegin', 2]; |
| yield 'readListEnd' => ['readListEnd', 0]; |
| yield 'readSetBegin' => ['readSetBegin', 2]; |
| yield 'readSetEnd' => ['readSetEnd', 0]; |
| yield 'readBool' => ['readBool', 1]; |
| yield 'readByte' => ['readByte', 1]; |
| yield 'readI16' => ['readI16', 1]; |
| yield 'readI32' => ['readI32', 1]; |
| yield 'readI64' => ['readI64', 1]; |
| yield 'readDouble' => ['readDouble', 1]; |
| yield 'readString' => ['readString', 1]; |
| } |
| |
| #[DataProvider('writeScalarProvider')] |
| public function testWriteScalar(string $writeMethod, $value, string $expectedJson) |
| { |
| $transport = new TMemoryBuffer(); |
| $protocol = new TSimpleJSONProtocol($transport); |
| |
| $protocol->writeListBegin(TType::STRING, 1); |
| $protocol->$writeMethod($value); |
| $protocol->writeListEnd(); |
| |
| $this->assertSame('[' . $expectedJson . ']', $transport->getBuffer()); |
| } |
| |
| public static function writeScalarProvider() |
| { |
| yield 'bool true' => [ |
| 'writeMethod' => 'writeBool', |
| 'value' => true, |
| 'expectedJson' => '1', |
| ]; |
| yield 'bool false' => [ |
| 'writeMethod' => 'writeBool', |
| 'value' => false, |
| 'expectedJson' => '0', |
| ]; |
| yield 'byte zero' => [ |
| 'writeMethod' => 'writeByte', |
| 'value' => 0, |
| 'expectedJson' => '0', |
| ]; |
| yield 'byte max' => [ |
| 'writeMethod' => 'writeByte', |
| 'value' => 127, |
| 'expectedJson' => '127', |
| ]; |
| yield 'byte min' => [ |
| 'writeMethod' => 'writeByte', |
| 'value' => -128, |
| 'expectedJson' => '-128', |
| ]; |
| yield 'i16 max' => [ |
| 'writeMethod' => 'writeI16', |
| 'value' => 32767, |
| 'expectedJson' => '32767', |
| ]; |
| yield 'i16 min' => [ |
| 'writeMethod' => 'writeI16', |
| 'value' => -32768, |
| 'expectedJson' => '-32768', |
| ]; |
| yield 'i16 zero' => [ |
| 'writeMethod' => 'writeI16', |
| 'value' => 0, |
| 'expectedJson' => '0', |
| ]; |
| yield 'i32 max' => [ |
| 'writeMethod' => 'writeI32', |
| 'value' => 2147483647, |
| 'expectedJson' => '2147483647', |
| ]; |
| yield 'i32 zero' => [ |
| 'writeMethod' => 'writeI32', |
| 'value' => 0, |
| 'expectedJson' => '0', |
| ]; |
| yield 'i32 negative' => [ |
| 'writeMethod' => 'writeI32', |
| 'value' => -1, |
| 'expectedJson' => '-1', |
| ]; |
| yield 'i64 large' => [ |
| 'writeMethod' => 'writeI64', |
| 'value' => 1000000000, |
| 'expectedJson' => '1000000000', |
| ]; |
| yield 'i64 zero' => [ |
| 'writeMethod' => 'writeI64', |
| 'value' => 0, |
| 'expectedJson' => '0', |
| ]; |
| yield 'i64 negative' => [ |
| 'writeMethod' => 'writeI64', |
| 'value' => -1000000000, |
| 'expectedJson' => '-1000000000', |
| ]; |
| yield 'double pi' => [ |
| 'writeMethod' => 'writeDouble', |
| 'value' => 3.14, |
| 'expectedJson' => json_encode(3.14), |
| ]; |
| yield 'double negative' => [ |
| 'writeMethod' => 'writeDouble', |
| 'value' => -2.5, |
| 'expectedJson' => json_encode(-2.5), |
| ]; |
| yield 'double large' => [ |
| 'writeMethod' => 'writeDouble', |
| 'value' => 1.0e10, |
| 'expectedJson' => json_encode(1.0e10), |
| ]; |
| yield 'double zero' => [ |
| 'writeMethod' => 'writeDouble', |
| 'value' => 0.0, |
| 'expectedJson' => json_encode(0.0), |
| ]; |
| yield 'string simple' => [ |
| 'writeMethod' => 'writeString', |
| 'value' => 'hello', |
| 'expectedJson' => '"hello"', |
| ]; |
| yield 'string with quotes' => [ |
| 'writeMethod' => 'writeString', |
| 'value' => 'quote "inside"', |
| 'expectedJson' => '"quote \"inside\""', |
| ]; |
| yield 'string with path' => [ |
| 'writeMethod' => 'writeString', |
| 'value' => 'path/to/file', |
| 'expectedJson' => '"path/to/file"', |
| ]; |
| yield 'string empty' => [ |
| 'writeMethod' => 'writeString', |
| 'value' => '', |
| 'expectedJson' => '""', |
| ]; |
| yield 'string unicode' => [ |
| 'writeMethod' => 'writeString', |
| 'value' => 'привіт', |
| 'expectedJson' => json_encode('привіт'), |
| ]; |
| yield 'uuid' => [ |
| 'writeMethod' => 'writeUuid', |
| 'value' => '12345678-1234-5678-1234-567812345678', |
| 'expectedJson' => '"12345678-1234-5678-1234-567812345678"', |
| ]; |
| } |
| |
| #[DataProvider('writeContainerProvider')] |
| public function testWriteContainer(array $operations, string $expectedJson) |
| { |
| $transport = new TMemoryBuffer(); |
| $protocol = new TSimpleJSONProtocol($transport); |
| |
| foreach ($operations as [$method, $args]) { |
| $protocol->$method(...$args); |
| } |
| |
| $this->assertSame($expectedJson, $transport->getBuffer()); |
| } |
| |
| public static function writeContainerProvider() |
| { |
| yield 'list of integers' => [ |
| 'operations' => [ |
| ['writeListBegin', [TType::I32, 3]], |
| ['writeI32', [1]], |
| ['writeI32', [2]], |
| ['writeI32', [3]], |
| ['writeListEnd', []], |
| ], |
| 'expectedJson' => '[1,2,3]', |
| ]; |
| yield 'empty list' => [ |
| 'operations' => [ |
| ['writeListBegin', [TType::I32, 0]], |
| ['writeListEnd', []], |
| ], |
| 'expectedJson' => '[]', |
| ]; |
| yield 'set of integers' => [ |
| 'operations' => [ |
| ['writeSetBegin', [TType::I32, 2]], |
| ['writeI32', [10]], |
| ['writeI32', [20]], |
| ['writeSetEnd', []], |
| ], |
| 'expectedJson' => '[10,20]', |
| ]; |
| yield 'map string to i32' => [ |
| 'operations' => [ |
| ['writeMapBegin', [TType::STRING, TType::I32, 2]], |
| ['writeString', ['key1']], |
| ['writeI32', [100]], |
| ['writeString', ['key2']], |
| ['writeI32', [200]], |
| ['writeMapEnd', []], |
| ], |
| 'expectedJson' => '{"key1":100,"key2":200}', |
| ]; |
| yield 'empty map' => [ |
| 'operations' => [ |
| ['writeMapBegin', [TType::STRING, TType::I32, 0]], |
| ['writeMapEnd', []], |
| ], |
| 'expectedJson' => '{}', |
| ]; |
| yield 'nested list' => [ |
| 'operations' => [ |
| ['writeListBegin', [TType::LST, 2]], |
| ['writeListBegin', [TType::I32, 2]], |
| ['writeI32', [1]], |
| ['writeI32', [2]], |
| ['writeListEnd', []], |
| ['writeListBegin', [TType::I32, 2]], |
| ['writeI32', [3]], |
| ['writeI32', [4]], |
| ['writeListEnd', []], |
| ['writeListEnd', []], |
| ], |
| 'expectedJson' => '[[1,2],[3,4]]', |
| ]; |
| } |
| |
| #[DataProvider('writeStructuralProvider')] |
| public function testWriteStructural(array $operations, string $expectedJson) |
| { |
| $transport = new TMemoryBuffer(); |
| $protocol = new TSimpleJSONProtocol($transport); |
| |
| foreach ($operations as [$method, $args]) { |
| $protocol->$method(...$args); |
| } |
| |
| $this->assertSame($expectedJson, $transport->getBuffer()); |
| } |
| |
| public static function writeStructuralProvider() |
| { |
| yield 'message begin' => [ |
| 'operations' => [ |
| ['writeMessageBegin', ['name', 1, 42]], |
| ], |
| 'expectedJson' => '["name",1,42', |
| ]; |
| yield 'message begin and end' => [ |
| 'operations' => [ |
| ['writeMessageBegin', ['name', 1, 42]], |
| ['writeMessageEnd', []], |
| ], |
| 'expectedJson' => '["name",1,42]', |
| ]; |
| yield 'struct with i32 field' => [ |
| 'operations' => [ |
| ['writeStructBegin', ['MyStruct']], |
| ['writeFieldBegin', ['field_name', TType::I32, 1]], |
| ['writeI32', [42]], |
| ['writeFieldEnd', []], |
| ['writeFieldStop', []], |
| ['writeStructEnd', []], |
| ], |
| 'expectedJson' => '{"field_name":42}', |
| ]; |
| yield 'struct with multiple fields' => [ |
| 'operations' => [ |
| ['writeStructBegin', ['MyStruct']], |
| ['writeFieldBegin', ['name', TType::STRING, 1]], |
| ['writeString', ['test']], |
| ['writeFieldEnd', []], |
| ['writeFieldBegin', ['value', TType::I32, 2]], |
| ['writeI32', [99]], |
| ['writeFieldEnd', []], |
| ['writeFieldStop', []], |
| ['writeStructEnd', []], |
| ], |
| 'expectedJson' => '{"name":"test","value":99}', |
| ]; |
| } |
| |
| public function testMapKeyCannotBeCollection() |
| { |
| $this->expectException(CollectionMapKeyException::class); |
| |
| $transport = new TMemoryBuffer(); |
| $protocol = new TSimpleJSONProtocol($transport); |
| |
| $protocol->writeMapBegin(TType::STRING, TType::I32, 1); |
| $protocol->writeMapBegin(TType::STRING, TType::I32, 1); |
| } |
| } |