[THRIFT-5757] Unit tests for php lib
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index f003db2..0b29ddb 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -90,7 +90,7 @@
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
- extensions: mbstring, intl, xml
+ extensions: mbstring, intl, xml, curl
ini-values: "error_reporting=E_ALL"
- name: Install Dependencies
diff --git a/composer.json b/composer.json
index 77248a9..900fb28 100644
--- a/composer.json
+++ b/composer.json
@@ -23,8 +23,10 @@
"require-dev": {
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"squizlabs/php_codesniffer": "3.*",
+ "php-mock/php-mock-phpunit": "^2.10",
"ext-json": "*",
- "ext-xml": "*"
+ "ext-xml": "*",
+ "ext-curl": "*"
},
"autoload": {
"psr-4": {"Thrift\\": "lib/php/lib/"}
diff --git a/lib/php/lib/Transport/TBufferedTransport.php b/lib/php/lib/Transport/TBufferedTransport.php
index 253c5ac..e3a40a4 100644
--- a/lib/php/lib/Transport/TBufferedTransport.php
+++ b/lib/php/lib/Transport/TBufferedTransport.php
@@ -1,4 +1,5 @@
<?php
+
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
diff --git a/lib/php/lib/Transport/TCurlClient.php b/lib/php/lib/Transport/TCurlClient.php
index 2087433..709798e 100644
--- a/lib/php/lib/Transport/TCurlClient.php
+++ b/lib/php/lib/Transport/TCurlClient.php
@@ -1,4 +1,5 @@
<?php
+
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
@@ -227,7 +228,6 @@
register_shutdown_function(array('Thrift\\Transport\\TCurlClient', 'closeCurlHandle'));
self::$curlHandle = curl_init();
curl_setopt(self::$curlHandle, CURLOPT_RETURNTRANSFER, true);
- curl_setopt(self::$curlHandle, CURLOPT_BINARYTRANSFER, true);
curl_setopt(self::$curlHandle, CURLOPT_USERAGENT, 'PHP/TCurlClient');
curl_setopt(self::$curlHandle, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt(self::$curlHandle, CURLOPT_FOLLOWLOCATION, true);
@@ -238,9 +238,11 @@
$fullUrl = $this->scheme_ . "://" . $host . $this->uri_;
$headers = array();
- $defaultHeaders = array('Accept' => 'application/x-thrift',
+ $defaultHeaders = array(
+ 'Accept' => 'application/x-thrift',
'Content-Type' => 'application/x-thrift',
- 'Content-Length' => TStringFuncFactory::create()->strlen($this->request_));
+ 'Content-Length' => TStringFuncFactory::create()->strlen($this->request_)
+ );
foreach (array_merge($defaultHeaders, $this->headers_) as $key => $value) {
$headers[] = "$key: $value";
}
@@ -292,10 +294,11 @@
{
try {
if (self::$curlHandle) {
- curl_close(self::$curlHandle);
+ curl_close(self::$curlHandle); #This function has no effect. Prior to PHP 8.0.0, this function was used to close the resource.
self::$curlHandle = null;
}
} catch (\Exception $x) {
+ #it's not possible to throw an exception by calling a function that has no effect
error_log('There was an error closing the curl handle: ' . $x->getMessage());
}
}
diff --git a/lib/php/lib/Transport/THttpClient.php b/lib/php/lib/Transport/THttpClient.php
index 4d6be32..0f767f4 100644
--- a/lib/php/lib/Transport/THttpClient.php
+++ b/lib/php/lib/Transport/THttpClient.php
@@ -1,4 +1,5 @@
<?php
+
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
@@ -212,11 +213,14 @@
$host = $this->host_ . ($this->port_ != 80 ? ':' . $this->port_ : '');
$headers = array();
- $defaultHeaders = array('Host' => $host,
+ $defaultHeaders = array(
+ 'Host' => $host,
'Accept' => 'application/x-thrift',
'User-Agent' => 'PHP/THttpClient',
'Content-Type' => 'application/x-thrift',
- 'Content-Length' => TStringFuncFactory::create()->strlen($this->buf_));
+ 'Content-Length' => TStringFuncFactory::create()->strlen($this->buf_)
+ );
+
foreach (array_merge($defaultHeaders, $this->headers_) as $key => $value) {
$headers[] = "$key: $value";
}
@@ -225,10 +229,12 @@
$baseHttpOptions = isset($options["http"]) ? $options["http"] : array();
- $httpOptions = $baseHttpOptions + array('method' => 'POST',
+ $httpOptions = $baseHttpOptions + array(
+ 'method' => 'POST',
'header' => implode("\r\n", $headers),
'max_redirects' => 1,
- 'content' => $this->buf_);
+ 'content' => $this->buf_
+ );
if ($this->timeout_ > 0) {
$httpOptions['timeout'] = $this->timeout_;
}
diff --git a/lib/php/lib/Transport/TMemoryBuffer.php b/lib/php/lib/Transport/TMemoryBuffer.php
index fee03a2..e5da9da 100644
--- a/lib/php/lib/Transport/TMemoryBuffer.php
+++ b/lib/php/lib/Transport/TMemoryBuffer.php
@@ -1,4 +1,5 @@
<?php
+
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
@@ -35,6 +36,8 @@
*/
class TMemoryBuffer extends TTransport
{
+ protected $buf_ = '';
+
/**
* Constructor. Optionally pass an initial value
* for the buffer.
@@ -44,8 +47,6 @@
$this->buf_ = $buf;
}
- protected $buf_ = '';
-
public function isOpen()
{
return true;
diff --git a/lib/php/lib/Transport/TPhpStream.php b/lib/php/lib/Transport/TPhpStream.php
index 42823ff..2350b96 100644
--- a/lib/php/lib/Transport/TPhpStream.php
+++ b/lib/php/lib/Transport/TPhpStream.php
@@ -1,4 +1,5 @@
<?php
+
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
@@ -53,7 +54,7 @@
public function open()
{
if ($this->read_) {
- $this->inStream_ = @fopen(self::inStreamName(), 'r');
+ $this->inStream_ = @fopen($this->inStreamName(), 'r');
if (!is_resource($this->inStream_)) {
throw new TException('TPhpStream: Could not open php://input');
}
@@ -113,7 +114,7 @@
@fflush($this->outStream_);
}
- private static function inStreamName()
+ private function inStreamName()
{
if (php_sapi_name() == 'cli') {
return 'php://stdin';
diff --git a/lib/php/lib/Transport/TSSLSocket.php b/lib/php/lib/Transport/TSSLSocket.php
index b4a0adb..16956e7 100644
--- a/lib/php/lib/Transport/TSSLSocket.php
+++ b/lib/php/lib/Transport/TSSLSocket.php
@@ -36,7 +36,7 @@
/**
* Remote port
*
- * @var resource
+ * @var null|resource
*/
protected $context_ = null;
@@ -57,6 +57,10 @@
) {
$this->host_ = $this->getSSLHost($host);
$this->port_ = $port;
+ // Initialize a stream context if not provided
+ if ($context === null) {
+ $context = stream_context_create();
+ }
$this->context_ = $context;
$this->debugHandler_ = $debugHandler ? $debugHandler : 'error_log';
}
@@ -87,7 +91,8 @@
throw new TTransportException('Socket already connected', TTransportException::ALREADY_OPEN);
}
- if (empty($this->host_)) {
+ $host = parse_url($this->host_, PHP_URL_HOST);
+ if (empty($host)) {
throw new TTransportException('Cannot open null host', TTransportException::NOT_OPEN);
}
diff --git a/lib/php/lib/Transport/TSocket.php b/lib/php/lib/Transport/TSocket.php
index 8fe60fd..fb74fdb 100644
--- a/lib/php/lib/Transport/TSocket.php
+++ b/lib/php/lib/Transport/TSocket.php
@@ -252,8 +252,10 @@
if (function_exists('socket_import_stream') && function_exists('socket_set_option')) {
// warnings silenced due to bug https://bugs.php.net/bug.php?id=70939
- $socket = @socket_import_stream($this->handle_);
- @socket_set_option($socket, SOL_TCP, TCP_NODELAY, 1);
+ $socket = socket_import_stream($this->handle_);
+ if ($socket !== false) {
+ @socket_set_option($socket, SOL_TCP, TCP_NODELAY, 1);
+ }
}
}
diff --git a/lib/php/lib/Transport/TSocketPool.php b/lib/php/lib/Transport/TSocketPool.php
index 307885f..312e023 100644
--- a/lib/php/lib/Transport/TSocketPool.php
+++ b/lib/php/lib/Transport/TSocketPool.php
@@ -1,4 +1,5 @@
<?php
+
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
@@ -25,24 +26,6 @@
use Thrift\Exception\TException;
/**
- * This library makes use of APCu cache to make hosts as down in a web
- * environment. If you are running from the CLI or on a system without APCu
- * installed, then these null functions will step in and act like cache
- * misses.
- */
-if (!function_exists('apcu_fetch')) {
- function apcu_fetch($key)
- {
- return false;
- }
-
- function apcu_store($key, $var, $ttl = 0)
- {
- return false;
- }
-}
-
-/**
* Sockets implementation of the TTransport interface that allows connection
* to a pool of servers.
*
@@ -92,6 +75,12 @@
private $alwaysTryLast_ = true;
/**
+ * Use apcu cache
+ * @var bool
+ */
+ private $useApcuCache;
+
+ /**
* Socket pool constructor
*
* @param array $hosts List of remote hostnames
@@ -116,9 +105,13 @@
}
foreach ($hosts as $key => $host) {
- $this->servers_ [] = array('host' => $host,
- 'port' => $ports[$key]);
+ $this->servers_ [] = array(
+ 'host' => $host,
+ 'port' => $ports[$key]
+ );
}
+
+ $this->useApcuCache = function_exists('apcu_fetch');
}
/**
@@ -206,7 +199,7 @@
$failtimeKey = 'thrift_failtime:' . $host . ':' . $port . '~';
// Cache miss? Assume it's OK
- $lastFailtime = apcu_fetch($failtimeKey);
+ $lastFailtime = $this->apcuFetch($failtimeKey);
if ($lastFailtime === false) {
$lastFailtime = 0;
}
@@ -251,7 +244,7 @@
// Only clear the failure counts if required to do so
if ($lastFailtime > 0) {
- apcu_store($failtimeKey, 0);
+ $this->apcuStore($failtimeKey, 0);
}
// Successful connection, return now
@@ -265,7 +258,7 @@
$consecfailsKey = 'thrift_consecfails:' . $host . ':' . $port . '~';
// Ignore cache misses
- $consecfails = apcu_fetch($consecfailsKey);
+ $consecfails = $this->apcuFetch($consecfailsKey);
if ($consecfails === false) {
$consecfails = 0;
}
@@ -284,12 +277,12 @@
);
}
// Store the failure time
- apcu_store($failtimeKey, time());
+ $this->apcuStore($failtimeKey, time());
// Clear the count of consecutive failures
- apcu_store($consecfailsKey, 0);
+ $this->apcuStore($consecfailsKey, 0);
} else {
- apcu_store($consecfailsKey, $consecfails);
+ $this->apcuStore($consecfailsKey, $consecfails);
}
}
}
@@ -307,4 +300,20 @@
}
throw new TException($error);
}
+
+ /**
+ * This library makes use of APCu cache to make hosts as down in a web
+ * environment. If you are running from the CLI or on a system without APCu
+ * installed, then these null functions will step in and act like cache
+ * misses.
+ */
+ private function apcuFetch($key, &$success = null)
+ {
+ return $this->useApcuCache ? apcu_fetch($key, $success) : false;
+ }
+
+ private function apcuStore($key, $var, $ttl = 0)
+ {
+ return $this->useApcuCache ? apcu_store($key, $var, $ttl) : false;
+ }
}
diff --git a/lib/php/phpunit.xml b/lib/php/phpunit.xml
index 53b3f35..2cbea95 100644
--- a/lib/php/phpunit.xml
+++ b/lib/php/phpunit.xml
@@ -28,12 +28,11 @@
stopOnFailure="true"
processIsolation="true"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
- <coverage includeUncoveredFiles="true">
- <include>
- <directory suffix=".php">./src</directory>
+ <filter>
+ <whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./lib</directory>
- </include>
- </coverage>
+ </whitelist>
+ </filter>
<testsuites>
<testsuite name="Thrift PHP Test Suite">
<directory>./test/Unit</directory>
diff --git a/lib/php/test/Fixtures/Fixtures.php b/lib/php/test/Fixtures/Fixtures.php
index eb348fc..d48be40 100644
--- a/lib/php/test/Fixtures/Fixtures.php
+++ b/lib/php/test/Fixtures/Fixtures.php
@@ -17,8 +17,6 @@
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
- *
- * @package thrift.test
*/
namespace Test\Thrift\Fixtures;
diff --git a/lib/php/test/Fixtures/TJSONProtocolFixtures.php b/lib/php/test/Fixtures/TJSONProtocolFixtures.php
index 81ada77..77fb270 100644
--- a/lib/php/test/Fixtures/TJSONProtocolFixtures.php
+++ b/lib/php/test/Fixtures/TJSONProtocolFixtures.php
@@ -17,8 +17,6 @@
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
- *
- * @package thrift.test
*/
namespace Test\Thrift\Fixtures;
diff --git a/lib/php/test/Fixtures/TSimpleJSONProtocolFixtures.php b/lib/php/test/Fixtures/TSimpleJSONProtocolFixtures.php
index 448eb61..0281a87 100644
--- a/lib/php/test/Fixtures/TSimpleJSONProtocolFixtures.php
+++ b/lib/php/test/Fixtures/TSimpleJSONProtocolFixtures.php
@@ -17,8 +17,6 @@
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
- *
- * @package thrift.test
*/
namespace Test\Thrift\Fixtures;
diff --git a/lib/php/test/Unit/Lib/ClassLoader/Fixtures/A/TestClass.php b/lib/php/test/Unit/Lib/ClassLoader/Fixtures/A/TestClass.php
index e39f501..3652b25 100644
--- a/lib/php/test/Unit/Lib/ClassLoader/Fixtures/A/TestClass.php
+++ b/lib/php/test/Unit/Lib/ClassLoader/Fixtures/A/TestClass.php
@@ -17,11 +17,6 @@
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
- *
- * ClassLoader to load Thrift library and definitions
- * Inspired from UniversalClassLoader from Symfony 2
- *
- * @package thrift.classloader
*/
namespace A;
diff --git a/lib/php/test/Unit/Lib/ClassLoader/Fixtures/B/TestClass.php b/lib/php/test/Unit/Lib/ClassLoader/Fixtures/B/TestClass.php
index d2e87e2..1d5a543 100644
--- a/lib/php/test/Unit/Lib/ClassLoader/Fixtures/B/TestClass.php
+++ b/lib/php/test/Unit/Lib/ClassLoader/Fixtures/B/TestClass.php
@@ -17,11 +17,6 @@
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
- *
- * ClassLoader to load Thrift library and definitions
- * Inspired from UniversalClassLoader from Symfony 2
- *
- * @package thrift.classloader
*/
namespace B;
diff --git a/lib/php/test/Unit/Lib/ClassLoader/Fixtures/C/TestClass.php b/lib/php/test/Unit/Lib/ClassLoader/Fixtures/C/TestClass.php
index 9e4000c..58bae58 100644
--- a/lib/php/test/Unit/Lib/ClassLoader/Fixtures/C/TestClass.php
+++ b/lib/php/test/Unit/Lib/ClassLoader/Fixtures/C/TestClass.php
@@ -17,11 +17,6 @@
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
- *
- * ClassLoader to load Thrift library and definitions
- * Inspired from UniversalClassLoader from Symfony 2
- *
- * @package thrift.classloader
*/
namespace C;
diff --git a/lib/php/test/Unit/Lib/ClassLoader/Fixtures/D/TestClass.php b/lib/php/test/Unit/Lib/ClassLoader/Fixtures/D/TestClass.php
index c0cda0c..592fe56 100644
--- a/lib/php/test/Unit/Lib/ClassLoader/Fixtures/D/TestClass.php
+++ b/lib/php/test/Unit/Lib/ClassLoader/Fixtures/D/TestClass.php
@@ -17,11 +17,6 @@
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
- *
- * ClassLoader to load Thrift library and definitions
- * Inspired from UniversalClassLoader from Symfony 2
- *
- * @package thrift.classloader
*/
namespace D;
diff --git a/lib/php/test/Unit/Lib/ClassLoader/Fixtures/E/TestClass.php b/lib/php/test/Unit/Lib/ClassLoader/Fixtures/E/TestClass.php
index b1981b4..56b5679 100644
--- a/lib/php/test/Unit/Lib/ClassLoader/Fixtures/E/TestClass.php
+++ b/lib/php/test/Unit/Lib/ClassLoader/Fixtures/E/TestClass.php
@@ -17,11 +17,6 @@
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
- *
- * ClassLoader to load Thrift library and definitions
- * Inspired from UniversalClassLoader from Symfony 2
- *
- * @package thrift.classloader
*/
namespace E;
diff --git a/lib/php/test/Unit/Lib/ClassLoader/ThriftClassLoaderTest.php b/lib/php/test/Unit/Lib/ClassLoader/ThriftClassLoaderTest.php
index 1180211..46ed2ec 100644
--- a/lib/php/test/Unit/Lib/ClassLoader/ThriftClassLoaderTest.php
+++ b/lib/php/test/Unit/Lib/ClassLoader/ThriftClassLoaderTest.php
@@ -21,6 +21,7 @@
namespace Test\Thrift\Unit\Lib\ClassLoader;
+use phpmock\phpunit\PHPMock;
use PHPUnit\Framework\TestCase;
use Thrift\ClassLoader\ThriftClassLoader;
@@ -30,7 +31,7 @@
*/
class ThriftClassLoaderTest extends TestCase
{
- const APCU_PREFIX = 'test';
+ use PHPMock;
/**
* @dataProvider registerNamespaceDataProvider
@@ -42,6 +43,16 @@
$useApcu = false,
$apcuPrefix = null
) {
+ $this->getFunctionMock('Thrift\ClassLoader', 'apcu_fetch')
+ ->expects($useApcu ? $this->once() : $this->never())
+ ->with($apcuPrefix . $class)
+ ->willReturn(false);
+
+ $this->getFunctionMock('Thrift\ClassLoader', 'apcu_store')
+ ->expects($useApcu ? $this->once() : $this->never())
+ ->with($apcuPrefix . $class, $this->anything())
+ ->willReturn(true);
+
$loader = new ThriftClassLoader($useApcu, $apcuPrefix);
foreach ($namespaces as $namespace => $paths) {
$loader->registerNamespace($namespace, $paths);
@@ -97,7 +108,7 @@
'class' => '\E\TestClass',
'isClassExist' => true,
'useApcu' => true,
- 'apcuPrefix' => self::APCU_PREFIX,
+ 'apcuPrefix' => 'APCU_PREFIX',
];
}
@@ -111,6 +122,16 @@
$useApcu = false,
$apcuPrefix = null
) {
+ $this->getFunctionMock('Thrift\ClassLoader', 'apcu_fetch')
+ ->expects($useApcu ? $this->once() : $this->never())
+ ->with($apcuPrefix . $class)
+ ->willReturn(false);
+
+ $this->getFunctionMock('Thrift\ClassLoader', 'apcu_store')
+ ->expects($useApcu ? $this->once() : $this->never())
+ ->with($apcuPrefix . $class, $this->anything())
+ ->willReturn(true);
+
$loader = new ThriftClassLoader($useApcu, $apcuPrefix);
foreach ($definitions as $namespace => $paths) {
$loader->registerDefinition($namespace, $paths);
@@ -191,33 +212,7 @@
'class' => '\TestValidators\TestServiceClient',
'checkInterfaceExist' => false,
'useApcu' => true,
- 'apcuPrefix' => self::APCU_PREFIX,
+ 'apcuPrefix' => 'APCU_PREFIX',
];
}
}
-
-namespace Thrift\ClassLoader;
-
-use Test\Thrift\Unit\Lib\ClassLoader\ThriftClassLoaderTest;
-
-if (!function_exists('apcu_fetch')) {
- {
- function apcu_fetch($key, &$success = null)
- {
- if (strpos($key, ThriftClassLoaderTest::APCU_PREFIX) === false) {
- throw new \Exception('apcu_fetch error, invalid key');
- }
-
- return false;
- }
-
- function apcu_store($key, $var, $ttl = 0)
- {
- if (strpos($key, ThriftClassLoaderTest::APCU_PREFIX) === false) {
- throw new \Exception('apcu_store error, invalid key');
- }
-
- return false;
- }
- }
-}
diff --git a/lib/php/test/Unit/Lib/Factory/TBinaryProtocolFactoryTest.php b/lib/php/test/Unit/Lib/Factory/TBinaryProtocolFactoryTest.php
index 5f7c2a2..76ff187 100644
--- a/lib/php/test/Unit/Lib/Factory/TBinaryProtocolFactoryTest.php
+++ b/lib/php/test/Unit/Lib/Factory/TBinaryProtocolFactoryTest.php
@@ -17,8 +17,6 @@
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
- *
- * @package thrift.protocol
*/
namespace Test\Thrift\Unit\Lib\Factory;
diff --git a/lib/php/test/Unit/Lib/Factory/TCompactProtocolFactoryTest.php b/lib/php/test/Unit/Lib/Factory/TCompactProtocolFactoryTest.php
index f81e789..1483c6a 100644
--- a/lib/php/test/Unit/Lib/Factory/TCompactProtocolFactoryTest.php
+++ b/lib/php/test/Unit/Lib/Factory/TCompactProtocolFactoryTest.php
@@ -17,8 +17,6 @@
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
- *
- * @package thrift.protocol
*/
namespace Test\Thrift\Unit\Lib\Factory;
diff --git a/lib/php/test/Unit/Lib/Factory/TFramedTransportFactoryTest.php b/lib/php/test/Unit/Lib/Factory/TFramedTransportFactoryTest.php
index 2cb32d8..3b8b5cc 100644
--- a/lib/php/test/Unit/Lib/Factory/TFramedTransportFactoryTest.php
+++ b/lib/php/test/Unit/Lib/Factory/TFramedTransportFactoryTest.php
@@ -17,8 +17,6 @@
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
- *
- * @package thrift.protocol
*/
namespace Test\Thrift\Unit\Lib\Factory;
diff --git a/lib/php/test/Unit/Lib/Factory/TJSONProtocolFactoryTest.php b/lib/php/test/Unit/Lib/Factory/TJSONProtocolFactoryTest.php
index 0685af7..9c7055d 100644
--- a/lib/php/test/Unit/Lib/Factory/TJSONProtocolFactoryTest.php
+++ b/lib/php/test/Unit/Lib/Factory/TJSONProtocolFactoryTest.php
@@ -17,8 +17,6 @@
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
- *
- * @package thrift.protocol
*/
namespace Test\Thrift\Unit\Lib\Factory;
diff --git a/lib/php/test/Unit/Lib/Factory/TStringFuncFactoryTest.php b/lib/php/test/Unit/Lib/Factory/TStringFuncFactoryTest.php
index eb4df44..c6feb2c 100644
--- a/lib/php/test/Unit/Lib/Factory/TStringFuncFactoryTest.php
+++ b/lib/php/test/Unit/Lib/Factory/TStringFuncFactoryTest.php
@@ -17,12 +17,11 @@
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
- *
- * @package thrift.protocol
*/
namespace Test\Thrift\Unit\Lib\Factory;
+use phpmock\phpunit\PHPMock;
use PHPUnit\Framework\TestCase;
use Thrift\Factory\TStringFuncFactory;
use Thrift\StringFunc\Core;
@@ -31,16 +30,21 @@
class TStringFuncFactoryTest extends TestCase
{
- /**
- * @return void
- */
- public function testCreate()
- {
- $factory = new TStringFuncFactory();
- $stringFunc = $factory::create();
- $this->assertInstanceOf(TStringFunc::class, $stringFunc);
- $this->assertInstanceOf(Mbstring::class, $stringFunc);
+ use PHPMock;
+ /**
+ * @dataProvider createDataProvider
+ */
+ public function testCreate(
+ $mbstringFuncOverload,
+ $expectedClass
+ ) {
+ $this->getFunctionMock('Thrift\Factory', 'ini_get')
+ ->expects($this->once())
+ ->with('mbstring.func_overload')
+ ->willReturn($mbstringFuncOverload);
+
+ $factory = new TStringFuncFactory();
/**
* it is a hack to nullable the instance of TStringFuncFactory, and get a new instance based on the new ini_get value
*/
@@ -50,25 +54,21 @@
$refInstance->setValue($factory, null);
$stringFunc = $factory::create();
+
$this->assertInstanceOf(TStringFunc::class, $stringFunc);
- $this->assertInstanceOf(Core::class, $stringFunc);
+ $this->assertInstanceOf($expectedClass, $stringFunc);
}
-}
+ public function createDataProvider()
+ {
+ yield 'mbstring' => [
+ 'mbstring.func_overload' => 2,
+ 'expected' => Mbstring::class
+ ];
-namespace Thrift\Factory;
-
-function ini_get($key)
-{
- static $count = 0;
- if ($key === 'mbstring.func_overload') {
- if ($count === 0) {
- $count++;
- return 2;
- } else {
- return 0;
- }
- } else {
- return \ini_get($key);
+ yield 'string' => [
+ 'mbstring.func_overload' => 0,
+ 'expected' => Core::class
+ ];
}
}
diff --git a/lib/php/test/Unit/Lib/Factory/TTransportFactoryTest.php b/lib/php/test/Unit/Lib/Factory/TTransportFactoryTest.php
index da91b64..a8a791a 100644
--- a/lib/php/test/Unit/Lib/Factory/TTransportFactoryTest.php
+++ b/lib/php/test/Unit/Lib/Factory/TTransportFactoryTest.php
@@ -17,8 +17,6 @@
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
- *
- * @package thrift.protocol
*/
namespace Test\Thrift\Unit\Lib\Factory;
diff --git a/lib/php/test/Unit/Lib/Transport/TBufferedTransportTest.php b/lib/php/test/Unit/Lib/Transport/TBufferedTransportTest.php
new file mode 100644
index 0000000..dd6003a
--- /dev/null
+++ b/lib/php/test/Unit/Lib/Transport/TBufferedTransportTest.php
@@ -0,0 +1,286 @@
+<?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\Thrift\Unit\Lib\Transport;
+
+use PHPUnit\Framework\TestCase;
+use Thrift\Transport\TBufferedTransport;
+use Thrift\Transport\TTransport;
+
+class TBufferedTransportTest extends TestCase
+{
+ public function testIsOpen()
+ {
+ $transport = $this->createMock(TTransport::class);
+ $bufferedTransport = new TBufferedTransport($transport);
+
+ $transport
+ ->expects($this->once())
+ ->method('isOpen')
+ ->willReturn(true);
+
+ $this->assertTrue($bufferedTransport->isOpen());
+ }
+
+ public function testOpen()
+ {
+ $transport = $this->createMock(TTransport::class);
+ $bufferedTransport = new TBufferedTransport($transport);
+
+ $transport
+ ->expects($this->once())
+ ->method('open')
+ ->willReturn(null);
+
+ $this->assertNull($bufferedTransport->open());
+ }
+
+ public function testClose()
+ {
+ $transport = $this->createMock(TTransport::class);
+ $bufferedTransport = new TBufferedTransport($transport);
+
+ $transport
+ ->expects($this->once())
+ ->method('close')
+ ->willReturn(null);
+
+ $this->assertNull($bufferedTransport->close());
+ }
+
+ public function testPutBack()
+ {
+ $transport = $this->createMock(TTransport::class);
+ $bufferedTransport = new TBufferedTransport($transport);
+ $bufferedTransport->putBack('test');
+
+ $ref = new \ReflectionClass($bufferedTransport);
+ $property = $ref->getProperty('rBuf_');
+ $property->setAccessible(true);
+ $this->assertEquals('test', $property->getValue($bufferedTransport));
+
+ $bufferedTransport->putBack('abcde');
+ $this->assertEquals('abcdetest', $property->getValue($bufferedTransport));
+ }
+
+ /**
+ * @dataProvider readAllDataProvider
+ */
+ public function testReadAll(
+ $startBuffer,
+ $readLength,
+ $bufferReadLength,
+ $bufferReadResult,
+ $expectedBufferValue,
+ $expectedRead
+ ) {
+ $transport = $this->createMock(TTransport::class);
+ $bufferedTransport = new TBufferedTransport($transport);
+ $bufferedTransport->putBack($startBuffer);
+
+ $transport
+ ->expects($bufferReadLength > 0 ? $this->once() : $this->never())
+ ->method('readAll')
+ ->with($bufferReadLength)
+ ->willReturn($bufferReadResult);
+
+ $this->assertEquals($expectedRead, $bufferedTransport->readAll($readLength));
+
+ $ref = new \ReflectionClass($bufferedTransport);
+ $property = $ref->getProperty('rBuf_');
+ $property->setAccessible(true);
+ $this->assertEquals($expectedBufferValue, $property->getValue($bufferedTransport));
+ }
+
+ public function readAllDataProvider()
+ {
+ yield 'buffer empty' => [
+ 'startBuffer' => '',
+ 'readLength' => 5,
+ 'bufferReadLength' => 5,
+ 'bufferReadResult' => '12345',
+ 'expectedBufferValue' => '',
+ 'expectedRead' => '12345',
+ ];
+ yield 'buffer have partly loaded data' => [
+ 'startBuffer' => '12345',
+ 'readLength' => 10,
+ 'bufferReadLength' => 5,
+ 'bufferReadResult' => '67890',
+ 'expectedBufferValue' => '',
+ 'expectedRead' => '1234567890',
+ ];
+ yield 'buffer fully read' => [
+ 'startBuffer' => '12345',
+ 'readLength' => 5,
+ 'bufferReadLength' => 0,
+ 'bufferReadResult' => '',
+ 'expectedBufferValue' => '',
+ 'expectedRead' => '12345',
+ ];
+ yield 'request less data that we have in buffer' => [
+ 'startBuffer' => '12345',
+ 'readLength' => 3,
+ 'bufferReadLength' => 0,
+ 'bufferReadResult' => '',
+ 'expectedBufferValue' => '45',
+ 'expectedRead' => '123',
+ ];
+ }
+
+ /**
+ * @dataProvider readDataProvider
+ */
+ public function testRead(
+ $readBufferSize,
+ $startBuffer,
+ $readLength,
+ $bufferReadResult,
+ $expectedBufferValue,
+ $expectedRead
+ ) {
+ $transport = $this->createMock(TTransport::class);
+ $bufferedTransport = new TBufferedTransport($transport, $readBufferSize);
+ $bufferedTransport->putBack($startBuffer);
+
+ $transport
+ ->expects(empty($startBuffer) > 0 ? $this->once() : $this->never())
+ ->method('read')
+ ->with($readBufferSize)
+ ->willReturn($bufferReadResult);
+
+ $this->assertEquals($expectedRead, $bufferedTransport->read($readLength));
+
+ $ref = new \ReflectionClass($bufferedTransport);
+ $property = $ref->getProperty('rBuf_');
+ $property->setAccessible(true);
+ $this->assertEquals($expectedBufferValue, $property->getValue($bufferedTransport));
+ }
+
+ public function readDataProvider()
+ {
+ yield 'buffer empty' => [
+ 'readBufferSize' => 10,
+ 'startBuffer' => '',
+ 'readLength' => 5,
+ 'bufferReadResult' => '12345',
+ 'expectedBufferValue' => '',
+ 'expectedRead' => '12345',
+ ];
+ yield 'buffer read partly' => [
+ 'readBufferSize' => 10,
+ 'startBuffer' => '',
+ 'readLength' => 5,
+ 'bufferReadResult' => '1234567890',
+ 'expectedBufferValue' => '67890',
+ 'expectedRead' => '12345',
+ ];
+ yield 'buffer fully read' => [
+ 'readBufferSize' => 10,
+ 'startBuffer' => '12345',
+ 'readLength' => 5,
+ 'bufferReadResult' => '',
+ 'expectedBufferValue' => '',
+ 'expectedRead' => '12345',
+ ];
+ }
+
+ /**
+ * @dataProvider writeDataProvider
+ */
+ public function testWrite(
+ $writeBufferSize,
+ $writeData,
+ $bufferedTransportCall,
+ $expectedWriteBufferValue
+ ) {
+ $transport = $this->createMock(TTransport::class);
+ $bufferedTransport = new TBufferedTransport($transport, 512, $writeBufferSize);
+
+ $transport
+ ->expects($this->exactly($bufferedTransportCall))
+ ->method('write')
+ ->with($writeData)
+ ->willReturn(null);
+
+ $this->assertNull($bufferedTransport->write($writeData));
+
+ $ref = new \ReflectionClass($bufferedTransport);
+ $property = $ref->getProperty('wBuf_');
+ $property->setAccessible(true);
+ $this->assertEquals($expectedWriteBufferValue, $property->getValue($bufferedTransport));
+ }
+
+ public function writeDataProvider()
+ {
+ yield 'store data in buffer' => [
+ 'writeBufferSize' => 10,
+ 'writeData' => '12345',
+ 'bufferedTransportCall' => 0,
+ 'expectedWriteBufferValue' => '12345',
+ ];
+ yield 'send data to buffered transport' => [
+ 'writeBufferSize' => 10,
+ 'writeData' => '12345678901',
+ 'bufferedTransportCall' => 1,
+ 'expectedWriteBufferValue' => '',
+ ];
+ }
+
+ /**
+ * @dataProvider flushDataProvider
+ */
+ public function testFlush(
+ $writeBuffer
+ ) {
+ $transport = $this->createMock(TTransport::class);
+ $bufferedTransport = new TBufferedTransport($transport, 512, 512);
+ $ref = new \ReflectionClass($bufferedTransport);
+ $property = $ref->getProperty('wBuf_');
+ $property->setAccessible(true);
+ $property->setValue($bufferedTransport, $writeBuffer);
+
+ $transport
+ ->expects(!empty($writeBuffer) ? $this->once() : $this->never())
+ ->method('write')
+ ->with($writeBuffer)
+ ->willReturn(null);
+
+ $transport
+ ->expects($this->once())
+ ->method('flush')
+ ->willReturn(null);
+
+ $this->assertNull($bufferedTransport->flush());
+
+ $this->assertEquals('', $property->getValue($bufferedTransport));
+ }
+
+ public function flushDataProvider()
+ {
+ yield 'empty buffer' => [
+ 'writeBuffer' => '',
+ ];
+ yield 'not empty buffer' => [
+ 'writeBuffer' => '12345',
+ ];
+ }
+}
diff --git a/lib/php/test/Unit/Lib/Transport/TCurlClientTest.php b/lib/php/test/Unit/Lib/Transport/TCurlClientTest.php
new file mode 100644
index 0000000..7cd7446
--- /dev/null
+++ b/lib/php/test/Unit/Lib/Transport/TCurlClientTest.php
@@ -0,0 +1,423 @@
+<?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\Thrift\Unit\Lib\Transport;
+
+use phpmock\phpunit\PHPMock;
+use PHPUnit\Framework\TestCase;
+use Thrift\Exception\TTransportException;
+use Thrift\Transport\TCurlClient;
+
+class TCurlClientTest extends TestCase
+{
+ use PHPMock;
+
+ public function testSetTimeoutSecs()
+ {
+ $host = 'localhost';
+ $transport = new TCurlClient($host);
+ $transport->setTimeoutSecs(1000);
+
+ $ref = new \ReflectionClass($transport);
+ $prop = $ref->getProperty('timeout_');
+ $prop->setAccessible(true);
+ $this->assertEquals(1000, $prop->getValue($transport));
+ }
+
+ public function testSetConnectionTimeoutSecs()
+ {
+ $host = 'localhost';
+ $transport = new TCurlClient($host);
+ $transport->setConnectionTimeoutSecs(1000);
+
+ $ref = new \ReflectionClass($transport);
+ $prop = $ref->getProperty('connectionTimeout_');
+ $prop->setAccessible(true);
+ $this->assertEquals(1000, $prop->getValue($transport));
+ }
+
+ public function testIsOpen()
+ {
+ $host = 'localhost';
+ $transport = new TCurlClient($host);
+ $this->assertTrue($transport->isOpen());
+ }
+
+ public function testOpen()
+ {
+ $host = 'localhost';
+ $transport = new TCurlClient($host);
+ $this->assertNull($transport->open());
+ }
+
+ public function testClose()
+ {
+ $host = 'localhost';
+ $transport = new TCurlClient($host);
+
+ $ref = new \ReflectionClass($transport);
+ $propRequest = $ref->getProperty('request_');
+ $propRequest->setAccessible(true);
+ $propRequest->setValue($transport, 'testRequest');
+ $propResponse = $ref->getProperty('response_');
+ $propResponse->setAccessible(true);
+ $propResponse->setValue($transport, 'testResponse');
+
+ $this->assertNull($transport->close());
+ $this->assertEmpty($propRequest->getValue($transport));
+ $this->assertEmpty($propResponse->getValue($transport));
+ }
+
+ public function testRead()
+ {
+ $host = 'localhost';
+ $transport = new TCurlClient($host);
+
+ $ref = new \ReflectionClass($transport);
+ $propResponse = $ref->getProperty('response_');
+ $propResponse->setAccessible(true);
+ $propResponse->setValue($transport, '1234567890');
+
+ $response = $transport->read(5);
+ $this->assertEquals('12345', $response);
+ $this->assertEquals('67890', $propResponse->getValue($transport));
+
+ $response = $transport->read(5);
+ $this->assertEquals('67890', $response);
+ # The response does not cleaned after reading full answer, maybe it should be fixed
+ $this->assertEquals('67890', $propResponse->getValue($transport));
+ }
+
+ public function testReadAll()
+ {
+ $host = 'localhost';
+ $transport = new TCurlClient($host);
+
+ $ref = new \ReflectionClass($transport);
+ $propResponse = $ref->getProperty('response_');
+ $propResponse->setAccessible(true);
+ $propResponse->setValue($transport, '1234567890');
+
+ $response = $transport->readAll(5);
+ $this->assertEquals('12345', $response);
+ $this->assertEquals('67890', $propResponse->getValue($transport));
+ }
+
+ public function testReadAll_THRIFT_4656()
+ {
+ $host = 'localhost';
+ $transport = new TCurlClient($host);
+
+ $ref = new \ReflectionClass($transport);
+ $propResponse = $ref->getProperty('response_');
+ $propResponse->setAccessible(true);
+ $propResponse->setValue($transport, '');
+
+ $this->expectException(TTransportException::class);
+ $this->expectExceptionMessage('TCurlClient could not read 5 bytes');
+ $this->expectExceptionCode(TTransportException::UNKNOWN);
+
+ $transport->readAll(5);
+ }
+
+ public function testWrite()
+ {
+ $host = 'localhost';
+ $transport = new TCurlClient($host);
+
+ $ref = new \ReflectionClass($transport);
+ $propRequest = $ref->getProperty('request_');
+ $propRequest->setAccessible(true);
+ $propRequest->setValue($transport, '1234567890');
+
+ $transport->write('12345');
+ $this->assertEquals('123456789012345', $propRequest->getValue($transport));
+ }
+
+ public function testAddHeaders()
+ {
+ $host = 'localhost';
+ $transport = new TCurlClient($host);
+
+ $ref = new \ReflectionClass($transport);
+ $propRequest = $ref->getProperty('headers_');
+ $propRequest->setAccessible(true);
+ $propRequest->setValue($transport, ['test' => '1234567890']);
+
+ $transport->addHeaders(['test2' => '12345']);
+ $this->assertEquals(['test' => '1234567890', 'test2' => '12345'], $propRequest->getValue($transport));
+ }
+
+ /**
+ * @dataProvider flushDataProvider
+ */
+ public function testFlush(
+ $host,
+ $port,
+ $uri,
+ $scheme,
+ $headers,
+ $request,
+ $timeout,
+ $connectionTimeout,
+ $curlSetOptCalls,
+ $response,
+ $responseError,
+ $responseCode,
+ $expectedException = null,
+ $expectedMessage = null,
+ $expectedCode = null
+ ) {
+ $this->getFunctionMock('Thrift\\Transport', 'register_shutdown_function')
+ ->expects($this->once())
+ ->with(
+ $this->callback(
+ function ($arg) {
+ return is_array(
+ $arg
+ ) && $arg[0] === 'Thrift\\Transport\\TCurlClient' && $arg[1] === 'closeCurlHandle';
+ }
+ )
+ );
+ $this->getFunctionMock('Thrift\\Transport', 'curl_init')
+ ->expects($this->once());
+
+ $this->getFunctionMock('Thrift\\Transport', 'curl_setopt')
+ ->expects($this->any())
+ ->withConsecutive(...$curlSetOptCalls)
+ ->willReturn(true);
+
+ $this->getFunctionMock('Thrift\\Transport', 'curl_exec')
+ ->expects($this->once())
+ ->with($this->anything())
+ ->willReturn($response);
+
+ $this->getFunctionMock('Thrift\\Transport', 'curl_error')
+ ->expects($this->once())
+ ->with($this->anything())
+ ->willReturn($responseError);
+
+ $this->getFunctionMock('Thrift\\Transport', 'curl_getinfo')
+ ->expects($this->once())
+ ->with($this->anything(), CURLINFO_HTTP_CODE)
+ ->willReturn($responseCode);
+
+ if (!is_null($expectedException)) {
+ $this->expectException($expectedException);
+ $this->expectExceptionMessage($expectedMessage);
+ $this->expectExceptionCode($expectedCode);
+
+ $this->getFunctionMock('Thrift\\Transport', 'curl_close')
+ ->expects($this->once())
+ ->with($this->anything());
+ }
+
+ $transport = new TCurlClient($host, $port, $uri, $scheme);
+ if (!empty($headers)) {
+ $transport->addHeaders($headers);
+ }
+ $transport->write($request);
+ if (!empty($timeout)) {
+ $transport->setTimeoutSecs($timeout);
+ }
+ if (!empty($connectionTimeout)) {
+ $transport->setConnectionTimeoutSecs($connectionTimeout);
+ }
+
+ $transport->flush();
+ }
+
+ public function flushDataProvider()
+ {
+ $request = 'request';
+
+ $default = [
+ 'host' => 'localhost',
+ 'port' => 80,
+ 'uri' => '',
+ 'scheme' => 'http',
+ 'headers' => [],
+ 'request' => $request,
+ 'timeout' => null,
+ 'connectionTimeout' => null,
+ 'curlSetOptCalls' => [
+ [$this->anything(), CURLOPT_RETURNTRANSFER, true],
+ [$this->anything(), CURLOPT_USERAGENT, 'PHP/TCurlClient'],
+ [$this->anything(), CURLOPT_CUSTOMREQUEST, 'POST'],
+ [$this->anything(), CURLOPT_FOLLOWLOCATION, true],
+ [$this->anything(), CURLOPT_MAXREDIRS, 1],
+ [
+ $this->anything(),
+ CURLOPT_HTTPHEADER,
+ [
+ 'Accept: application/x-thrift',
+ 'Content-Type: application/x-thrift',
+ 'Content-Length: ' . strlen($request),
+ ],
+ ],
+ [$this->anything(), CURLOPT_POSTFIELDS, $request],
+ [$this->anything(), CURLOPT_URL, 'http://localhost'],
+ ],
+ 'response' => 'response',
+ 'responseError' => '',
+ 'responseCode' => 200,
+ ];
+
+ yield 'default' => $default;
+ yield 'additionalHeaders' => array_merge(
+ $default,
+ [
+ 'headers' => ['test' => '1234567890'],
+ 'curlSetOptCalls' => [
+ [$this->anything(), CURLOPT_RETURNTRANSFER, true],
+ [$this->anything(), CURLOPT_USERAGENT, 'PHP/TCurlClient'],
+ [$this->anything(), CURLOPT_CUSTOMREQUEST, 'POST'],
+ [$this->anything(), CURLOPT_FOLLOWLOCATION, true],
+ [$this->anything(), CURLOPT_MAXREDIRS, 1],
+ [
+ $this->anything(),
+ CURLOPT_HTTPHEADER,
+ [
+ 'Accept: application/x-thrift',
+ 'Content-Type: application/x-thrift',
+ 'Content-Length: ' . strlen($request),
+ 'test: 1234567890',
+ ],
+ ],
+ [$this->anything(), CURLOPT_POSTFIELDS, $request],
+ [$this->anything(), CURLOPT_URL, 'http://localhost'],
+ ],
+ ]
+ );
+ yield 'uri' => array_merge(
+ $default,
+ [
+ 'uri' => 'test1234567890',
+ 'curlSetOptCalls' => [
+ [$this->anything(), CURLOPT_RETURNTRANSFER, true],
+ [$this->anything(), CURLOPT_USERAGENT, 'PHP/TCurlClient'],
+ [$this->anything(), CURLOPT_CUSTOMREQUEST, 'POST'],
+ [$this->anything(), CURLOPT_FOLLOWLOCATION, true],
+ [$this->anything(), CURLOPT_MAXREDIRS, 1],
+ [
+ $this->anything(),
+ CURLOPT_HTTPHEADER,
+ [
+ 'Accept: application/x-thrift',
+ 'Content-Type: application/x-thrift',
+ 'Content-Length: ' . strlen($request),
+ ],
+ ],
+ [$this->anything(), CURLOPT_POSTFIELDS, $request],
+ [$this->anything(), CURLOPT_URL, 'http://localhost/test1234567890'],
+ ],
+ ]
+ );
+ yield 'timeout' => array_merge(
+ $default,
+ [
+ 'timeout' => 10,
+ 'connectionTimeout' => 10,
+ 'curlSetOptCalls' => [
+ [$this->anything(), CURLOPT_RETURNTRANSFER, true],
+ [$this->anything(), CURLOPT_USERAGENT, 'PHP/TCurlClient'],
+ [$this->anything(), CURLOPT_CUSTOMREQUEST, 'POST'],
+ [$this->anything(), CURLOPT_FOLLOWLOCATION, true],
+ [$this->anything(), CURLOPT_MAXREDIRS, 1],
+ [
+ $this->anything(),
+ CURLOPT_HTTPHEADER,
+ [
+ 'Accept: application/x-thrift',
+ 'Content-Type: application/x-thrift',
+ 'Content-Length: ' . strlen($request),
+ ],
+ ],
+ [$this->anything(), CURLOPT_TIMEOUT, 10],
+ [$this->anything(), CURLOPT_CONNECTTIMEOUT, 10],
+ [$this->anything(), CURLOPT_POSTFIELDS, $request],
+ [$this->anything(), CURLOPT_URL, 'http://localhost'],
+ ],
+ ]
+ );
+ yield 'timeout msec' => array_merge(
+ $default,
+ [
+ 'timeout' => 0.1,
+ 'connectionTimeout' => 0.1,
+ 'curlSetOptCalls' => [
+ [$this->anything(), CURLOPT_RETURNTRANSFER, true],
+ [$this->anything(), CURLOPT_USERAGENT, 'PHP/TCurlClient'],
+ [$this->anything(), CURLOPT_CUSTOMREQUEST, 'POST'],
+ [$this->anything(), CURLOPT_FOLLOWLOCATION, true],
+ [$this->anything(), CURLOPT_MAXREDIRS, 1],
+ [
+ $this->anything(),
+ CURLOPT_HTTPHEADER,
+ [
+ 'Accept: application/x-thrift',
+ 'Content-Type: application/x-thrift',
+ 'Content-Length: ' . strlen($request),
+ ],
+ ],
+ [$this->anything(), CURLOPT_TIMEOUT_MS, 100],
+ [$this->anything(), CURLOPT_CONNECTTIMEOUT_MS, 100],
+ [$this->anything(), CURLOPT_POSTFIELDS, $request],
+ [$this->anything(), CURLOPT_URL, 'http://localhost'],
+ ],
+ ]
+ );
+ yield 'curl_exec return false' => array_merge(
+ $default,
+ [
+ 'response' => false,
+ 'expectedException' => TTransportException::class,
+ 'expectedMessage' => 'TCurlClient: Could not connect to http://localhost',
+ 'expectedCode' => TTransportException::UNKNOWN,
+ ]
+ );
+ yield 'curl_exec return response code 403' => array_merge(
+ $default,
+ [
+ 'responseError' => 'Access denied',
+ 'responseCode' => 403,
+ 'expectedException' => TTransportException::class,
+ 'expectedMessage' => 'TCurlClient: Could not connect to http://localhost, Access denied, HTTP status code: 403',
+ 'expectedCode' => TTransportException::UNKNOWN,
+ ]
+ );
+ }
+
+ public function testCloseCurlHandle()
+ {
+ $this->getFunctionMock('Thrift\\Transport', 'curl_close')
+ ->expects($this->once())
+ ->with('testHandle');
+
+ $transport = new TCurlClient('localhost');
+ $ref = new \ReflectionClass($transport);
+ $prop = $ref->getProperty('curlHandle');
+ $prop->setAccessible(true);
+ $prop->setValue($transport, 'testHandle');
+
+ $transport::closeCurlHandle();
+ }
+}
diff --git a/lib/php/test/Unit/Lib/Transport/TFramedTransportTest.php b/lib/php/test/Unit/Lib/Transport/TFramedTransportTest.php
new file mode 100644
index 0000000..2607ddb
--- /dev/null
+++ b/lib/php/test/Unit/Lib/Transport/TFramedTransportTest.php
@@ -0,0 +1,240 @@
+<?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\Thrift\Unit\Lib\Transport;
+
+use PHPUnit\Framework\TestCase;
+use Thrift\Transport\TFramedTransport;
+use Thrift\Transport\TTransport;
+
+class TFramedTransportTest extends TestCase
+{
+ public function testIsOpen()
+ {
+ $transport = $this->createMock(TTransport::class);
+ $framedTransport = new TFramedTransport($transport);
+
+ $transport
+ ->expects($this->once())
+ ->method('isOpen')
+ ->willReturn(true);
+
+ $this->assertTrue($framedTransport->isOpen());
+ }
+
+ public function testOpen()
+ {
+ $transport = $this->createMock(TTransport::class);
+ $framedTransport = new TFramedTransport($transport);
+
+ $transport
+ ->expects($this->once())
+ ->method('open')
+ ->willReturn(null);
+
+ $this->assertNull($framedTransport->open());
+ }
+
+ public function testClose()
+ {
+ $transport = $this->createMock(TTransport::class);
+ $framedTransport = new TFramedTransport($transport);
+
+ $transport
+ ->expects($this->once())
+ ->method('close')
+ ->willReturn(null);
+
+ $this->assertNull($framedTransport->close());
+ }
+
+ public function testPutBack()
+ {
+ $transport = $this->createMock(TTransport::class);
+ $framedTransport = new TFramedTransport($transport);
+ $framedTransport->putBack('test');
+
+ $ref = new \ReflectionClass($framedTransport);
+ $property = $ref->getProperty('rBuf_');
+ $property->setAccessible(true);
+ $this->assertEquals('test', $property->getValue($framedTransport));
+
+ $framedTransport->putBack('abcde');
+ $this->assertEquals('abcdetest', $property->getValue($framedTransport));
+ }
+
+ /**
+ * @dataProvider readDataProvider
+ */
+ public function testRead(
+ $readAllowed,
+ $readBuffer,
+ $lowLevelTransportReadResult,
+ $lowLevelTransportReadAllParams,
+ $lowLevelTransportReadAllResult,
+ $readLength,
+ $expectedReadResult
+ ) {
+ $transport = $this->createMock(TTransport::class);
+ $framedTransport = new TFramedTransport($transport, $readAllowed);
+ $framedTransport->putBack($readBuffer);
+
+ $transport
+ ->expects($readAllowed ? $this->never() : $this->once())
+ ->method('read')
+ ->with($readLength)
+ ->willReturn($lowLevelTransportReadResult);
+
+ $transport
+ ->expects($this->exactly(count($lowLevelTransportReadAllParams)))
+ ->method('readAll')
+ ->withConsecutive(...$lowLevelTransportReadAllParams)
+ ->willReturnOnConsecutiveCalls(...$lowLevelTransportReadAllResult);
+
+ $this->assertEquals($expectedReadResult, $framedTransport->read($readLength));
+ }
+
+ public function readDataProvider()
+ {
+ yield 'read not allowed' => [
+ 'readAllowed' => false,
+ 'readBuffer' => '',
+ 'lowLevelTransportReadResult' => '12345',
+ 'lowLevelTransportReadAllParams' => [],
+ 'lowLevelTransportReadAllResult' => [],
+ 'readLength' => 5,
+ 'expectedReadResult' => '12345',
+ ];
+ yield 'read fully buffered item' => [
+ 'readAllowed' => true,
+ 'readBuffer' => '',
+ 'lowLevelTransportReadResult' => '',
+ 'lowLevelTransportReadAllParams' => [[4], [5]],
+ 'lowLevelTransportReadAllResult' => [pack('N', '5'), '12345'],
+ 'readLength' => 5,
+ 'expectedReadResult' => '12345',
+ ];
+ yield 'read partly buffered item' => [
+ 'readAllowed' => true,
+ 'readBuffer' => '',
+ 'lowLevelTransportReadResult' => '',
+ 'lowLevelTransportReadAllParams' => [[4], [10]],
+ 'lowLevelTransportReadAllResult' => [pack('N', '10'), '1234567890'],
+ 'readLength' => 5,
+ 'expectedReadResult' => '12345',
+ ];
+ }
+
+ /**
+ * @dataProvider writeDataProvider
+ */
+ public function testWrite(
+ $writeAllowed,
+ $writeData,
+ $writeLength,
+ $expectedWriteBufferValue
+ ) {
+ $transport = $this->createMock(TTransport::class);
+ $framedTransport = new TFramedTransport($transport, true, $writeAllowed);
+
+ $transport
+ ->expects($writeAllowed ? $this->never() : $this->once())
+ ->method('write')
+ ->with('12345', 5)
+ ->willReturn(5);
+
+ $framedTransport->write($writeData, $writeLength);
+
+ $ref = new \ReflectionClass($framedTransport);
+ $property = $ref->getProperty('wBuf_');
+ $property->setAccessible(true);
+ $this->assertEquals($expectedWriteBufferValue, $property->getValue($framedTransport));
+ }
+
+ public function writeDataProvider()
+ {
+ yield 'write not allowed' => [
+ 'writeAllowed' => false,
+ 'writeData' => '12345',
+ 'writeLength' => 5,
+ 'expectedWriteBufferValue' => '',
+ ];
+ yield 'write full' => [
+ 'writeAllowed' => true,
+ 'writeData' => '12345',
+ 'writeLength' => 5,
+ 'expectedWriteBufferValue' => '12345',
+ ];
+ yield 'write partly' => [
+ 'writeAllowed' => true,
+ 'writeData' => '1234567890',
+ 'writeLength' => 5,
+ 'expectedWriteBufferValue' => '12345',
+ ];
+ }
+
+ /**
+ * @dataProvider flushDataProvider
+ */
+ public function testFlush(
+ $writeAllowed,
+ $writeBuffer,
+ $lowLevelTransportWrite
+ ) {
+ $transport = $this->createMock(TTransport::class);
+ $framedTransport = new TFramedTransport($transport, true, $writeAllowed);
+ $ref = new \ReflectionClass($framedTransport);
+ $property = $ref->getProperty('wBuf_');
+ $property->setAccessible(true);
+ $property->setValue($framedTransport, $writeBuffer);
+
+ $transport
+ ->expects($this->once())
+ ->method('flush');
+
+ $transport
+ ->expects($writeAllowed && !empty($writeBuffer) ? $this->once() : $this->never())
+ ->method('write')
+ ->with($lowLevelTransportWrite)
+ ->willReturn(null);
+
+ $this->assertNull($framedTransport->flush());
+ }
+
+ public function flushDataProvider()
+ {
+ yield 'write not allowed' => [
+ 'writeAllowed' => false,
+ 'writeBuffer' => '12345',
+ 'lowLevelTransportWrite' => '',
+ ];
+ yield 'empty buffer' => [
+ 'writeAllowed' => true,
+ 'writeBuffer' => '',
+ 'lowLevelTransportWrite' => '',
+ ];
+ yield 'write full' => [
+ 'writeAllowed' => true,
+ 'writeBuffer' => '12345',
+ 'lowLevelTransportWrite' => pack('N', strlen('12345')) . '12345',
+ ];
+ }
+}
diff --git a/lib/php/test/Unit/Lib/Transport/THttpClientTest.php b/lib/php/test/Unit/Lib/Transport/THttpClientTest.php
new file mode 100644
index 0000000..ce6813c
--- /dev/null
+++ b/lib/php/test/Unit/Lib/Transport/THttpClientTest.php
@@ -0,0 +1,332 @@
+<?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\Thrift\Unit\Lib\Transport;
+
+use phpmock\phpunit\PHPMock;
+use PHPUnit\Framework\TestCase;
+use Thrift\Exception\TTransportException;
+use Thrift\Transport\THttpClient;
+
+class THttpClientTest extends TestCase
+{
+ use PHPMock;
+
+ public function testSetTimeoutSecs()
+ {
+ $host = 'localhost';
+ $transport = new THttpClient($host);
+ $transport->setTimeoutSecs(1000);
+
+ $ref = new \ReflectionClass($transport);
+ $prop = $ref->getProperty('timeout_');
+ $prop->setAccessible(true);
+ $this->assertEquals(1000, $prop->getValue($transport));
+ }
+
+ public function testIsOpen()
+ {
+ $host = 'localhost';
+ $transport = new THttpClient($host);
+ $this->assertTrue($transport->isOpen());
+ }
+
+ public function testOpen()
+ {
+ $host = 'localhost';
+ $transport = new THttpClient($host);
+ $this->assertNull($transport->open());
+ }
+
+ public function testClose()
+ {
+ $handle = fopen('php://temp', 'r+');
+ $this->getFunctionMock('Thrift\\Transport', 'fclose')
+ ->expects($this->once())
+ ->with($handle)
+ ->willReturn(true);
+
+ $host = 'localhost';
+ $transport = new THttpClient($host);
+
+ $ref = new \ReflectionClass($transport);
+ $propRequest = $ref->getProperty('handle_');
+ $propRequest->setAccessible(true);
+ $propRequest->setValue($transport, $handle);
+
+ $this->assertNull($transport->close());
+ $this->assertNull($propRequest->getValue($transport));
+ }
+
+ /**
+ * @dataProvider readDataProvider
+ */
+ public function testRead(
+ $readLen,
+ $freadResult,
+ $streamGetMetaDataResult,
+ $expectedResult,
+ $expectedException,
+ $expectedExceptionMessage,
+ $expectedExceptionCode
+ ) {
+ $handle = fopen('php://temp', 'r+');
+ $this->getFunctionMock('Thrift\\Transport', 'fread')
+ ->expects($this->once())
+ ->with($handle, $readLen)
+ ->willReturn($freadResult);
+
+ $this->getFunctionMock('Thrift\\Transport', 'stream_get_meta_data')
+ ->expects(!empty($streamGetMetaDataResult) ? $this->once() : $this->never())
+ ->with($handle)
+ ->willReturn($streamGetMetaDataResult);
+
+ if ($expectedException) {
+ $this->expectException($expectedException);
+ $this->expectExceptionMessage($expectedExceptionMessage);
+ $this->expectExceptionCode($expectedExceptionCode);
+ }
+
+ $host = 'localhost';
+ $transport = new THttpClient($host);
+
+ $ref = new \ReflectionClass($transport);
+ $propRequest = $ref->getProperty('handle_');
+ $propRequest->setAccessible(true);
+ $propRequest->setValue($transport, $handle);
+
+ $this->assertEquals($expectedResult, $transport->read($readLen));
+ }
+
+ public function readDataProvider()
+ {
+ yield 'read success' => [
+ 'readLen' => 10,
+ 'freadResult' => '1234567890',
+ 'streamGetMetaDataResult' => [],
+ 'expectedResult' => '1234567890',
+ 'expectedException' => null,
+ 'expectedExceptionMessage' => null,
+ 'expectedExceptionCode' => null,
+ ];
+ yield 'read failed' => [
+ 'readLen' => 10,
+ 'freadResult' => false,
+ 'streamGetMetaDataResult' => [
+ 'timed_out' => false,
+ ],
+ 'expectedResult' => '',
+ 'expectedException' => TTransportException::class,
+ 'expectedExceptionMessage' => 'THttpClient: Could not read 10 bytes from localhost:80',
+ 'expectedExceptionCode' => TTransportException::UNKNOWN,
+ ];
+ yield 'read timeout' => [
+ 'readLen' => 10,
+ 'freadResult' => '',
+ 'streamGetMetaDataResult' => [
+ 'timed_out' => true,
+ ],
+ 'expectedResult' => '',
+ 'expectedException' => TTransportException::class,
+ 'expectedExceptionMessage' => 'THttpClient: timed out reading 10 bytes from localhost:80',
+ 'expectedExceptionCode' => TTransportException::TIMED_OUT,
+ ];
+ }
+
+ public function testWrite()
+ {
+ $host = 'localhost';
+ $transport = new THttpClient($host);
+
+ $ref = new \ReflectionClass($transport);
+ $prop = $ref->getProperty('buf_');
+ $prop->setAccessible(true);
+
+ $transport->write('1234567890');
+
+ $this->assertEquals('1234567890', $prop->getValue($transport));
+ }
+
+ /**
+ * @dataProvider flushDataProvider
+ */
+ public function testFlush(
+ $host,
+ $port,
+ $uri,
+ $scheme,
+ $context,
+ $headers,
+ $timeout,
+ $streamContextOptions,
+ $streamContext,
+ $fopenResult,
+ $expectedHost,
+ $expectedUri,
+ $expectedException,
+ $expectedExceptionMessage,
+ $expectedExceptionCode
+ ) {
+ $this->getFunctionMock('Thrift\\Transport', 'stream_context_create')
+ ->expects($this->once())
+ ->with($streamContextOptions)
+ ->willReturn($streamContext);
+
+ $this->getFunctionMock('Thrift\\Transport', 'fopen')
+ ->expects($this->once())
+ ->with(
+ $scheme . '://' . $expectedHost . $expectedUri,
+ 'r',
+ false,
+ $streamContext
+ )->willReturn($fopenResult);
+
+ if ($expectedException) {
+ $this->expectException($expectedException);
+ $this->expectExceptionMessage($expectedExceptionMessage);
+ $this->expectExceptionCode($expectedExceptionCode);
+ }
+
+ $transport = new THttpClient($host, $port, $uri, $scheme, $context);
+ if (!empty($headers)) {
+ $transport->addHeaders($headers);
+ }
+ if (!empty($timeout)) {
+ $transport->setTimeoutSecs($timeout);
+ }
+
+ $this->assertNull($transport->flush());
+ }
+
+ public function flushDataProvider()
+ {
+ $default = [
+ 'host' => 'localhost',
+ 'port' => '80',
+ 'uri' => '',
+ 'scheme' => 'http',
+ 'context' => [],
+ 'headers' => [],
+ 'timeout' => null,
+ 'streamContextOptions' => [
+ 'http' => [
+ 'method' => 'POST',
+ 'header' => "Host: localhost\r\n" .
+ "Accept: application/x-thrift\r\n" .
+ "User-Agent: PHP/THttpClient\r\n" .
+ "Content-Type: application/x-thrift\r\n" .
+ "Content-Length: 0",
+ 'content' => '',
+ 'max_redirects' => 1,
+ ],
+ ],
+ 'streamContext' => fopen('php://temp', 'r+'),
+ 'fopenResult' => fopen('php://memory', 'r+'),
+ 'expectedHost' => 'localhost',
+ 'expectedUri' => '',
+ 'expectedException' => '',
+ 'expectedExceptionMessage' => '',
+ 'expectedExceptionCode' => '',
+ ];
+
+ yield 'success' => $default;
+ yield 'additionalHeaders' => array_merge(
+ $default,
+ [
+ 'headers' => [
+ 'X-Test-Header' => 'test',
+ ],
+ 'streamContextOptions' => [
+ 'http' => [
+ 'method' => 'POST',
+ 'header' => "Host: localhost\r\n" .
+ "Accept: application/x-thrift\r\n" .
+ "User-Agent: PHP/THttpClient\r\n" .
+ "Content-Type: application/x-thrift\r\n" .
+ "Content-Length: 0\r\n" .
+ "X-Test-Header: test",
+ 'content' => '',
+ 'max_redirects' => 1,
+ ],
+ ],
+ ]
+ );
+ yield 'timeout' => array_merge(
+ $default,
+ [
+ 'timeout' => 1000,
+ 'streamContextOptions' => [
+ 'http' => [
+ 'method' => 'POST',
+ 'header' => "Host: localhost\r\n" .
+ "Accept: application/x-thrift\r\n" .
+ "User-Agent: PHP/THttpClient\r\n" .
+ "Content-Type: application/x-thrift\r\n" .
+ "Content-Length: 0",
+ 'content' => '',
+ 'max_redirects' => 1,
+ 'timeout' => 1000,
+ ],
+ ],
+ ]
+ );
+ yield 'fopenFailed' => array_merge(
+ $default,
+ [
+ 'host' => 'localhost',
+ 'port' => 8080,
+ 'uri' => 'test',
+ 'expectedHost' => 'localhost:8080',
+ 'expectedUri' => '/test',
+ 'streamContextOptions' => [
+ 'http' => [
+ 'method' => 'POST',
+ 'header' => "Host: localhost:8080\r\n" .
+ "Accept: application/x-thrift\r\n" .
+ "User-Agent: PHP/THttpClient\r\n" .
+ "Content-Type: application/x-thrift\r\n" .
+ "Content-Length: 0",
+ 'content' => '',
+ 'max_redirects' => 1,
+ ],
+ ],
+ 'fopenResult' => false,
+ 'expectedException' => TTransportException::class,
+ 'expectedExceptionMessage' => 'THttpClient: Could not connect to localhost:8080/test',
+ 'expectedExceptionCode' => TTransportException::NOT_OPEN,
+ ]
+ );
+ }
+
+ public function testAddHeaders()
+ {
+ $host = 'localhost';
+ $transport = new THttpClient($host);
+
+ $ref = new \ReflectionClass($transport);
+ $propRequest = $ref->getProperty('headers_');
+ $propRequest->setAccessible(true);
+ $propRequest->setValue($transport, ['test' => '1234567890']);
+
+ $transport->addHeaders(['test2' => '12345']);
+ $this->assertEquals(['test' => '1234567890', 'test2' => '12345'], $propRequest->getValue($transport));
+ }
+}
diff --git a/lib/php/test/Unit/Lib/Transport/TMemoryBufferTest.php b/lib/php/test/Unit/Lib/Transport/TMemoryBufferTest.php
new file mode 100644
index 0000000..06f0012
--- /dev/null
+++ b/lib/php/test/Unit/Lib/Transport/TMemoryBufferTest.php
@@ -0,0 +1,143 @@
+<?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\Thrift\Unit\Lib\Transport;
+
+use PHPUnit\Framework\TestCase;
+use Thrift\Exception\TTransportException;
+use Thrift\Transport\TMemoryBuffer;
+
+class TMemoryBufferTest extends TestCase
+{
+ public function testIsOpen()
+ {
+ $transport = new TMemoryBuffer();
+ $this->assertTrue($transport->isOpen());
+ }
+
+ public function testOpen()
+ {
+ $transport = new TMemoryBuffer();
+ $this->assertNull($transport->open());
+ }
+
+ public function testClose()
+ {
+ $transport = new TMemoryBuffer();
+ $this->assertNull($transport->close());
+ }
+
+ public function testReadEmptyBuffer()
+ {
+ $transport = new TMemoryBuffer();
+ $this->expectException(\Thrift\Exception\TTransportException::class);
+ $this->expectExceptionMessage("TMemoryBuffer: Could not read 1 bytes from buffer.");
+ $this->expectExceptionCode(TTransportException::UNKNOWN);
+ $transport->read(1);
+ }
+
+ /**
+ * @dataProvider readDataProvider
+ */
+ public function testRead(
+ $startBuffer,
+ $readLength,
+ $expectedRead,
+ $expectedBuffer
+ ) {
+ $transport = new TMemoryBuffer($startBuffer);
+ $this->assertEquals($expectedRead, $transport->read($readLength));
+ $this->assertEquals($expectedBuffer, $transport->getBuffer());
+ }
+
+ public function readDataProvider()
+ {
+ yield 'Read part of buffer' => [
+ 'startBuffer' => '1234567890',
+ 'readLength' => 5,
+ 'expectedRead' => '12345',
+ 'expectedBuffer' => '67890',
+ ];
+ yield 'Read part of buffer UTF' => [
+ 'startBuffer' => 'Slovenščina',
+ 'readLength' => 6,
+ 'expectedRead' => 'Sloven',
+ 'expectedBuffer' => 'ščina',
+ ];
+ yield 'Read part of buffer UTF 2' => [
+ 'startBuffer' => 'Українська',
+ 'readLength' => 6,
+ 'expectedRead' => 'Укр',
+ 'expectedBuffer' => 'аїнська',
+ ];
+ yield 'Read full' => [
+ 'startBuffer' => '123456789',
+ 'readLength' => 10,
+ 'expectedRead' => '123456789',
+ 'expectedBuffer' => '',
+ ];
+ }
+
+ /**
+ * @dataProvider writeDataProvider
+ */
+ public function testWrite(
+ $startBuffer,
+ $writeData,
+ $expectedBuffer
+ ) {
+ $transport = new TMemoryBuffer($startBuffer);
+ $transport->write($writeData);
+ $this->assertEquals($expectedBuffer, $transport->getBuffer());
+ }
+
+ public function writeDataProvider()
+ {
+ yield 'empty start buffer' => [
+ 'startBuffer' => '',
+ 'writeData' => '12345',
+ 'expectedBuffer' => '12345',
+ ];
+ yield 'not empty start buffer' => [
+ 'startBuffer' => '67890',
+ 'writeData' => '12345',
+ 'expectedBuffer' => '6789012345',
+ ];
+ yield 'not empty start buffer UTF' => [
+ 'startBuffer' => 'Slovenščina',
+ 'writeData' => 'Українська',
+ 'expectedBuffer' => 'SlovenščinaУкраїнська',
+ ];
+ }
+
+ public function testAvailable()
+ {
+ $transport = new TMemoryBuffer('12345');
+ $this->assertEquals('5', $transport->available());
+ }
+
+ public function testPutBack()
+ {
+ $transport = new TMemoryBuffer('12345');
+ $transport->putBack('67890');
+ $this->assertEquals('6789012345', $transport->getBuffer());
+ }
+}
diff --git a/lib/php/test/Unit/Lib/Transport/TNullTransportTest.php b/lib/php/test/Unit/Lib/Transport/TNullTransportTest.php
new file mode 100644
index 0000000..044c703
--- /dev/null
+++ b/lib/php/test/Unit/Lib/Transport/TNullTransportTest.php
@@ -0,0 +1,62 @@
+<?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\Thrift\Unit\Lib\Transport;
+
+use PHPUnit\Framework\TestCase;
+use Thrift\Exception\TTransportException;
+use Thrift\Transport\TNullTransport;
+
+class TNullTransportTest extends TestCase
+{
+ public function testIsOpen()
+ {
+ $transport = new TNullTransport();
+ $this->assertTrue($transport->isOpen());
+ }
+
+ public function testOpen()
+ {
+ $transport = new TNullTransport();
+ $this->assertNull($transport->open());
+ }
+
+ public function testClose()
+ {
+ $transport = new TNullTransport();
+ $this->assertNull($transport->close());
+ }
+
+ public function testRead()
+ {
+ $transport = new TNullTransport();
+ $this->expectException(TTransportException::class);
+ $this->expectExceptionMessage("Can't read from TNullTransport.");
+ $this->expectExceptionCode(0);
+ $transport->read(1);
+ }
+
+ public function testWrite()
+ {
+ $transport = new TNullTransport();
+ $this->assertNull($transport->write('test'));
+ }
+}
diff --git a/lib/php/test/Unit/Lib/Transport/TPhpStreamTest.php b/lib/php/test/Unit/Lib/Transport/TPhpStreamTest.php
new file mode 100644
index 0000000..c2f950c
--- /dev/null
+++ b/lib/php/test/Unit/Lib/Transport/TPhpStreamTest.php
@@ -0,0 +1,296 @@
+<?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\Thrift\Unit\Lib\Transport;
+
+use phpmock\phpunit\PHPMock;
+use PHPUnit\Framework\TestCase;
+use Thrift\Exception\TException;
+use Thrift\Transport\TPhpStream;
+
+class TPhpStreamTest extends TestCase
+{
+ use PHPMock;
+
+ /**
+ * @dataProvider fopenDataProvider
+ */
+ public function testOpen(
+ $mode,
+ $sapiName,
+ $fopenParams,
+ $fopenResult,
+ $expectedException,
+ $expectedExceptionMessage,
+ $expectedExceptionCode
+ ) {
+ #due to the running tests in separate process we could not open stream in data provider, so we need to do it here
+ foreach ($fopenResult as $num => $result) {
+ $fopenResult[$num] = $result ? fopen(...$result) : $result;
+ }
+
+ $this->getFunctionMock('Thrift\Transport', 'php_sapi_name')
+ ->expects(!empty($sapiName) ? $this->once() : $this->never())
+ ->willReturn($sapiName);
+
+ $this->getFunctionMock('Thrift\Transport', 'fopen')
+ ->expects($this->exactly(count($fopenResult)))
+ ->withConsecutive(...$fopenParams)
+ ->willReturnOnConsecutiveCalls(...$fopenResult);
+
+ if ($expectedException) {
+ $this->expectException($expectedException);
+ $this->expectExceptionMessage($expectedExceptionMessage);
+ $this->expectExceptionCode($expectedExceptionCode);
+ }
+
+ $transport = new TPhpStream($mode);
+ $transport->open();
+ }
+
+ public function fopenDataProvider()
+ {
+ yield 'readCli' => [
+ 'mode' => TPhpStream::MODE_R,
+ 'sapiName' => 'cli',
+ 'fopenParams' => [['php://stdin', 'r']],
+ 'fopenResult' => [['php://temp', 'r']],
+ 'expectedException' => null,
+ 'expectedExceptionMessage' => '',
+ 'expectedExceptionCode' => 0,
+ ];
+ yield 'readNotCli' => [
+ 'mode' => TPhpStream::MODE_R,
+ 'sapiName' => 'apache',
+ 'fopenParams' => [['php://input', 'r']],
+ 'fopenResult' => [['php://temp', 'r']],
+ 'expectedException' => null,
+ 'expectedExceptionMessage' => '',
+ 'expectedExceptionCode' => 0,
+ ];
+ yield 'write' => [
+ 'mode' => TPhpStream::MODE_W,
+ 'sapiName' => '',
+ 'fopenParams' => [['php://output', 'w']],
+ 'fopenResult' => [['php://temp', 'w']],
+ 'expectedException' => null,
+ 'expectedExceptionMessage' => '',
+ 'expectedExceptionCode' => 0,
+ ];
+ yield 'read and write' => [
+ 'mode' => TPhpStream::MODE_R | TPhpStream::MODE_W,
+ 'sapiName' => 'cli',
+ 'fopenParams' => [['php://stdin', 'r'], ['php://output', 'w']],
+ 'fopenResult' => [['php://temp', 'r'], ['php://temp', 'w']],
+ 'expectedException' => null,
+ 'expectedExceptionMessage' => '',
+ 'expectedExceptionCode' => 0,
+ ];
+ yield 'read exception' => [
+ 'mode' => TPhpStream::MODE_R,
+ 'sapiName' => 'cli',
+ 'fopenParams' => [['php://stdin', 'r']],
+ 'fopenResult' => [false],
+ 'expectedException' => TException::class,
+ 'expectedExceptionMessage' => 'TPhpStream: Could not open php://input',
+ #should depend on php_sapi_name result
+ 'expectedExceptionCode' => 0,
+ ];
+ yield 'write exception' => [
+ 'mode' => TPhpStream::MODE_W,
+ 'sapiName' => '',
+ 'fopenParams' => [['php://output', 'w']],
+ 'fopenResult' => [false],
+ 'expectedException' => TException::class,
+ 'expectedExceptionMessage' => 'TPhpStream: Could not open php://output',
+ 'expectedExceptionCode' => 0,
+ ];
+ }
+
+ /**
+ * @dataProvider closeDataProvider
+ */
+ public function testClose(
+ $mode,
+ $fopenParams,
+ $fopenResult
+ ) {
+ #due to the running tests in separate process we could not open stream in data provider, so we need to do it here
+ foreach ($fopenResult as $num => $result) {
+ $fopenResult[$num] = $result ? fopen(...$result) : $result;
+ }
+
+ $this->getFunctionMock('Thrift\Transport', 'fopen')
+ ->expects($this->exactly(count($fopenParams)))
+ ->withConsecutive(...$fopenParams)
+ ->willReturnOnConsecutiveCalls(...$fopenResult);
+
+ $this->getFunctionMock('Thrift\Transport', 'fclose')
+ ->expects($this->exactly(count($fopenParams)))
+ ->with(
+ $this->callback(function ($stream) {
+ return is_resource($stream);
+ })
+ )
+ ->willReturn(true);
+
+ $transport = new TPhpStream($mode);
+ $transport->open();
+ $this->assertTrue($transport->isOpen());
+
+ $transport->close();
+ $this->assertFalse($transport->isOpen());
+ }
+
+ public function closeDataProvider()
+ {
+ $read = ['php://temp', 'r'];
+ $write = ['php://temp', 'w'];
+ yield 'read' => [
+ 'mode' => TPhpStream::MODE_R,
+ 'fopenParams' => [['php://stdin', 'r']],
+ 'fopenResult' => [$read],
+ ];
+ yield 'write' => [
+ 'mode' => TPhpStream::MODE_W,
+ 'fopenParams' => [['php://output', 'w']],
+ 'fopenResult' => [$write],
+ ];
+ yield 'read and write' => [
+ 'mode' => TPhpStream::MODE_R | TPhpStream::MODE_W,
+ 'fopenParams' => [['php://stdin', 'r'], ['php://output', 'w']],
+ 'fopenResult' => [$read, $write],
+ ];
+ }
+
+ /**
+ * @dataProvider readDataProvider
+ */
+ public function testRead(
+ $freadResult,
+ $expectedResult,
+ $expectedException,
+ $expectedExceptionMessage,
+ $expectedExceptionCode
+ ) {
+ $this->getFunctionMock('Thrift\Transport', 'fread')
+ ->expects($this->once())
+ ->with($this->anything(), 5)
+ ->willReturn($freadResult);
+
+ if ($expectedException) {
+ $this->expectException($expectedException);
+ $this->expectExceptionMessage($expectedExceptionMessage);
+ $this->expectExceptionCode($expectedExceptionCode);
+ }
+
+ $transport = new TPhpStream(TPhpStream::MODE_R);
+ $this->assertEquals($expectedResult, $transport->read(5));
+ }
+
+ public function readDataProvider()
+ {
+ yield 'success' => [
+ 'freadResult' => '12345',
+ 'expectedResult' => '12345',
+ 'expectedException' => null,
+ 'expectedExceptionMessage' => '',
+ 'expectedExceptionCode' => 0,
+ ];
+ yield 'empty' => [
+ 'freadResult' => '',
+ 'expectedResult' => '',
+ 'expectedException' => TException::class,
+ 'expectedExceptionMessage' => 'TPhpStream: Could not read 5 bytes',
+ 'expectedExceptionCode' => 0,
+ ];
+ yield 'false' => [
+ 'freadResult' => false,
+ 'expectedResult' => false,
+ 'expectedException' => TException::class,
+ 'expectedExceptionMessage' => 'TPhpStream: Could not read 5 bytes',
+ 'expectedExceptionCode' => 0,
+ ];
+ }
+
+ /**
+ * @dataProvider writeDataProvider
+ */
+ public function testWrite(
+ $buf,
+ $fwriteParams,
+ $fwriteResult,
+ $expectedException,
+ $expectedExceptionMessage,
+ $expectedExceptionCode
+ ) {
+ $this->getFunctionMock('Thrift\Transport', 'fwrite')
+ ->expects($this->exactly(count($fwriteParams)))
+ ->withConsecutive(...$fwriteParams)
+ ->willReturnOnConsecutiveCalls(...$fwriteResult);
+
+ if ($expectedException) {
+ $this->expectException($expectedException);
+ $this->expectExceptionMessage($expectedExceptionMessage);
+ $this->expectExceptionCode($expectedExceptionCode);
+ }
+
+ $transport = new TPhpStream(TPhpStream::MODE_W);
+ $transport->write($buf);
+ }
+
+ public function writeDataProvider()
+ {
+ yield 'success' => [
+ 'buf' => '12345',
+ 'fwriteParams' => [[$this->anything(), '12345']],
+ 'fwriteResult' => [5],
+ 'expectedException' => null,
+ 'expectedExceptionMessage' => '',
+ 'expectedExceptionCode' => 0,
+ ];
+ yield 'several iteration' => [
+ 'buf' => '1234567890',
+ 'fwriteParams' => [[$this->anything(), '1234567890'], [$this->anything(), '67890']],
+ 'fwriteResult' => [5, 5],
+ 'expectedException' => null,
+ 'expectedExceptionMessage' => '',
+ 'expectedExceptionCode' => 0,
+ ];
+ yield 'fail' => [
+ 'buf' => '1234567890',
+ 'fwriteParams' => [[$this->anything(), '1234567890']],
+ 'fwriteResult' => [false],
+ 'expectedException' => TException::class,
+ 'expectedExceptionMessage' => 'TPhpStream: Could not write 10 bytes',
+ 'expectedExceptionCode' => 0,
+ ];
+ }
+
+ public function testFlush()
+ {
+ $this->getFunctionMock('Thrift\Transport', 'fflush')
+ ->expects($this->once());
+
+ $transport = new TPhpStream(TPhpStream::MODE_R);
+ $transport->flush();
+ }
+}
diff --git a/lib/php/test/Unit/Lib/Transport/TSSLSocketTest.php b/lib/php/test/Unit/Lib/Transport/TSSLSocketTest.php
new file mode 100644
index 0000000..7177219
--- /dev/null
+++ b/lib/php/test/Unit/Lib/Transport/TSSLSocketTest.php
@@ -0,0 +1,247 @@
+<?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\Thrift\Unit\Lib\Transport;
+
+use phpmock\phpunit\PHPMock;
+use PHPUnit\Framework\TestCase;
+use Thrift\Exception\TException;
+use Thrift\Exception\TTransportException;
+use Thrift\Transport\TSSLSocket;
+
+class TSSLSocketTest extends TestCase
+{
+ use PHPMock;
+
+ /**
+ * @dataProvider openExceptionDataProvider
+ */
+ public function testOpenException(
+ $host,
+ $port,
+ $context,
+ $debugHandler,
+ $streamSocketClientCallCount,
+ $expectedException,
+ $expectedMessage,
+ $expectedCode
+ ) {
+ $this->expectException($expectedException);
+ $this->expectExceptionMessage($expectedMessage);
+ $this->expectExceptionCode($expectedCode);
+
+ $this->getFunctionMock('Thrift\Transport', 'stream_socket_client')
+ ->expects($this->exactly($streamSocketClientCallCount))
+ ->with(
+ 'ssl://' . $host . ':' . $port,
+ $this->anything(), #$errno,
+ $this->anything(), #$errstr,
+ $this->anything(), #$this->sendTimeoutSec_ + ($this->sendTimeoutUsec_ / 1000000),
+ STREAM_CLIENT_CONNECT,
+ $this->anything() #$context
+ )
+ ->willReturn(false);
+
+ $socket = new TSSLSocket(
+ $host,
+ $port,
+ $context,
+ $debugHandler
+ );
+ $socket->open();
+ }
+
+ public function openExceptionDataProvider()
+ {
+ yield 'host is empty' => [
+ 'host' => '',
+ 'port' => 9090,
+ 'context' => null,
+ 'debugHandler' => null,
+ 'streamSocketClientCallCount' => 0,
+ 'expectedException' => TTransportException::class,
+ 'expectedMessage' => 'Cannot open null host',
+ 'expectedCode' => TTransportException::NOT_OPEN,
+ ];
+ yield 'port is not positive' => [
+ 'host' => 'localhost',
+ 'port' => 0,
+ 'context' => null,
+ 'debugHandler' => null,
+ 'streamSocketClientCallCount' => 0,
+ 'expectedException' => TTransportException::class,
+ 'expectedMessage' => 'Cannot open without port',
+ 'expectedCode' => TTransportException::NOT_OPEN,
+ ];
+ yield 'connection failure' => [
+ 'host' => 'nonexistent-host',
+ 'port' => 9090,
+ 'context' => null,
+ 'debugHandler' => null,
+ 'streamSocketClientCallCount' => 1,
+ 'expectedException' => TException::class,
+ 'expectedMessage' => 'TSocket: Could not connect to',
+ 'expectedCode' => TTransportException::UNKNOWN,
+ ];
+ }
+
+ public function testDoubleConnect(): void
+ {
+ $host = 'localhost';
+ $port = 9090;
+ $context = null;
+ $debugHandler = null;
+
+ $this->getFunctionMock('Thrift\Transport', 'stream_socket_client')
+ ->expects($this->once())
+ ->with(
+ 'ssl://' . $host . ':' . $port,
+ $this->anything(), #$errno,
+ $this->anything(), #$errstr,
+ $this->anything(), #$this->sendTimeoutSec_ + ($this->sendTimeoutUsec_ / 1000000),
+ STREAM_CLIENT_CONNECT,
+ $this->anything() #$context
+ )
+ ->willReturn(fopen('php://memory', 'r+'));
+
+ $transport = new TSSLSocket(
+ $host,
+ $port,
+ $context,
+ $debugHandler
+ );
+
+ $transport->open();
+ $this->expectException(TTransportException::class);
+ $this->expectExceptionMessage('Socket already connected');
+ $this->expectExceptionCode(TTransportException::ALREADY_OPEN);
+ $transport->open();
+ }
+
+ public function testDebugHandler()
+ {
+ $host = 'nonexistent-host';
+ $port = 9090;
+ $context = null;
+
+ $debugHandler = function ($error) {
+ $this->assertEquals(
+ 'TSocket: Could not connect to ssl://nonexistent-host:9090 (Connection refused [999])',
+ $error
+ );
+ };
+
+ $this->getFunctionMock('Thrift\Transport', 'stream_socket_client')
+ ->expects($this->once())
+ ->with(
+ 'ssl://' . $host . ':' . $port,
+ $this->anything(), #$errno,
+ $this->anything(), #$errstr,
+ $this->anything(), #$this->sendTimeoutSec_ + ($this->sendTimeoutUsec_ / 1000000),
+ STREAM_CLIENT_CONNECT,
+ $this->anything() #$context
+ )
+ ->willReturnCallback(
+ function ($host, &$error_code, &$error_message, $timeout, $flags, $context) {
+ $error_code = 999;
+ $error_message = 'Connection refused';
+
+ return false;
+ }
+ );
+
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage('TSocket: Could not connect to');
+ $this->expectExceptionCode(0);
+
+ $transport = new TSSLSocket(
+ $host,
+ $port,
+ $context,
+ $debugHandler
+ );
+ $transport->setDebug(true);
+ $transport->open();
+ }
+
+ public function testOpenWithContext()
+ {
+ $host = 'self-signed-localhost';
+ $port = 9090;
+ $context = stream_context_create(
+ [
+ 'ssl' => [
+ 'verify_peer' => true,
+ 'verify_peer_name' => true,
+ 'allow_self_signed' => true,
+ ],
+ ]
+ );
+ $debugHandler = null;
+
+ $this->getFunctionMock('Thrift\Transport', 'stream_socket_client')
+ ->expects($this->once())
+ ->with(
+ 'ssl://' . $host . ':' . $port,
+ $this->anything(), #$errno,
+ $this->anything(), #$errstr,
+ $this->anything(), #$this->sendTimeoutSec_ + ($this->sendTimeoutUsec_ / 1000000),
+ STREAM_CLIENT_CONNECT,
+ $context #$context
+ )
+ ->willReturn(fopen('php://memory', 'r+'));
+
+ $transport = new TSSLSocket(
+ $host,
+ $port,
+ $context,
+ $debugHandler
+ );
+
+
+ $transport->open();
+ $this->assertTrue($transport->isOpen());
+ }
+
+ /**
+ * @dataProvider hostDataProvider
+ */
+ public function testGetHost($host, $expected)
+ {
+ $port = 9090;
+ $context = null;
+ $debugHandler = null;
+ $transport = new TSSLSocket(
+ $host,
+ $port,
+ $context,
+ $debugHandler
+ );
+ $this->assertEquals($expected, $transport->getHost());
+ }
+
+ public function hostDataProvider()
+ {
+ yield 'localhost' => ['localhost', 'ssl://localhost'];
+ yield 'ssl_localhost' => ['ssl://localhost', 'ssl://localhost'];
+ yield 'http_localhost' => ['http://localhost', 'http://localhost'];
+ }
+}
diff --git a/lib/php/test/Unit/Lib/Transport/TSocketPoolTest.php b/lib/php/test/Unit/Lib/Transport/TSocketPoolTest.php
new file mode 100644
index 0000000..01e4532
--- /dev/null
+++ b/lib/php/test/Unit/Lib/Transport/TSocketPoolTest.php
@@ -0,0 +1,541 @@
+<?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\Thrift\Unit\Lib\Transport;
+
+use phpmock\phpunit\PHPMock;
+use PHPUnit\Framework\TestCase;
+use Thrift\Exception\TException;
+use Thrift\Transport\TSocketPool;
+
+class TSocketPoolTest extends TestCase
+{
+ use PHPMock;
+
+ protected function setUp(): void
+ {
+ #need to be defined before the TSocketPool class definition
+ self::defineFunctionMock('Thrift\Transport', 'function_exists');
+ }
+
+ /**
+ * @dataProvider constructDataProvider
+ */
+ public function testConstruct(
+ $hosts,
+ $ports,
+ $persist,
+ $debugHandler,
+ $expectedServers
+ ) {
+ $socketPool = new TSocketPool($hosts, $ports, $persist, $debugHandler);
+
+ $ref = new \ReflectionObject($socketPool);
+ $serversProp = $ref->getProperty('servers_');
+ $serversProp->setAccessible(true);
+
+ $this->assertEquals($expectedServers, $serversProp->getValue($socketPool));
+ }
+
+
+ public function constructDataProvider()
+ {
+ yield 'one server' => [
+ ['localhost'],
+ [9090],
+ false,
+ null,
+ [
+ ['host' => 'localhost', 'port' => 9090],
+ ],
+ ];
+ yield 'two servers' => [
+ ['localhost1', 'localhost2'],
+ [9090, 9091],
+ false,
+ null,
+ [
+ ['host' => 'localhost1', 'port' => 9090],
+ ['host' => 'localhost2', 'port' => 9091],
+ ],
+ ];
+ yield 'one server with one port' => [
+ ['localhost'],
+ 9090,
+ false,
+ null,
+ [
+ ['host' => 'localhost', 'port' => 9090],
+ ],
+ ];
+ yield 'two servers with one port' => [
+ ['localhost1', 'localhost2'],
+ 9090,
+ false,
+ null,
+ [
+ ['host' => 'localhost1', 'port' => 9090],
+ ['host' => 'localhost2', 'port' => 9090],
+ ],
+ ];
+ }
+
+ public function testAddServer(): void
+ {
+ $socketPool = new TSocketPool([], []);
+ $socketPool->addServer('localhost', 9090);
+
+ $ref = new \ReflectionObject($socketPool);
+ $servers = $ref->getProperty('servers_');
+ $servers->setAccessible(true);
+
+ $this->assertEquals([['host' => 'localhost', 'port' => 9090]], $servers->getValue($socketPool));
+ }
+
+ public function testSetNumRetries(): void
+ {
+ $socketPool = new TSocketPool([], []);
+ $socketPool->setNumRetries(5);
+
+ $ref = new \ReflectionObject($socketPool);
+ $numRetries = $ref->getProperty('numRetries_');
+ $numRetries->setAccessible(true);
+
+ $this->assertEquals(5, $numRetries->getValue($socketPool));
+ }
+
+ public function testrSetRetryInterval(): void
+ {
+ $socketPool = new TSocketPool([], []);
+ $socketPool->setRetryInterval(5);
+
+ $ref = new \ReflectionObject($socketPool);
+ $retryInterval = $ref->getProperty('retryInterval_');
+ $retryInterval->setAccessible(true);
+
+ $this->assertEquals(5, $retryInterval->getValue($socketPool));
+ }
+
+ public function testrSetMaxConsecutiveFailures(): void
+ {
+ $socketPool = new TSocketPool([], []);
+ $socketPool->setMaxConsecutiveFailures(5);
+
+ $ref = new \ReflectionObject($socketPool);
+ $maxConsecutiveFailures = $ref->getProperty('maxConsecutiveFailures_');
+ $maxConsecutiveFailures->setAccessible(true);
+
+ $this->assertEquals(5, $maxConsecutiveFailures->getValue($socketPool));
+ }
+
+ public function testrSetRandomize(): void
+ {
+ $socketPool = new TSocketPool([], []);
+ $socketPool->setRandomize(false);
+
+ $ref = new \ReflectionObject($socketPool);
+ $randomize = $ref->getProperty('randomize_');
+ $randomize->setAccessible(true);
+
+ $this->assertEquals(false, $randomize->getValue($socketPool));
+ }
+
+ public function testrSetAlwaysTryLast(): void
+ {
+ $socketPool = new TSocketPool([], []);
+ $socketPool->setAlwaysTryLast(false);
+
+ $ref = new \ReflectionObject($socketPool);
+ $alwaysTryLast = $ref->getProperty('alwaysTryLast_');
+ $alwaysTryLast->setAccessible(true);
+
+ $this->assertEquals(false, $alwaysTryLast->getValue($socketPool));
+ }
+
+ /**
+ * @dataProvider openDataProvider
+ */
+ public function testOpen(
+ $hosts,
+ $ports,
+ $persist,
+ $debugHandler,
+ $randomize,
+ $retryInterval,
+ $numRetries,
+ $maxConsecutiveFailures,
+ $debug,
+ $servers,
+ $functionExistCallParams,
+ $functionExistResult,
+ $apcuFetchCallParams,
+ $apcuFetchResult,
+ $timeResult,
+ $debugHandlerCall,
+ $apcuStoreCallParams,
+ $fsockopenCallParams,
+ $fsockopenResult,
+ $expectedException,
+ $expectedExceptionMessage
+ ) {
+ $this->getFunctionMock('Thrift\Transport', 'function_exists')
+ ->expects($this->exactly(count($functionExistCallParams)))
+ ->withConsecutive(...$functionExistCallParams)
+ ->willReturnOnConsecutiveCalls(...$functionExistResult);
+
+ $this->getFunctionMock('Thrift\Transport', 'shuffle')
+ ->expects($randomize ? $this->once() : $this->never())
+ ->with($servers)
+ ->willReturnCallback(function (array &$servers) {
+ $servers = array_reverse($servers);
+
+ return true;
+ });
+
+ $this->getFunctionMock('Thrift\Transport', 'apcu_fetch')
+ ->expects($this->exactly(count($apcuFetchCallParams)))
+ ->withConsecutive(...$apcuFetchCallParams)
+ ->willReturnOnConsecutiveCalls(...$apcuFetchResult);
+
+ $this->getFunctionMock('Thrift\Transport', 'call_user_func')
+ ->expects($this->exactly(count($debugHandlerCall)))
+ ->withConsecutive(...$debugHandlerCall)
+ ->willReturn(true);
+
+ $this->getFunctionMock('Thrift\Transport', 'apcu_store')
+ ->expects($this->exactly(count($apcuStoreCallParams)))
+ ->withConsecutive(...$apcuStoreCallParams)
+ ->willReturn(true);
+
+ $this->getFunctionMock('Thrift\Transport', 'time')
+ ->expects($this->exactly(count($timeResult)))
+ ->willReturnOnConsecutiveCalls(...$timeResult);
+
+ #due to the running tests in separate process we could not open stream in data provider, so we need to do it here
+ foreach ($fsockopenResult as $num => $result) {
+ $fsockopenResult[$num] = $result ? fopen(...$result) : $result;
+ }
+
+ $this->getFunctionMock('Thrift\Transport', $persist ? 'pfsockopen' : 'fsockopen')
+ ->expects($this->exactly(count($fsockopenCallParams)))
+ ->withConsecutive(...$fsockopenCallParams)
+ ->willReturnOnConsecutiveCalls(...$fsockopenResult);
+
+ $this->getFunctionMock('Thrift\Transport', 'socket_import_stream')
+ ->expects(is_null($expectedException) ? $this->once() : $this->never())
+ ->with(
+ $this->callback(function ($stream) {
+ return is_resource($stream);
+ })
+ )
+ ->willReturn(true);
+
+ $this->getFunctionMock('Thrift\Transport', 'socket_set_option')
+ ->expects(is_null($expectedException) ? $this->once() : $this->never())
+ ->with(
+ $this->anything(), #$socket,
+ SOL_TCP, #$level
+ TCP_NODELAY, #$option
+ 1 #$value
+ )
+ ->willReturn(true);
+
+ if ($expectedException) {
+ $this->expectException($expectedException);
+ $this->expectExceptionMessage($expectedExceptionMessage);
+ }
+
+ $socketPool = new TSocketPool($hosts, $ports, $persist, $debugHandler);
+ $socketPool->setRandomize($randomize);
+ $socketPool->setRetryInterval($retryInterval);
+ $socketPool->setNumRetries($numRetries);
+ $socketPool->setMaxConsecutiveFailures($maxConsecutiveFailures);
+ $socketPool->setDebug($debug);
+
+ $this->assertNull($socketPool->open());
+ }
+
+ public function openDataProvider()
+ {
+ $default = [
+ 'hosts' => ['localhost'],
+ 'ports' => [9090],
+ 'persist' => false,
+ 'debugHandler' => null,
+ 'randomize' => true,
+ 'retryInterval' => 5,
+ 'numRetries' => 1,
+ 'maxConsecutiveFailures' => 1,
+ 'debug' => false,
+ 'servers' => [
+ ['host' => 'localhost', 'port' => 9090],
+ ],
+ 'functionExistCallParams' => [
+ ['apcu_fetch'],
+ ['socket_import_stream'],
+ ['socket_set_option'],
+ ],
+ 'functionExistResult' => [
+ true,
+ true,
+ true,
+ ],
+ 'apcuFetchCallParams' => [
+ ['thrift_failtime:localhost:9090~', $this->anything()],
+ ],
+ 'apcuFetchResult' => [
+ false,
+ ],
+ 'timeResult' => [],
+ 'debugHandlerCall' => [],
+ 'apcuStoreCallParams' => [],
+ 'fsockopenCallParams' => [
+ [
+ 'localhost',
+ 9090,
+ $this->anything(), #$errno,
+ $this->anything(), #$errstr,
+ $this->anything(), #$this->sendTimeoutSec_ + ($this->sendTimeoutUsec_ / 1000000),
+ ],
+ ],
+ 'fsockopenResult' => [
+ ['php://temp', 'r'],
+ ],
+ 'expectedException' => null,
+ 'expectedExceptionMessage' => null,
+ ];
+
+ yield 'one server ready' => $default;
+ yield 'one server failed' => array_merge(
+ $default,
+ [
+ 'functionExistCallParams' => [
+ ['apcu_fetch'],
+ ],
+ 'fsockopenResult' => [
+ false,
+ ],
+ 'apcuFetchCallParams' => [
+ ['thrift_failtime:localhost:9090~', $this->anything()],
+ ['thrift_consecfails:localhost:9090~', $this->anything()],
+ ],
+ 'apcuStoreCallParams' => [
+ ['thrift_failtime:localhost:9090~', $this->anything()],
+ ['thrift_consecfails:localhost:9090~', $this->anything(), 0],
+ ],
+ 'timeResult' => [
+ 1,
+ ],
+ 'expectedException' => TException::class,
+ 'expectedExceptionMessage' => 'TSocketPool: All hosts in pool are down. (localhost:9090)',
+ ]
+ );
+ yield 'connect to one server on second attempt' => array_merge(
+ $default,
+ [
+ 'numRetries' => 2,
+ 'fsockopenCallParams' => [
+ [
+ 'localhost',
+ 9090,
+ $this->anything(), #$errno,
+ $this->anything(), #$errstr,
+ $this->anything(), #$this->sendTimeoutSec_ + ($this->sendTimeoutUsec_ / 1000000),
+ ],
+ [
+ 'localhost',
+ 9090,
+ $this->anything(), #$errno,
+ $this->anything(), #$errstr,
+ $this->anything(), #$this->sendTimeoutSec_ + ($this->sendTimeoutUsec_ / 1000000),
+ ],
+ ],
+ 'fsockopenResult' => [
+ false,
+ ['php://temp', 'r'],
+ ],
+ 'apcuStoreCallParams' => [],
+ ]
+ );
+ yield 'last time fail time is not expired' => array_merge(
+ $default,
+ [
+ 'retryInterval' => 5,
+ 'apcuFetchResult' => [
+ 99,
+ ],
+ 'apcuStoreCallParams' => [
+ ['thrift_failtime:localhost:9090~', $this->anything()],
+ ],
+ 'timeResult' => [
+ 100,
+ ],
+ ]
+ );
+ yield 'last time fail time is expired, store info to debug' => array_merge(
+ $default,
+ [
+ 'retryInterval' => 5,
+ 'apcuFetchResult' => [
+ 90,
+ ],
+ 'apcuStoreCallParams' => [
+ ['thrift_failtime:localhost:9090~', $this->anything()],
+ ],
+ 'timeResult' => [
+ 100,
+ ],
+ 'debug' => true,
+ 'debugHandlerCall' => [
+ ['error_log', 'TSocketPool: retryInterval (5) has passed for host localhost:9090'],
+ ],
+ ]
+ );
+ yield 'not accessible server, store info to debug' => array_merge(
+ $default,
+ [
+ 'retryInterval' => 5,
+ 'functionExistCallParams' => [
+ ['apcu_fetch'],
+ ],
+ 'functionExistResult' => [
+ true,
+ ],
+ 'apcuFetchCallParams' => [
+ ['thrift_failtime:localhost:9090~', $this->anything()],
+ ['thrift_consecfails:localhost:9090~', $this->anything()],
+ ],
+ 'apcuFetchResult' => [
+ 90,
+ ],
+ 'apcuStoreCallParams' => [
+ ['thrift_failtime:localhost:9090~', $this->anything()],
+ ['thrift_consecfails:localhost:9090~', 0],
+ ],
+ 'timeResult' => [
+ 100,
+ 101,
+ ],
+ 'fsockopenResult' => [
+ false,
+ ],
+ 'debug' => true,
+ 'debugHandlerCall' => [
+ ['error_log', 'TSocketPool: retryInterval (5) has passed for host localhost:9090'],
+ ['error_log', 'TSocket: Could not connect to localhost:9090 ( [])'],
+ ['error_log', 'TSocketPool: marking localhost:9090 as down for 5 secs after 1 failed attempts.'],
+ ['error_log', 'TSocketPool: All hosts in pool are down. (localhost:9090)'],
+ ],
+ 'expectedException' => TException::class,
+ 'expectedExceptionMessage' => 'TSocketPool: All hosts in pool are down. (localhost:9090)',
+ ]
+ );
+ yield 'max consecutive failures' => array_merge(
+ $default,
+ [
+ 'maxConsecutiveFailures' => 5,
+ 'functionExistCallParams' => [
+ ['apcu_fetch'],
+ ],
+ 'functionExistResult' => [
+ true,
+ ],
+ 'apcuFetchCallParams' => [
+ ['thrift_failtime:localhost:9090~', $this->anything()],
+ ['thrift_consecfails:localhost:9090~', $this->anything()],
+ ],
+ 'apcuStoreCallParams' => [
+ ['thrift_consecfails:localhost:9090~', 1],
+ ],
+ 'timeResult' => [],
+ 'fsockopenResult' => [
+ false,
+ ],
+ 'expectedException' => TException::class,
+ 'expectedExceptionMessage' => 'TSocketPool: All hosts in pool are down. (localhost:9090)',
+ ]
+ );
+ yield 'apcu disabled' => array_merge(
+ $default,
+ [
+ 'functionExistCallParams' => [
+ ['apcu_fetch'],
+ ],
+ 'functionExistResult' => [
+ false,
+ ],
+ 'fsockopenResult' => [
+ false,
+ ],
+ 'timeResult' => [
+ 1,
+ ],
+ 'apcuFetchCallParams' => [],
+ 'apcuStoreCallParams' => [],
+ 'expectedException' => TException::class,
+ 'expectedExceptionMessage' => 'TSocketPool: All hosts in pool are down. (localhost:9090)',
+ ]
+ );
+ yield 'second host accessible' => array_merge(
+ $default,
+ [
+ 'hosts' => ['host1', 'host2'],
+ 'ports' => [9090, 9091],
+ 'servers' => [
+ ['host' => 'host1', 'port' => 9090],
+ ['host' => 'host2', 'port' => 9091],
+ ],
+ 'fsockopenCallParams' => [
+ [
+ 'host2',
+ 9091,
+ $this->anything(), #$errno,
+ $this->anything(), #$errstr,
+ $this->anything(), #$this->sendTimeoutSec_ + ($this->sendTimeoutUsec_ / 1000000),
+ ],
+ [
+ 'host1',
+ 9090,
+ $this->anything(), #$errno,
+ $this->anything(), #$errstr,
+ $this->anything(), #$this->sendTimeoutSec_ + ($this->sendTimeoutUsec_ / 1000000),
+ ],
+ ],
+ 'fsockopenResult' => [
+ false,
+ ['php://temp', 'r'],
+ ],
+ 'apcuFetchCallParams' => [
+ ['thrift_failtime:host2:9091~', $this->anything()],
+ ['thrift_consecfails:host2:9091~', $this->anything()],
+ ['thrift_failtime:host1:9090~', $this->anything()],
+ ],
+ 'apcuStoreCallParams' => [
+ ['thrift_failtime:host2:9091~', $this->anything()],
+ ['thrift_consecfails:host2:9091~', $this->anything(), 0],
+ ],
+ 'timeResult' => [
+ 1,
+ ],
+ ]
+ );
+ }
+}
diff --git a/lib/php/test/Unit/Lib/Transport/TSocketTest.php b/lib/php/test/Unit/Lib/Transport/TSocketTest.php
new file mode 100644
index 0000000..6bab297
--- /dev/null
+++ b/lib/php/test/Unit/Lib/Transport/TSocketTest.php
@@ -0,0 +1,669 @@
+<?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\Thrift\Unit\Lib\Transport;
+
+use phpmock\phpunit\PHPMock;
+use PHPUnit\Framework\TestCase;
+use Thrift\Exception\TException;
+use Thrift\Exception\TTransportException;
+use Thrift\Transport\TSocket;
+
+class TSocketTest extends TestCase
+{
+ use PHPMock;
+
+ /**
+ * @dataProvider openExceptionDataProvider
+ */
+ public function testOpenException(
+ $host,
+ $port,
+ $persist,
+ $debugHandler,
+ $fsockopenCallCount,
+ $expectedException,
+ $expectedMessage,
+ $expectedCode
+ ) {
+ $this->expectException($expectedException);
+ $this->expectExceptionMessage($expectedMessage);
+ $this->expectExceptionCode($expectedCode);
+
+ $this->getFunctionMock('Thrift\Transport', 'fsockopen')
+ ->expects($this->exactly($fsockopenCallCount))
+ ->with(
+ $host,
+ $port,
+ $this->anything(), #$errno,
+ $this->anything(), #$errstr,
+ $this->anything() #$this->sendTimeoutSec_ + ($this->sendTimeoutUsec_ / 1000000),
+ )
+ ->willReturn(false);
+
+ $socket = new TSocket(
+ $host,
+ $port,
+ $persist,
+ $debugHandler
+ );
+ $socket->open();
+ }
+
+ public function openExceptionDataProvider()
+ {
+ yield 'host is empty' => [
+ 'host' => '',
+ 'port' => 9090,
+ 'persist' => null,
+ 'debugHandler' => false,
+ 'fsockopenCallCount' => 0,
+ 'expectedException' => TTransportException::class,
+ 'expectedMessage' => 'Cannot open null host',
+ 'expectedCode' => TTransportException::NOT_OPEN,
+ ];
+ yield 'port is not positive' => [
+ 'host' => 'localhost',
+ 'port' => 0,
+ 'persist' => false,
+ 'debugHandler' => null,
+ 'fsockopenCallCount' => 0,
+ 'expectedException' => TTransportException::class,
+ 'expectedMessage' => 'Cannot open without port',
+ 'expectedCode' => TTransportException::NOT_OPEN,
+ ];
+ yield 'connection failure' => [
+ 'host' => 'nonexistent-host',
+ 'port' => 9090,
+ 'persist' => false,
+ 'debugHandler' => null,
+ 'fsockopenCallCount' => 1,
+ 'expectedException' => TException::class,
+ 'expectedMessage' => 'TSocket: Could not connect to',
+ 'expectedCode' => TTransportException::UNKNOWN,
+ ];
+ }
+
+ public function testDoubleConnect(): void
+ {
+ $host = 'localhost';
+ $port = 9090;
+ $persist = false;
+ $debugHandler = null;
+ $handle = fopen('php://memory', 'r+');
+ $this->getFunctionMock('Thrift\Transport', 'fsockopen')
+ ->expects($this->once())
+ ->with(
+ $host,
+ $port,
+ $this->anything(), #$errno,
+ $this->anything(), #$errstr,
+ $this->anything() #$this->sendTimeoutSec_ + ($this->sendTimeoutUsec_ / 1000000),
+ )
+ ->willReturn($handle);
+
+ $this->getFunctionMock('Thrift\Transport', 'socket_import_stream')
+ ->expects($this->once())
+ ->with($handle)
+ ->willReturn(true);
+
+ $this->getFunctionMock('Thrift\Transport', 'socket_set_option')
+ ->expects($this->once())
+ ->with(
+ $this->anything(), #$socket,
+ SOL_TCP, #$level
+ TCP_NODELAY, #$option
+ 1 #$value
+ )
+ ->willReturn(true);
+
+ $transport = new TSocket(
+ $host,
+ $port,
+ $persist,
+ $debugHandler
+ );
+
+ $transport->open();
+ $this->expectException(TTransportException::class);
+ $this->expectExceptionMessage('Socket already connected');
+ $this->expectExceptionCode(TTransportException::ALREADY_OPEN);
+ $transport->open();
+ }
+
+ public function testDebugHandler()
+ {
+ $host = 'nonexistent-host';
+ $port = 9090;
+ $false = false;
+
+ $debugHandler = function ($error) {
+ $this->assertEquals(
+ 'TSocket: Could not connect to nonexistent-host:9090 (Connection refused [999])',
+ $error
+ );
+ };
+
+ $this->getFunctionMock('Thrift\Transport', 'fsockopen')
+ ->expects($this->once())
+ ->with(
+ $host,
+ $port,
+ $this->anything(), #$errno,
+ $this->anything(), #$errstr,
+ $this->anything() #$this->sendTimeoutSec_ + ($this->sendTimeoutUsec_ / 1000000),
+ )
+ ->willReturnCallback(
+ function (
+ string $hostname,
+ int $port,
+ &$error_code,
+ &$error_message,
+ ?float $timeout
+ ) {
+ $error_code = 999;
+ $error_message = 'Connection refused';
+
+ return false;
+ }
+ );
+
+ $transport = new TSocket(
+ $host,
+ $port,
+ $false,
+ $debugHandler
+ );
+ $transport->setDebug(true);
+
+ $this->expectException(\Exception::class);
+ $this->expectExceptionMessage('TSocket: Could not connect to');
+ $this->expectExceptionCode(0);
+ $transport->open();
+ }
+
+ public function testOpenPersist()
+ {
+ $host = 'persist-localhost';
+ $port = 9090;
+ $persist = true;
+ $debugHandler = null;
+
+ $handle = fopen('php://memory', 'r+');
+
+ $this->getFunctionMock('Thrift\Transport', 'pfsockopen')
+ ->expects($this->once())
+ ->with(
+ $host,
+ $port,
+ $this->anything(), #$errno,
+ $this->anything(), #$errstr,
+ $this->anything() #$this->sendTimeoutSec_ + ($this->sendTimeoutUsec_ / 1000000),
+ )
+ ->willReturn($handle);
+
+ $this->getFunctionMock('Thrift\Transport', 'socket_import_stream')
+ ->expects($this->once())
+ ->with($handle)
+ ->willReturn(true);
+
+ $this->getFunctionMock('Thrift\Transport', 'socket_set_option')
+ ->expects($this->once())
+ ->with(
+ $this->anything(), #$socket,
+ SOL_TCP, #$level
+ TCP_NODELAY, #$option
+ 1 #$value
+ )
+ ->willReturn(true);
+
+ $transport = new TSocket(
+ $host,
+ $port,
+ $persist,
+ $debugHandler
+ );
+
+ $transport->open();
+ $this->assertTrue($transport->isOpen());
+ }
+
+ /**
+ * @dataProvider open_THRIFT_5132_DataProvider
+ */
+ public function testOpen_THRIFT_5132(
+ $socketImportResult
+ ) {
+ $host = 'localhost';
+ $port = 9090;
+ $persist = false;
+ $debugHandler = null;
+
+ $this->getFunctionMock('Thrift\Transport', 'fsockopen')
+ ->expects($this->once())
+ ->with(
+ $host,
+ $port,
+ $this->anything(), #$errno,
+ $this->anything(), #$errstr,
+ $this->anything() #$this->sendTimeoutSec_ + ($this->sendTimeoutUsec_ / 1000000),
+ )
+ ->willReturn(fopen('php://input', 'r+'));
+
+ $this->getFunctionMock('Thrift\Transport', 'socket_import_stream')
+ ->expects($this->once())
+ ->willReturn($socketImportResult);
+
+ $this->getFunctionMock('Thrift\Transport', 'socket_set_option')
+ ->expects($socketImportResult ? $this->once() : $this->never())
+ ->with(
+ $this->anything(), #$socket,
+ SOL_TCP, #$level
+ TCP_NODELAY, #$option
+ 1 #$value
+ )
+ ->willReturn(true);
+
+ $transport = new TSocket(
+ $host,
+ $port,
+ $persist,
+ $debugHandler
+ );
+
+ $transport->open();
+ $this->assertTrue($transport->isOpen());
+ }
+
+ public function open_THRIFT_5132_DataProvider()
+ {
+ yield 'socket_import_stream success' => [
+ 'socketImportResult' => true,
+ ];
+ yield 'socket_import_stream fail' => [
+ 'socketImportResult' => false,
+ ];
+ }
+
+ public function testSetHandle()
+ {
+ $host = 'localhost';
+ $port = 9090;
+ $persist = false;
+ $debugHandler = null;
+ $transport = new TSocket(
+ $host,
+ $port,
+ $persist,
+ $debugHandler
+ );
+
+ $this->assertFalse($transport->isOpen());
+ $transport->setHandle(fopen('php://memory', 'r+'));
+ $this->assertTrue($transport->isOpen());
+ }
+
+ public function testSetSendTimeout()
+ {
+ $host = 'localhost';
+ $port = 9090;
+ $persist = false;
+ $debugHandler = null;
+ $transport = new TSocket(
+ $host,
+ $port,
+ $persist,
+ $debugHandler
+ );
+
+ $transport->setSendTimeout(9999);
+ $reflector = new \ReflectionClass($transport);
+ $property = $reflector->getProperty('sendTimeoutSec_');
+ $property->setAccessible(true);
+ $this->assertEquals(9.0, $property->getValue($transport));
+ $property = $reflector->getProperty('sendTimeoutUsec_');
+ $property->setAccessible(true);
+ $this->assertEquals(999000, $property->getValue($transport));
+ }
+
+ public function testSetRecvTimeout()
+ {
+ $host = 'localhost';
+ $port = 9090;
+ $persist = false;
+ $debugHandler = null;
+ $transport = new TSocket(
+ $host,
+ $port,
+ $persist,
+ $debugHandler
+ );
+
+ $transport->setRecvTimeout(9999);
+ $reflector = new \ReflectionClass($transport);
+ $property = $reflector->getProperty('recvTimeoutSec_');
+ $property->setAccessible(true);
+ $this->assertEquals(9.0, $property->getValue($transport));
+ $property = $reflector->getProperty('recvTimeoutUsec_');
+ $property->setAccessible(true);
+ $this->assertEquals(999000, $property->getValue($transport));
+ }
+
+ /**
+ * @dataProvider hostDataProvider
+ */
+ public function testGetHost($host, $expected)
+ {
+ $port = 9090;
+ $persist = false;
+ $debugHandler = null;
+ $transport = new TSocket(
+ $host,
+ $port,
+ $persist,
+ $debugHandler
+ );
+ $this->assertEquals($expected, $transport->getHost());
+ }
+
+ public function hostDataProvider()
+ {
+ yield 'localhost' => ['localhost', 'localhost'];
+ yield 'ssl_localhost' => ['ssl://localhost', 'ssl://localhost'];
+ yield 'http_localhost' => ['http://localhost', 'http://localhost'];
+ }
+
+ public function testGetPort()
+ {
+ $host = 'localhost';
+ $port = 9090;
+ $persist = false;
+ $debugHandler = null;
+ $transport = new TSocket(
+ $host,
+ $port,
+ $persist,
+ $debugHandler
+ );
+ $this->assertEquals($port, $transport->getPort());
+ }
+
+ public function testClose()
+ {
+ $host = 'localhost';
+ $port = 9090;
+ $persist = false;
+ $debugHandler = null;
+ $transport = new TSocket(
+ $host,
+ $port,
+ $persist,
+ $debugHandler
+ );
+ $transport->setHandle(fopen('php://memory', 'r+'));
+ $reflector = new \ReflectionClass($transport);
+ $property = $reflector->getProperty('handle_');
+ $property->setAccessible(true);
+ $this->assertNotNull($property->getValue($transport));
+
+ $transport->close();
+ $reflector = new \ReflectionClass($transport);
+ $property = $reflector->getProperty('handle_');
+ $property->setAccessible(true);
+ $this->assertNull($property->getValue($transport));
+ }
+
+ /**
+ * @dataProvider writeFailDataProvider
+ */
+ public function testWriteFail(
+ $streamSelectResult,
+ $fwriteCallCount,
+ $expectedException,
+ $expectedMessage,
+ $expectedCode
+ ) {
+ $host = 'localhost';
+ $port = 9090;
+ $persist = false;
+ $debugHandler = null;
+ $handle = fopen('php://memory', 'r+');
+
+ $this->getFunctionMock('Thrift\Transport', 'stream_select')
+ ->expects($this->once())
+ ->with(
+ $this->anything(), #$null,
+ [$handle],
+ $this->anything(), #$null,
+ $this->anything(), #$this->sendTimeoutSec_,
+ $this->anything() #$this->sendTimeoutUsec_
+ )
+ ->willReturn($streamSelectResult);
+
+ $this->getFunctionMock('Thrift\Transport', 'fwrite')
+ ->expects($this->exactly($fwriteCallCount))
+ ->with(
+ $handle,
+ 'test1234456789132456798'
+ )
+ ->willReturn(false);
+
+ $this->expectException($expectedException);
+ $this->expectExceptionMessage($expectedMessage);
+ $this->expectExceptionCode($expectedCode);
+
+ $transport = new TSocket(
+ $host,
+ $port,
+ $persist,
+ $debugHandler
+ );
+ $transport->setHandle($handle);
+
+ $transport->write('test1234456789132456798');
+ }
+
+ public function testWrite()
+ {
+ $host = 'localhost';
+ $port = 9090;
+ $persist = false;
+ $debugHandler = null;
+ $transport = new TSocket(
+ $host,
+ $port,
+ $persist,
+ $debugHandler
+ );
+ $fileName = sys_get_temp_dir() . '/' . md5(mt_rand(0, time()) . time());
+ touch($fileName);
+ $handle = fopen($fileName, 'r+');
+ $transport->setHandle($handle);
+ $transport->write('test1234456789132456798');
+ $this->assertEquals('test1234456789132456798', file_get_contents($fileName));
+
+ register_shutdown_function(function () use ($fileName) {
+ is_file($fileName) && unlink($fileName);
+ });
+ }
+
+ public function writeFailDataProvider()
+ {
+ yield 'stream_select timeout' => [
+ 'streamSelectResult' => 0,
+ 'fwriteCallCount' => 0,
+ 'expectedException' => TTransportException::class,
+ 'expectedMessage' => 'TSocket: timed out writing 23 bytes from localhost:9090',
+ 'expectedCode' => 0,
+ ];
+ yield 'stream_select fail write' => [
+ 'streamSelectResult' => 1,
+ 'fwriteCallCount' => 1,
+ 'expectedException' => TTransportException::class,
+ 'expectedMessage' => 'TSocket: Could not write 23 bytes localhost:9090',
+ 'expectedCode' => 0,
+ ];
+ yield 'stream_select fail' => [
+ 'streamSelectResult' => false,
+ 'fwriteCallCount' => 0,
+ 'expectedException' => TTransportException::class,
+ 'expectedMessage' => 'TSocket: Could not write 23 bytes localhost:9090',
+ 'expectedCode' => 0,
+ ];
+ }
+
+ public function testRead()
+ {
+ $host = 'localhost';
+ $port = 9090;
+ $persist = false;
+ $debugHandler = null;
+ $transport = new TSocket(
+ $host,
+ $port,
+ $persist,
+ $debugHandler
+ );
+ $fileName = sys_get_temp_dir() . '/' . md5(mt_rand(0, time()) . time());
+ file_put_contents($fileName, '12345678901234567890');
+ $handle = fopen($fileName, 'r+');
+ $transport->setHandle($handle);
+ $this->assertEquals('12345', $transport->read(5));
+
+ register_shutdown_function(function () use ($fileName) {
+ is_file($fileName) && unlink($fileName);
+ });
+ }
+
+ /**
+ * @dataProvider readFailDataProvider
+ */
+ public function testReadFail(
+ $streamSelectResult,
+ $freadResult,
+ $feofResult,
+ $expectedException,
+ $expectedMessage,
+ $expectedCode
+ ) {
+ $host = 'localhost';
+ $port = 9090;
+ $persist = false;
+ $debugHandler = null;
+ $handle = fopen('php://memory', 'r+');
+
+ $this->getFunctionMock('Thrift\Transport', 'stream_select')
+ ->expects($this->once())
+ ->with(
+ [$handle],
+ $this->anything(), #$null,
+ $this->anything(), #$null,
+ $this->anything(), #$this->recvTimeoutSec_,
+ $this->anything() #$this->recvTimeoutUsec_
+ )
+ ->willReturn($streamSelectResult);
+
+ $this->getFunctionMock('Thrift\Transport', 'fread')
+ ->expects($this->exactly($streamSelectResult ? 1 : 0))
+ ->with(
+ $handle,
+ 5
+ )
+ ->willReturn($freadResult);
+ $this->getFunctionMock('Thrift\Transport', 'feof')
+ ->expects($this->exactly($feofResult ? 1 : 0))
+ ->with($handle)
+ ->willReturn($feofResult);
+
+ $this->expectException($expectedException);
+ $this->expectExceptionMessage($expectedMessage);
+ $this->expectExceptionCode($expectedCode);
+
+ $transport = new TSocket(
+ $host,
+ $port,
+ $persist,
+ $debugHandler
+ );
+ $transport->setHandle($handle);
+
+ $transport->read(5);
+ }
+
+ public function readFailDataProvider()
+ {
+ yield 'stream_select timeout' => [
+ 'streamSelectResult' => 0,
+ 'freadResult' => '',
+ 'feofResult' => false,
+ 'expectedException' => TTransportException::class,
+ 'expectedMessage' => 'TSocket: timed out reading 5 bytes from localhost:9090',
+ 'expectedCode' => 0,
+ ];
+ yield 'stream_select fail read' => [
+ 'streamSelectResult' => 1,
+ 'freadResult' => '',
+ 'feofResult' => true,
+ 'expectedException' => TTransportException::class,
+ 'expectedMessage' => 'TSocket read 0 bytes',
+ 'expectedCode' => 0,
+ ];
+ yield 'stream_select fail' => [
+ 'streamSelectResult' => false,
+ 'freadResult' => '',
+ 'feofResult' => false,
+ 'expectedException' => TTransportException::class,
+ 'expectedMessage' => 'TSocket: Could not read 5 bytes from localhost:9090',
+ 'expectedCode' => 0,
+ ];
+ yield 'fread false' => [
+ 'streamSelectResult' => 1,
+ 'freadResult' => false,
+ 'feofResult' => false,
+ 'expectedException' => TTransportException::class,
+ 'expectedMessage' => 'TSocket: Could not read 5 bytes from localhost:9090',
+ 'expectedCode' => 0,
+ ];
+ yield 'fread empty' => [
+ 'streamSelectResult' => 1,
+ 'freadResult' => '',
+ 'feofResult' => true,
+ 'expectedException' => TTransportException::class,
+ 'expectedMessage' => 'TSocket read 0 bytes',
+ 'expectedCode' => 0,
+ ];
+ }
+
+ public function testFlush()
+ {
+ $host = 'localhost';
+ $port = 9090;
+ $persist = false;
+ $debugHandler = null;
+ $transport = new TSocket(
+ $host,
+ $port,
+ $persist,
+ $debugHandler
+ );
+ $this->assertNUll($transport->flush());
+ }
+}