| <?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. |
| */ |
| |
| namespace test\php; |
| |
| use Psr\Log\AbstractLogger; |
| use Psr\Log\LoggerInterface; |
| use Thrift\ClassLoader\ThriftClassLoader; |
| use Thrift\Transport\TBufferedTransport; |
| use Thrift\Transport\TFramedTransport; |
| use Thrift\Transport\TPsrHttpClient; |
| use Thrift\Transport\TSocketPool; |
| |
| /** @var \Composer\Autoload\ClassLoader $loader */ |
| $loader = require __DIR__ . '/../../vendor/autoload.php'; |
| |
| if (!isset($GEN_DIR)) { |
| $GEN_DIR = 'gen-php'; |
| } |
| if (!isset($MODE)) { |
| $MODE = 'normal'; |
| } |
| |
| if ($GEN_DIR == 'gen-php') { |
| $loader->addPsr4('', $GEN_DIR); |
| } else { |
| $loader = new ThriftClassLoader(); |
| $loader->registerDefinition('ThriftTest', $GEN_DIR); |
| $loader->register(); |
| } |
| |
| require_once __DIR__ . '/protocols.php'; |
| |
| /** |
| * Minimal PSR-3 logger that forwards every message to PHP's error_log |
| * (stderr in CLI mode). Used here to exercise the new logger-aware |
| * debugHandler path on the cross-test client. |
| */ |
| final class StderrLogger extends AbstractLogger implements LoggerInterface |
| { |
| /** |
| * @param string|\Stringable $level |
| * @param string|\Stringable $message |
| * @param array<mixed> $context |
| */ |
| public function log($level, $message, array $context = []): void |
| { |
| error_log('[' . (string) $level . '] ' . (string) $message); |
| } |
| } |
| |
| $port = 9090; |
| |
| foreach ($argv as $arg) { |
| if (substr($arg, 0, 7) == '--port=') { |
| $port = (int) substr($arg, 7); |
| } elseif (substr($arg, 0, 12) == '--transport=') { |
| $MODE = substr($arg, 12); |
| } elseif (substr($arg, 0, 11) == '--protocol=') { |
| $PROTO = substr($arg, 11); |
| } |
| } |
| |
| // TPsrHttpClient buffers internally, so no framed/buffered wrapper is needed. |
| // Inline mode passes the raw transport to the generated client without a |
| // protocol wrapper, matching the legacy code path. |
| $transport = match ($MODE) { |
| 'http' => new TPsrHttpClient(sprintf('http://127.0.0.1:%d/', $port)), |
| default => new TSocketPool(['localhost'], $port, false, new StderrLogger()), |
| }; |
| $transport = match ($MODE) { |
| 'framed' => new TFramedTransport($transport), |
| 'http', 'inline' => $transport, |
| default => new TBufferedTransport($transport, 1024, 1024), |
| }; |
| |
| $protocol = $MODE === 'inline' ? null : thrift_test_protocol_factory($PROTO)->getProtocol($transport); |
| $testClient = new \ThriftTest\ThriftTestClient($protocol ?? $transport); |
| |
| $transport->open(); |
| |
| $start = microtime(true); |
| |
| define('ERR_BASETYPES', 1); |
| define('ERR_STRUCTS', 2); |
| define('ERR_CONTAINERS', 4); |
| define('ERR_EXCEPTIONS', 8); |
| define('ERR_UNKNOWN', 64); |
| $exitcode = 0; |
| |
| /** |
| * VOID TEST |
| */ |
| print_r("testVoid()"); |
| $testClient->testVoid(); |
| print_r(" = void\n"); |
| |
| function roundtrip($testClient, $method, $value) |
| { |
| global $exitcode; |
| print_r("$method($value)"); |
| $ret = $testClient->$method($value); |
| print_r(" = \"$ret\"\n"); |
| if ($value !== $ret) { |
| print_r("*** FAILED ***\n"); |
| $exitcode |= ERR_BASETYPES; |
| } |
| } |
| |
| /** |
| * STRING TEST |
| */ |
| roundtrip($testClient, 'testString', "Test"); |
| |
| /** |
| * BOOL TEST |
| */ |
| roundtrip($testClient, 'testBool', true); |
| roundtrip($testClient, 'testBool', false); |
| |
| /** |
| * BYTE TEST |
| */ |
| roundtrip($testClient, 'testByte', 1); |
| roundtrip($testClient, 'testByte', -1); |
| roundtrip($testClient, 'testByte', 127); |
| roundtrip($testClient, 'testByte', -128); |
| |
| /** |
| * I32 TEST |
| */ |
| roundtrip($testClient, 'testI32', -1); |
| |
| /** |
| * I64 TEST |
| */ |
| roundtrip($testClient, 'testI64', 0); |
| roundtrip($testClient, 'testI64', 1); |
| roundtrip($testClient, 'testI64', -1); |
| roundtrip($testClient, 'testI64', -34359738368); |
| |
| /** |
| * DOUBLE TEST |
| */ |
| roundtrip($testClient, 'testDouble', -852.234234234); |
| |
| /** |
| * BINARY TEST -- TODO |
| */ |
| |
| /** |
| * UUID TEST |
| */ |
| print_r("testUuid('00000000-0000-0000-0000-000000000000')"); |
| $uuid_in = '00000000-0000-0000-0000-000000000000'; |
| $uuid_out = $testClient->testUuid($uuid_in); |
| print_r(" = \"$uuid_out\"\n"); |
| if ($uuid_in !== $uuid_out) { |
| echo "**FAILED**\n"; |
| $exitcode |= ERR_BASETYPES; |
| } |
| |
| roundtrip($testClient, 'testUuid', '00000000-0000-0000-0000-000000000000'); |
| roundtrip($testClient, 'testUuid', '550e8400-e29b-41d4-a716-446655440000'); |
| |
| /** |
| * STRUCT TEST |
| */ |
| print_r("testStruct({\"Zero\", 1, -3, -5})"); |
| $out = new \ThriftTest\Xtruct(); |
| $out->string_thing = "Zero"; |
| $out->byte_thing = 1; |
| $out->i32_thing = -3; |
| $out->i64_thing = -5; |
| $in = $testClient->testStruct($out); |
| print_r(" = {\"" . $in->string_thing . "\", " . |
| $in->byte_thing . ", " . |
| $in->i32_thing . ", " . |
| $in->i64_thing . "}\n"); |
| |
| if ($in != $out) { |
| echo "**FAILED**\n"; |
| $exitcode |= ERR_STRUCTS; |
| } |
| |
| /** |
| * NESTED STRUCT TEST |
| */ |
| print_r("testNest({1, {\"Zero\", 1, -3, -5}), 5}"); |
| $out2 = new \ThriftTest\Xtruct2(); |
| $out2->byte_thing = 1; |
| $out2->struct_thing = $out; |
| $out2->i32_thing = 5; |
| $in2 = $testClient->testNest($out2); |
| $in = $in2->struct_thing; |
| print_r(" = {" . $in2->byte_thing . ", {\"" . |
| $in->string_thing . "\", " . |
| $in->byte_thing . ", " . |
| $in->i32_thing . ", " . |
| $in->i64_thing . "}, " . |
| $in2->i32_thing . "}\n"); |
| |
| if ($in2 != $out2) { |
| echo "**FAILED**\n"; |
| $exitcode |= ERR_STRUCTS; |
| } |
| |
| /** |
| * MAP TEST |
| */ |
| $mapout = []; |
| for ($i = 0; $i < 5; ++$i) { |
| $mapout[$i] = $i - 10; |
| } |
| print_r("testMap({"); |
| $first = true; |
| foreach ($mapout as $key => $val) { |
| if ($first) { |
| $first = false; |
| } else { |
| print_r(", "); |
| } |
| print_r("$key => $val"); |
| } |
| print_r("})"); |
| |
| $mapin = $testClient->testMap($mapout); |
| print_r(" = {"); |
| $first = true; |
| foreach ($mapin as $key => $val) { |
| if ($first) { |
| $first = false; |
| } else { |
| print_r(", "); |
| } |
| print_r("$key => $val"); |
| } |
| print_r("}\n"); |
| |
| if ($mapin != $mapout) { |
| echo "**FAILED**\n"; |
| $exitcode |= ERR_CONTAINERS; |
| } |
| |
| $mapout = []; |
| for ($i = 0; $i < 10; $i++) { |
| $mapout["key$i"] = "val$i"; |
| } |
| print_r('testStringMap({'); |
| $first = true; |
| foreach ($mapout as $key => $val) { |
| if ($first) { |
| $first = false; |
| } else { |
| print_r(", "); |
| } |
| print_r("\"$key\" => \"$val\""); |
| } |
| print_r("})"); |
| $mapin = $testClient->testStringMap($mapout); |
| print_r(" = {"); |
| $first = true; |
| foreach ($mapin as $key => $val) { |
| if ($first) { |
| $first = false; |
| } else { |
| print_r(", "); |
| } |
| print_r("\"$key\" => \"$val\""); |
| } |
| print_r("}\n"); |
| ksort($mapin); |
| if ($mapin != $mapout) { |
| echo "**FAILED**\n"; |
| $exitcode |= ERR_CONTAINERS; |
| } |
| |
| /** |
| * SET TEST |
| */ |
| $setout = []; |
| for ($i = -2; $i < 3; ++$i) { |
| $setout[$i] = true; |
| } |
| print_r("testSet({"); |
| echo implode(',', array_keys($setout)); |
| print_r("})"); |
| $setin = $testClient->testSet($setout); |
| print_r(" = {"); |
| echo implode(', ', array_keys($setin)); |
| print_r("}\n"); |
| // Order of keys in set does not matter |
| ksort($setin); |
| if ($setout !== $setin) { |
| echo "**FAILED**\n"; |
| $exitcode |= ERR_CONTAINERS; |
| } |
| // Regression test for corrupted array |
| if ($setin[2] !== $setout[2] || is_int($setin[2])) { |
| echo "**FAILED**\n"; |
| $exitcode |= ERR_CONTAINERS; |
| } |
| |
| /** |
| * LIST TEST |
| */ |
| $listout = []; |
| for ($i = -2; $i < 3; ++$i) { |
| $listout[] = $i; |
| } |
| print_r("testList({"); |
| $first = true; |
| foreach ($listout as $val) { |
| if ($first) { |
| $first = false; |
| } else { |
| print_r(", "); |
| } |
| print_r($val); |
| } |
| print_r("})"); |
| $listin = $testClient->testList($listout); |
| print_r(" = {"); |
| $first = true; |
| foreach ($listin as $val) { |
| if ($first) { |
| $first = false; |
| } else { |
| print_r(", "); |
| } |
| print_r($val); |
| } |
| print_r("}\n"); |
| if ($listin !== $listout) { |
| echo "**FAILED**\n"; |
| $exitcode |= ERR_CONTAINERS; |
| } |
| |
| /** |
| * ENUM TEST |
| */ |
| print_r("testEnum(ONE)"); |
| $ret = $testClient->testEnum(\ThriftTest\Numberz::ONE); |
| print_r(" = $ret\n"); |
| if ($ret != \ThriftTest\Numberz::ONE) { |
| echo "**FAILED**\n"; |
| $exitcode |= ERR_STRUCTS; |
| } |
| |
| print_r("testEnum(TWO)"); |
| $ret = $testClient->testEnum(\ThriftTest\Numberz::TWO); |
| print_r(" = $ret\n"); |
| if ($ret != \ThriftTest\Numberz::TWO) { |
| echo "**FAILED**\n"; |
| $exitcode |= ERR_STRUCTS; |
| } |
| |
| print_r("testEnum(THREE)"); |
| $ret = $testClient->testEnum(\ThriftTest\Numberz::THREE); |
| print_r(" = $ret\n"); |
| if ($ret != \ThriftTest\Numberz::THREE) { |
| echo "**FAILED**\n"; |
| $exitcode |= ERR_STRUCTS; |
| } |
| |
| print_r("testEnum(FIVE)"); |
| $ret = $testClient->testEnum(\ThriftTest\Numberz::FIVE); |
| print_r(" = $ret\n"); |
| if ($ret != \ThriftTest\Numberz::FIVE) { |
| echo "**FAILED**\n"; |
| $exitcode |= ERR_STRUCTS; |
| } |
| |
| print_r("testEnum(EIGHT)"); |
| $ret = $testClient->testEnum(\ThriftTest\Numberz::EIGHT); |
| print_r(" = $ret\n"); |
| if ($ret != \ThriftTest\Numberz::EIGHT) { |
| echo "**FAILED**\n"; |
| $exitcode |= ERR_STRUCTS; |
| } |
| |
| /** |
| * TYPEDEF TEST |
| */ |
| print_r("testTypedef(309858235082523)"); |
| $uid = $testClient->testTypedef(309858235082523); |
| print_r(" = $uid\n"); |
| if ($uid !== 309858235082523) { |
| echo "**FAILED**\n"; |
| $exitcode |= ERR_STRUCTS; |
| } |
| |
| /** |
| * NESTED MAP TEST |
| */ |
| print_r("testMapMap(1)"); |
| $mm = $testClient->testMapMap(1); |
| print_r(" = {"); |
| foreach ($mm as $key => $val) { |
| print_r("$key => {"); |
| foreach ($val as $k2 => $v2) { |
| print_r("$k2 => $v2, "); |
| } |
| print_r("}, "); |
| } |
| print_r("}\n"); |
| $expected_mm = [ |
| -4 => [-4 => -4, -3 => -3, -2 => -2, -1 => -1], |
| 4 => [4 => 4, 3 => 3, 2 => 2, 1 => 1], |
| ]; |
| if ($mm != $expected_mm) { |
| echo "**FAILED**\n"; |
| $exitcode |= ERR_CONTAINERS; |
| } |
| |
| /** |
| * INSANITY TEST |
| */ |
| $insane = new \ThriftTest\Insanity(); |
| $insane->userMap[\ThriftTest\Numberz::FIVE] = 5000; |
| $truck = new \ThriftTest\Xtruct(); |
| $truck->string_thing = "Truck"; |
| $truck->byte_thing = 8; |
| $truck->i32_thing = 8; |
| $truck->i64_thing = 8; |
| $insane->xtructs[] = $truck; |
| print_r("testInsanity()"); |
| $whoa = $testClient->testInsanity($insane); |
| print_r(" = {"); |
| foreach ($whoa as $key => $val) { |
| print_r("$key => {"); |
| foreach ($val as $k2 => $v2) { |
| print_r("$k2 => {"); |
| $userMap = $v2->userMap; |
| print_r("{"); |
| if (is_array($userMap)) { |
| foreach ($userMap as $k3 => $v3) { |
| print_r("$k3 => $v3, "); |
| } |
| } |
| print_r("}, "); |
| |
| $xtructs = $v2->xtructs; |
| print_r("{"); |
| if (is_array($xtructs)) { |
| foreach ($xtructs as $x) { |
| print_r("{\"" . $x->string_thing . "\", " . |
| $x->byte_thing . ", " . $x->i32_thing . ", " . $x->i64_thing . "}, "); |
| } |
| } |
| print_r("}"); |
| |
| print_r("}, "); |
| } |
| print_r("}, "); |
| } |
| print_r("}\n"); |
| |
| /** |
| * EXCEPTION TEST |
| */ |
| print_r("testException('Xception')"); |
| try { |
| $testClient->testException('Xception'); |
| print_r(" void\nFAILURE\n"); |
| $exitcode |= ERR_EXCEPTIONS; |
| } catch (\ThriftTest\Xception $x) { |
| print_r(' caught xception ' . $x->errorCode . ': ' . $x->message . "\n"); |
| } |
| |
| // Regression test for THRIFT-4263 |
| print_r("testBinarySerializer_Deserialize('foo')"); |
| try { |
| \Thrift\Serializer\TBinarySerializer::deserialize(base64_decode('foo'), \ThriftTest\Xtruct2::class); |
| echo "**FAILED**\n"; |
| $exitcode |= ERR_STRUCTS; |
| } catch (\Thrift\Exception\TTransportException $happy_exception) { |
| // We expected this due to binary data of base64_decode('foo') is less then 4 |
| // bytes and it tries to find thrift version number in the transport by |
| // reading i32() at the beginning. Casting to string validates that |
| // exception is still accessible in memory and not corrupted. Without patch, |
| // PHP will error log that the exception doesn't have any tostring method, |
| // which is a lie due to corrupted memory. |
| for ($i = 99; $i > 0; $i--) { |
| (string) $happy_exception; |
| } |
| print_r(" SUCCESS\n"); |
| } |
| |
| /** |
| * Normal tests done. |
| */ |
| |
| $stop = microtime(true); |
| $elp = round(1000 * ($stop - $start), 0); |
| print_r("Total time: $elp ms\n"); |
| |
| /** |
| * Extraneous "I don't trust PHP to pack/unpack integer" tests |
| */ |
| |
| if ($protocol instanceof \Thrift\Protocol\TBinaryProtocolAccelerated) { |
| // Regression check: check that method name is not double-freed |
| // Method name should not be an interned string. |
| $method_name = "Void"; |
| $method_name = "test$method_name"; |
| |
| $seqid = 0; |
| $args = new \ThriftTest\ThriftTest_testVoid_args(); |
| thrift_protocol_write_binary($protocol, $method_name, \Thrift\Type\TMessageType::CALL, $args, $seqid, $protocol->isStrictWrite()); |
| $testClient->recv_testVoid(); |
| } |
| |
| // Max I32 |
| $num = pow(2, 30) + (pow(2, 30) - 1); |
| roundtrip($testClient, 'testI32', $num); |
| |
| // Min I32 |
| $num = 0 - pow(2, 31); |
| roundtrip($testClient, 'testI32', $num); |
| |
| // Max I64 |
| $num = pow(2, 62) + (pow(2, 62) - 1); |
| roundtrip($testClient, 'testI64', $num); |
| |
| // Min I64 |
| $num = 0 - pow(2, 62) - pow(2, 62); |
| roundtrip($testClient, 'testI64', $num); |
| |
| $transport->close(); |
| exit($exitcode); |