diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml
index 8ceb27f..c2b5161 100644
--- a/.github/workflows/analysis.yml
+++ b/.github/workflows/analysis.yml
@@ -30,7 +30,9 @@ jobs:
run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Run Static Analysis
- run: composer run-script analyse
+ run: |
+ composer run-script analyse
+ composer run-script analyse-tests
- name: Run Test Coverage
run: composer run-script test-coverage
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 3ac300a..51a5db8 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -6,7 +6,7 @@ jobs:
strategy:
matrix:
operating-system: [ubuntu-latest, windows-latest]
- php-versions: ['8.1', '8.2', '8.3', '8.4', '8.5']
+ php-versions: ['8.2', '8.3', '8.4', '8.5']
name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }}
steps:
- name: Checkout
@@ -16,7 +16,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
- coverage: xdebug
+ coverage: none
- name: Get Composer Cache Directory
id: composer-cache
@@ -33,5 +33,5 @@ jobs:
- name: Install Composer dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader
- - name: Run Specs
- run: composer run-script test
+ - name: Run Tests
+ run: composer run-script test-unit
diff --git a/.gitignore b/.gitignore
index 16cbe25..b798b30 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,5 @@ composer.lock
composer.phar
.idea/
vendor/
-bin/
\ No newline at end of file
+bin/
+.phpunit.result.cache
diff --git a/composer.json b/composer.json
index 8a00e5f..a0d7e20 100644
--- a/composer.json
+++ b/composer.json
@@ -15,13 +15,14 @@
}
],
"require": {
- "php": ">=7.1"
+ "php": ">=8.2"
},
"require-dev": {
- "phpspec/phpspec": "^7.1|^8.0",
"freedsx/asn1": ">=0.4, <1.0",
- "friends-of-phpspec/phpspec-code-coverage": "^6.1|^7.0",
- "phpstan/phpstan": "^2.0"
+ "phpunit/phpunit": "^11.5",
+ "phpstan/phpstan": "^2.0",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpstan/extension-installer": "^1.4"
},
"suggest": {
"ext-openssl": "For SSL/TLS support.",
@@ -32,19 +33,26 @@
},
"autoload-dev": {
"psr-4": {
- "fixture\\FreeDSx\\Socket\\": "tests/fixture/FreeDSx/Socket",
- "spec\\FreeDSx\\Socket\\": "tests/spec/FreeDSx/Socket"
+ "Tests\\Unit\\FreeDSx\\Socket\\": "tests/unit"
+ }
+ },
+ "config": {
+ "allow-plugins": {
+ "phpstan/extension-installer": true
}
},
"scripts": {
- "test-coverage": [
- "phpspec run --no-interaction -c phpspec.cov.yml"
+ "test-unit": [
+ "@php -d xdebug.mode=off vendor/bin/phpunit --testsuite unit"
],
- "test": [
- "phpspec run --no-interaction"
+ "test-coverage": [
+ "@php -d xdebug.mode=coverage vendor/bin/phpunit --testsuite unit --coverage-clover=coverage.xml"
],
"analyse": [
"phpstan analyse"
+ ],
+ "analyse-tests": [
+ "phpstan analyse -c phpstan.tests.neon"
]
}
}
diff --git a/phpspec.cov.yml b/phpspec.cov.yml
deleted file mode 100644
index 5271b7f..0000000
--- a/phpspec.cov.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-suites:
- default:
- spec_path: tests/
- src_path: src/
-formatter.name: progress
-extensions:
- FriendsOfPhpSpec\PhpSpec\CodeCoverage\CodeCoverageExtension:
- format:
- - clover
- output:
- clover: coverage.xml
- whitelist:
- - src
diff --git a/phpspec.yml b/phpspec.yml
deleted file mode 100644
index 0f1eed9..0000000
--- a/phpspec.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-suites:
- default:
- spec_path: tests/
- src_path: src/
diff --git a/phpstan.neon b/phpstan.neon
index b8fa543..876ed33 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -2,5 +2,5 @@ parameters:
level: 6
paths:
- %currentWorkingDirectory%/src
- phpVersion: 70100
- treatPhpDocTypesAsCertain: false
\ No newline at end of file
+ phpVersion: 80200
+ treatPhpDocTypesAsCertain: false
diff --git a/phpstan.tests.neon b/phpstan.tests.neon
new file mode 100644
index 0000000..0987142
--- /dev/null
+++ b/phpstan.tests.neon
@@ -0,0 +1,5 @@
+parameters:
+ treatPhpDocTypesAsCertain: false
+ level: 10
+ paths:
+ - %currentWorkingDirectory%/tests
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 0000000..63ba7d6
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,30 @@
+
+
+
+
+ ./src
+
+
+
+
+
+
+ ./tests/unit
+
+
+
diff --git a/src/FreeDSx/Socket/MessageQueue.php b/src/FreeDSx/Socket/MessageQueue.php
deleted file mode 100644
index a886233..0000000
--- a/src/FreeDSx/Socket/MessageQueue.php
+++ /dev/null
@@ -1,21 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace FreeDSx\Socket;
-
-use FreeDSx\Socket\Queue\Asn1MessageQueue;
-
-/**
- * @deprecated Used for backwards-compatibility. Will be removed.
- * @author Chad Sikorra
- */
-class MessageQueue extends Asn1MessageQueue
-{
-}
diff --git a/tests/fixture/FreeDSx/Socket/Pdu.php b/tests/fixture/FreeDSx/Socket/Pdu.php
deleted file mode 100644
index 29642db..0000000
--- a/tests/fixture/FreeDSx/Socket/Pdu.php
+++ /dev/null
@@ -1,50 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace fixture\FreeDSx\Socket;
-
-use FreeDSx\Asn1\Type\AbstractType;
-
-/**
- * Throw away PDU class for message queue specs.
- *
- * @author Chad Sikorra
- */
-class Pdu implements \FreeDSx\Socket\PduInterface
-{
- /**
- * @var AbstractType
- */
- protected $type;
-
- /**
- * @param AbstractType $type
- */
- public function __construct(AbstractType $type)
- {
- $this->type = $type;
- }
-
- /**
- * {@inheritdoc}
- */
- public function toAsn1(): AbstractType
- {
- return $this->type;
- }
-
- /**
- * {@inheritdoc}
- */
- public static function fromAsn1(AbstractType $asn1)
- {
- return new self($asn1);
- }
-}
diff --git a/tests/spec/FreeDSx/Socket/Exception/ConnectionExceptionSpec.php b/tests/spec/FreeDSx/Socket/Exception/ConnectionExceptionSpec.php
deleted file mode 100644
index 1a41c99..0000000
--- a/tests/spec/FreeDSx/Socket/Exception/ConnectionExceptionSpec.php
+++ /dev/null
@@ -1,27 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace spec\FreeDSx\Socket\Exception;
-
-use FreeDSx\Socket\Exception\ConnectionException;
-use PhpSpec\ObjectBehavior;
-
-class ConnectionExceptionSpec extends ObjectBehavior
-{
- function it_is_initializable()
- {
- $this->shouldHaveType(ConnectionException::class);
- }
-
- function it_should_extend_exception()
- {
- $this->shouldBeAnInstanceOf(\Exception::class);
- }
-}
diff --git a/tests/spec/FreeDSx/Socket/Exception/PartialMessageExceptionSpec.php b/tests/spec/FreeDSx/Socket/Exception/PartialMessageExceptionSpec.php
deleted file mode 100644
index eee5e1a..0000000
--- a/tests/spec/FreeDSx/Socket/Exception/PartialMessageExceptionSpec.php
+++ /dev/null
@@ -1,27 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace spec\FreeDSx\Socket\Exception;
-
-use FreeDSx\Socket\Exception\PartialMessageException;
-use PhpSpec\ObjectBehavior;
-
-class PartialMessageExceptionSpec extends ObjectBehavior
-{
- function it_is_initializable()
- {
- $this->shouldHaveType(PartialMessageException::class);
- }
-
- function it_should_extend_exception()
- {
- $this->shouldBeAnInstanceOf(\Exception::class);
- }
-}
diff --git a/tests/spec/FreeDSx/Socket/MessageQueueSpec.php b/tests/spec/FreeDSx/Socket/MessageQueueSpec.php
deleted file mode 100644
index fa651f6..0000000
--- a/tests/spec/FreeDSx/Socket/MessageQueueSpec.php
+++ /dev/null
@@ -1,62 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace spec\FreeDSx\Socket;
-
-use fixture\FreeDSx\Socket\Pdu;
-use FreeDSx\Asn1\Encoder\EncoderInterface;
-use FreeDSx\Asn1\Exception\PartialPduException;
-use FreeDSx\Asn1\Type\IntegerType;
-use FreeDSx\Socket\Exception\ConnectionException;
-use FreeDSx\Socket\MessageQueue;
-use FreeDSx\Socket\Socket;
-use PhpSpec\ObjectBehavior;
-
-class MessageQueueSpec extends ObjectBehavior
-{
- function let(Socket $socket, EncoderInterface $encoder)
- {
- $this->beConstructedWith($socket, $encoder, Pdu::class);
- }
-
- function it_is_initializable()
- {
- $this->shouldHaveType(MessageQueue::class);
- }
-
- function it_should_return_a_single_message_on_tcp_read(Socket $socket, EncoderInterface $encoder)
- {
- $socket->read()->willReturn('foo');
- $socket->read(false)->willReturn(false);
- $encoder->decode('foo')->shouldBeCalled()->willReturn(new IntegerType(100));
- $encoder->getLastPosition()->willReturn(2);
-
- $this->getMessage()->shouldBeLike(new Pdu(new IntegerType(100)));
- }
-
- function it_should_continue_on_during_partial_PDUs(Socket $socket, EncoderInterface $encoder)
- {
- $socket->read()->willReturn('foo', 'bar');
- $socket->read(false)->willReturn(false);
-
- $encoder->decode('foo')->shouldBeCalled()->willThrow(PartialPduException::class);
- $encoder->decode('foobar')->shouldBeCalled()->willReturn(new IntegerType(100));
- $encoder->getLastPosition()->willReturn(6);
-
- $this->getMessage()->shouldBeLike(new Pdu(new IntegerType(100)));
- }
-
- function it_should_throw_an_exception_on_get_message_when_there_is_none(Socket $socket)
- {
- $socket->read()->willReturn(false);
-
- $this->shouldThrow(ConnectionException::class)->duringGetMessage();
- }
-}
diff --git a/tests/spec/FreeDSx/Socket/Queue/Asn1MessageQueueSpec.php b/tests/spec/FreeDSx/Socket/Queue/Asn1MessageQueueSpec.php
deleted file mode 100644
index 5bfdb7b..0000000
--- a/tests/spec/FreeDSx/Socket/Queue/Asn1MessageQueueSpec.php
+++ /dev/null
@@ -1,102 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace spec\FreeDSx\Socket\Queue;
-
-use fixture\FreeDSx\Socket\Pdu;
-use FreeDSx\Asn1\Encoder\EncoderInterface;
-use FreeDSx\Asn1\Exception\PartialPduException;
-use FreeDSx\Asn1\Type\IntegerType;
-use FreeDSx\Socket\Exception\ConnectionException;
-use FreeDSx\Socket\Queue\Asn1MessageQueue;
-use FreeDSx\Socket\Socket;
-use PhpSpec\ObjectBehavior;
-
-class Asn1MessageQueueSpec extends ObjectBehavior
-{
- function it_is_initializable()
- {
- $this->shouldHaveType(Asn1MessageQueue::class);
- }
-
- function let(Socket $socket, EncoderInterface $encoder)
- {
- $this->beConstructedWith($socket, $encoder, Pdu::class);
- }
-
- function it_should_return_a_single_message_on_tcp_read($socket, $encoder)
- {
- $socket->read()->willReturn('foo');
- $socket->read(false)->willReturn(false);
- $encoder->decode('foo')->shouldBeCalled()->willReturn(new IntegerType(100));
- $encoder->getLastPosition()->willReturn(3);
-
- $this->getMessage()->shouldBeLike(new Pdu(new IntegerType(100)));
- }
-
- function it_should_continue_on_during_partial_PDUs($socket, $encoder)
- {
- $socket->read()->willReturn('foo', 'bar');
-
- $encoder->decode('foo')->shouldBeCalled()->willThrow(PartialPduException::class);
- $encoder->decode('foobar')->shouldBeCalled()->willReturn(new IntegerType(100));
- $encoder->getLastPosition()->willReturn(3);
-
- $this->getMessage()->shouldBeLike(new Pdu(new IntegerType(100)));
- }
-
- function it_should_throw_an_exception_on_get_message_when_there_is_none($socket)
- {
- $socket->read()->willReturn(false);
-
- $this->shouldThrow(ConnectionException::class)->duringGetMessage();
- }
-
- function it_should_not_peek_the_socket_after_decoding_a_complete_message($socket, $encoder)
- {
- $socket->read()->willReturn('foo');
- $socket->read(false)->shouldNotBeCalled();
- $encoder->decode('foo')->shouldBeCalled()->willReturn(new IntegerType(100));
- $encoder->getLastPosition()->willReturn(3);
-
- $this->getMessage()->shouldBeLike(new Pdu(new IntegerType(100)));
- }
-
- function it_should_yield_messages_continuously_from_the_generator($socket, $encoder)
- {
- $socket->read()->willReturn('foobar', 'baz');
- $encoder->decode('foobar')->willReturn(new IntegerType(1));
- $encoder->decode('bar')->willReturn(new IntegerType(2));
- $encoder->decode('baz')->willReturn(new IntegerType(3));
- $encoder->getLastPosition()->willReturn(3);
-
- $iter = $this->getMessages()->getWrappedObject();
-
- if (!$iter instanceof \Generator) {
- throw new \RuntimeException('getMessages() must return a Generator.');
- }
-
- $first = $iter->current();
- $iter->next();
- $second = $iter->current();
- $iter->next();
- $third = $iter->current();
-
- if ($first != new Pdu(new IntegerType(1))) {
- throw new \RuntimeException('First yielded message did not match.');
- }
- if ($second != new Pdu(new IntegerType(2))) {
- throw new \RuntimeException('Second yielded message did not match.');
- }
- if ($third != new Pdu(new IntegerType(3))) {
- throw new \RuntimeException('Third yielded message did not match.');
- }
- }
-}
diff --git a/tests/spec/FreeDSx/Socket/Queue/BufferSpec.php b/tests/spec/FreeDSx/Socket/Queue/BufferSpec.php
deleted file mode 100644
index 93f6ae1..0000000
--- a/tests/spec/FreeDSx/Socket/Queue/BufferSpec.php
+++ /dev/null
@@ -1,37 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace spec\FreeDSx\Socket\Queue;
-
-use FreeDSx\Socket\Queue\Buffer;
-use PhpSpec\ObjectBehavior;
-
-class BufferSpec extends ObjectBehavior
-{
- function let()
- {
- $this->beConstructedWith('foo', 4);
- }
-
- function it_is_initializable()
- {
- $this->shouldHaveType(Buffer::class);
- }
-
- function it_should_get_the_bytes()
- {
- $this->bytes()->shouldBeEqualTo('foo');
- }
-
- function it_should_get_where_the_buffer_ends()
- {
- $this->endsAt()->shouldBeEqualTo(4);
- }
-}
diff --git a/tests/spec/FreeDSx/Socket/Queue/MessageSpec.php b/tests/spec/FreeDSx/Socket/Queue/MessageSpec.php
deleted file mode 100644
index 058a6ce..0000000
--- a/tests/spec/FreeDSx/Socket/Queue/MessageSpec.php
+++ /dev/null
@@ -1,46 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace spec\FreeDSx\Socket\Queue;
-
-use fixture\FreeDSx\Socket\Pdu;
-use FreeDSx\Asn1\Type\IntegerType;
-use FreeDSx\Socket\Queue\Message;
-use PhpSpec\ObjectBehavior;
-
-class MessageSpec extends ObjectBehavior
-{
- function let()
- {
- $this->beConstructedWith(new Pdu(new IntegerType(1)));
- }
-
- function it_is_initializable()
- {
- $this->shouldHaveType(Message::class);
- }
-
- function it_should_get_the_message()
- {
- $this->getMessage()->shouldBeLike(new Pdu(new IntegerType(1)));
- }
-
- function it_should_have_no_last_position_data_by_default()
- {
- $this->getLastPosition()->shouldBeNull();
- }
-
- function it_should_get_the_last_position()
- {
- $this->beConstructedWith(new Pdu(new IntegerType(1)), 2);
-
- $this->getLastPosition()->shouldBeEqualTo(2);
- }
-}
diff --git a/tests/spec/FreeDSx/Socket/SocketServerSpec.php b/tests/spec/FreeDSx/Socket/SocketServerSpec.php
deleted file mode 100644
index ef034fc..0000000
--- a/tests/spec/FreeDSx/Socket/SocketServerSpec.php
+++ /dev/null
@@ -1,96 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace spec\FreeDSx\Socket;
-
-use FreeDSx\Socket\Exception\ConnectionException;
-use FreeDSx\Socket\Socket;
-use FreeDSx\Socket\SocketServer;
-use PhpSpec\ObjectBehavior;
-
-class SocketServerSpec extends ObjectBehavior
-{
- use RequiresUnixTransport;
-
- private $testSocket = '';
-
- function let()
- {
- $this->testSocket = sys_get_temp_dir() . '/phpspec.socket';
- $this->beConstructedThrough('bind', ['0.0.0.0', 33389]);
- }
-
- function letGo()
- {
- @$this->close();
- if ($this->testSocket && file_exists($this->testSocket)) {
- @unlink($this->testSocket);
- }
- }
-
- function it_is_initializable()
- {
- $this->shouldHaveType(SocketServer::class);
- }
-
- function it_should_throw_a_connection_exception_if_it_cannot_listen_on_the_ip_and_port()
- {
- $this->beConstructedWith([]);
-
- $this->shouldThrow(ConnectionException::class)->during('Listen',['1.2.3.4', 389]);
- }
-
- function it_should_return_null_if_there_is_no_client_on_accept()
- {
- $this->accept(0)->shouldBeNull();
- }
-
- function it_should_construct_a_tcp_based_socket_server()
- {
- $this->beConstructedThrough('bindTcp', ['0.0.0.0', 33389]);
-
- $this->getOptions()->shouldHaveKeyWithValue('transport', 'tcp');
- $this->isConnected()->shouldBeEqualTo(true);
- $this->close();
- $this->isConnected()->shouldBeEqualTo(false);
- }
-
- function it_should_construct_a_udp_based_socket_server()
- {
- $this->beConstructedThrough('bindUdp', ['0.0.0.0', 33389]);
-
- $this->getOptions()->shouldHaveKeyWithValue('transport', 'udp');
- }
-
- function it_should_construct_a_unix_based_socket_server()
- {
- $this->requireUnixTransport();
-
- $this->beConstructedThrough('bindUnix', [$this->testSocket]);
-
- $this->getOptions()->shouldHaveKeyWithValue('transport', 'unix');
- $this->isConnected()->shouldBeEqualTo(true);
- $this->close();
- $this->isConnected()->shouldBeEqualTo(false);
- }
-
- function it_should_receive_data()
- {
- $this->beConstructedThrough('bindUdp', ['0.0.0.0', 33389]);
- # This is here to force PhpSpec to construct the object. It needs to be constructed to write to it.
- # Otherwise, the test would hang...
- $this->getOptions();
-
- $socket = Socket::udp('127.0.0.1', ['port' => 33389]);
- $socket->write('foo');
-
- $this->receive()->shouldBeEqualTo('foo');
- }
-}
diff --git a/tests/spec/FreeDSx/Socket/SocketSpec.php b/tests/spec/FreeDSx/Socket/SocketSpec.php
deleted file mode 100644
index fad4a3d..0000000
--- a/tests/spec/FreeDSx/Socket/SocketSpec.php
+++ /dev/null
@@ -1,200 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace spec\FreeDSx\Socket;
-
-use FreeDSx\Socket\Socket;
-use PhpSpec\ObjectBehavior;
-
-class SocketSpec extends ObjectBehavior
-{
- use RequiresUnixTransport;
-
-
- /**
- * @var resource|null
- */
- private $local;
-
- /**
- * @var resource|null
- */
- private $remote;
-
- /**
- * @var resource|null
- */
- private $unixServer;
-
- /**
- * @var string|null
- */
- private $unixPath;
-
- function letGo(): void
- {
- if (is_resource($this->remote)) {
- fclose($this->remote);
- }
- if (is_resource($this->local)) {
- fclose($this->local);
- }
- if (is_resource($this->unixServer)) {
- fclose($this->unixServer);
- }
- if ($this->unixPath !== null && file_exists($this->unixPath)) {
- @unlink($this->unixPath);
- }
- }
-
- private function createSocketPair(): void
- {
- $domain = DIRECTORY_SEPARATOR === '\\'
- ? STREAM_PF_INET
- : STREAM_PF_UNIX;
-
- [$this->local, $this->remote] = stream_socket_pair(
- $domain,
- STREAM_SOCK_STREAM,
- STREAM_IPPROTO_IP
- );
- }
-
- private function createUnixServer(): string
- {
- $this->requireUnixTransport();
- $this->unixPath = sys_get_temp_dir() . '/freedsx_socket_' . uniqid('', true) . '.sock';
- $this->unixServer = stream_socket_server('unix://' . $this->unixPath);
-
- return $this->unixPath;
- }
-
- function it_is_initializable()
- {
- $this->shouldHaveType(Socket::class);
- }
-
- function it_should_get_the_options_for_the_socket()
- {
- $this->getOptions()->shouldBeEqualTo([
- 'transport' => 'tcp',
- 'port' => 389,
- 'use_ssl' => false,
- 'ssl_crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | STREAM_CRYPTO_METHOD_TLS_CLIENT,
- 'ssl_ciphers' => 'DEFAULT',
- 'ssl_validate_cert' => true,
- 'ssl_allow_self_signed' => null,
- 'ssl_ca_cert' => null,
- 'ssl_peer_name' => null,
- 'timeout_connect' => 3,
- 'timeout_read' => 15,
- 'buffer_size' => 8192,
- ]);
- }
-
- function it_should_create_a_socket()
- {
- $this::create('www.google.com', ['port' => 80])->shouldBeAnInstanceOf(Socket::class);
- }
-
- function it_should_create_a_unix_based_socket()
- {
- $path = $this->createUnixServer();
-
- $this::unix($path)->shouldBeAnInstanceOf(Socket::class);
- }
-
- function it_should_create_a_tcp_based_socket()
- {
- $this::tcp('www.google.com', ['port' => 80])->getOptions()->shouldHaveKeyWithValue('transport', 'tcp');
- }
-
- function it_should_create_a_udp_based_socket()
- {
- $this::udp('8.8.8.8', ['port' => 53])->getOptions()->shouldHaveKeyWithValue('transport', 'udp');
- }
-
- function it_should_have_a_default_buffer_size_of_65507_for_UDP()
- {
- $this::udp('8.8.8.8', ['port' => 53])->getOptions()->shouldHaveKeyWithValue('buffer_size', 65507);
- }
-
- function it_should_tell_whether_or_not_it_is_connected_for_tcp()
- {
- $this->beConstructedThrough('tcp', ['www.google.com', ['port' => 80]]);
-
- $this->isConnected()->shouldBeEqualTo(true);
- $this->close();
- $this->isConnected()->shouldBeEqualTo(false);
- }
-
- function it_should_tell_whether_or_not_it_is_connected_for_udp()
- {
- $this->beConstructedThrough('udp', ['www.google.com', ['port' => 53]]);
-
- $this->isConnected()->shouldBeEqualTo(true);
- $this->close();
- $this->isConnected()->shouldBeEqualTo(false);
- }
-
- function it_should_tell_whether_it_is_connected_for_unix()
- {
- $path = $this->createUnixServer();
- $this->beConstructedThrough('unix', [$path]);
-
- $this->isConnected()->shouldBeEqualTo(true);
- $this->close();
- $this->isConnected()->shouldBeEqualTo(false);
- }
-
- function it_should_return_at_most_buffer_size_bytes_per_read()
- {
- $this->createSocketPair();
- fwrite($this->remote, '0123456789');
-
- $this->beConstructedWith($this->local, ['buffer_size' => 4]);
-
- $this->read()->shouldBe('0123');
- $this->read()->shouldBe('4567');
- $this->read()->shouldBe('89');
- }
-
- function it_should_return_false_on_a_non_blocking_read_when_no_data_is_available()
- {
- $this->createSocketPair();
-
- $this->beConstructedWith($this->local);
-
- $this->read(false)->shouldBe(false);
- }
-
- function it_should_return_false_on_a_blocking_read_when_the_peer_has_closed()
- {
- $this->createSocketPair();
- fclose($this->remote);
-
- $this->beConstructedWith($this->local);
-
- $this->read()->shouldBe(false);
- }
-
- function it_should_leave_the_socket_in_blocking_mode_after_a_non_blocking_read()
- {
- $this->createSocketPair();
-
- $this->beConstructedWith($this->local);
-
- $this->read(false);
-
- if (stream_get_meta_data($this->local)['blocked'] !== true) {
- throw new \RuntimeException('Socket should be in blocking mode after a non-blocking read.');
- }
- }
-}
diff --git a/tests/unit/Pdu.php b/tests/unit/Pdu.php
new file mode 100644
index 0000000..6a92078
--- /dev/null
+++ b/tests/unit/Pdu.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Tests\Unit\FreeDSx\Socket;
+
+use FreeDSx\Asn1\Type\AbstractType;
+use FreeDSx\Socket\PduInterface;
+
+/**
+ * Throw away PDU class for message queue tests.
+ */
+class Pdu implements PduInterface
+{
+ public function __construct(private readonly AbstractType $type)
+ {
+ }
+
+ public function toAsn1(): AbstractType
+ {
+ return $this->type;
+ }
+
+ public static function fromAsn1(AbstractType $asn1): self
+ {
+ return new self($asn1);
+ }
+}
diff --git a/tests/unit/Queue/Asn1MessageQueueTest.php b/tests/unit/Queue/Asn1MessageQueueTest.php
new file mode 100644
index 0000000..f34be58
--- /dev/null
+++ b/tests/unit/Queue/Asn1MessageQueueTest.php
@@ -0,0 +1,141 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Tests\Unit\FreeDSx\Socket\Queue;
+
+use FreeDSx\Asn1\Encoder\EncoderInterface;
+use FreeDSx\Asn1\Exception\PartialPduException;
+use FreeDSx\Asn1\Type\IntegerType;
+use FreeDSx\Socket\Exception\ConnectionException;
+use FreeDSx\Socket\Queue\Asn1MessageQueue;
+use FreeDSx\Socket\Socket;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+use Tests\Unit\FreeDSx\Socket\Pdu;
+
+final class Asn1MessageQueueTest extends TestCase
+{
+ private Socket&MockObject $socket;
+
+ private EncoderInterface&MockObject $encoder;
+
+ private Asn1MessageQueue $subject;
+
+ protected function setUp(): void
+ {
+ $this->socket = $this->createMock(Socket::class);
+ $this->encoder = $this->createMock(EncoderInterface::class);
+ $this->subject = new Asn1MessageQueue(
+ $this->socket,
+ $this->encoder,
+ Pdu::class,
+ );
+ }
+
+ public function test_it_should_return_a_single_message_on_tcp_read(): void
+ {
+ $this->socket->method('read')->willReturn('foo');
+ $this->encoder
+ ->expects(self::atLeastOnce())
+ ->method('decode')
+ ->with('foo')
+ ->willReturn(new IntegerType(100));
+ $this->encoder->method('getLastPosition')->willReturn(3);
+
+ self::assertEquals(
+ new Pdu(new IntegerType(100)),
+ $this->subject->getMessage(),
+ );
+ }
+
+ public function test_it_should_continue_on_during_partial_PDUs(): void
+ {
+ $this->socket
+ ->method('read')
+ ->willReturnOnConsecutiveCalls('foo', 'bar');
+ $this->encoder
+ ->expects(self::atLeast(2))
+ ->method('decode')
+ ->willReturnCallback(
+ static fn (string $bytes): IntegerType => match ($bytes) {
+ 'foo' => throw new PartialPduException(),
+ 'foobar' => new IntegerType(100),
+ default => self::fail("Unexpected decode argument: {$bytes}"),
+ },
+ );
+ $this->encoder->method('getLastPosition')->willReturn(3);
+
+ self::assertEquals(
+ new Pdu(new IntegerType(100)),
+ $this->subject->getMessage(),
+ );
+ }
+
+ public function test_it_should_throw_an_exception_on_get_message_when_there_is_none(): void
+ {
+ $this->socket->method('read')->willReturn(false);
+
+ $this->expectException(ConnectionException::class);
+
+ $this->subject->getMessage();
+ }
+
+ public function test_it_should_not_peek_the_socket_after_decoding_a_complete_message(): void
+ {
+ $this->socket->method('read')->willReturnCallback(
+ static function (bool $block = true): string {
+ if (!$block) {
+ self::fail('socket->read(false) should not be called after decoding a complete message.');
+ }
+ return 'foo';
+ },
+ );
+ $this->encoder
+ ->expects(self::atLeastOnce())
+ ->method('decode')
+ ->with('foo')
+ ->willReturn(new IntegerType(100));
+ $this->encoder->method('getLastPosition')->willReturn(3);
+
+ self::assertEquals(
+ new Pdu(new IntegerType(100)),
+ $this->subject->getMessage(),
+ );
+ }
+
+ public function test_it_should_yield_messages_continuously_from_the_generator(): void
+ {
+ $this->socket
+ ->method('read')
+ ->willReturnOnConsecutiveCalls('foobar', 'baz');
+ $this->encoder
+ ->method('decode')
+ ->willReturnCallback(
+ static fn (string $bytes): IntegerType => match ($bytes) {
+ 'foobar' => new IntegerType(1),
+ 'bar' => new IntegerType(2),
+ 'baz' => new IntegerType(3),
+ default => self::fail("Unexpected decode argument: {$bytes}"),
+ },
+ );
+ $this->encoder->method('getLastPosition')->willReturn(3);
+
+ $iter = $this->subject->getMessages();
+
+ self::assertEquals(new Pdu(new IntegerType(1)), $iter->current());
+ $iter->next();
+ self::assertEquals(new Pdu(new IntegerType(2)), $iter->current());
+ $iter->next();
+ self::assertEquals(new Pdu(new IntegerType(3)), $iter->current());
+ }
+}
diff --git a/tests/unit/Queue/BufferTest.php b/tests/unit/Queue/BufferTest.php
new file mode 100644
index 0000000..b087238
--- /dev/null
+++ b/tests/unit/Queue/BufferTest.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Tests\Unit\FreeDSx\Socket\Queue;
+
+use FreeDSx\Socket\Queue\Buffer;
+use PHPUnit\Framework\TestCase;
+
+final class BufferTest extends TestCase
+{
+ private Buffer $subject;
+
+ protected function setUp(): void
+ {
+ $this->subject = new Buffer('foo', 4);
+ }
+
+ public function test_it_should_get_the_bytes(): void
+ {
+ self::assertSame(
+ 'foo',
+ $this->subject->bytes(),
+ );
+ }
+
+ public function test_it_should_get_where_the_buffer_ends(): void
+ {
+ self::assertSame(
+ 4,
+ $this->subject->endsAt(),
+ );
+ }
+}
diff --git a/tests/unit/Queue/MessageTest.php b/tests/unit/Queue/MessageTest.php
new file mode 100644
index 0000000..b80abb0
--- /dev/null
+++ b/tests/unit/Queue/MessageTest.php
@@ -0,0 +1,52 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Tests\Unit\FreeDSx\Socket\Queue;
+
+use FreeDSx\Asn1\Type\IntegerType;
+use FreeDSx\Socket\Queue\Message;
+use PHPUnit\Framework\TestCase;
+use Tests\Unit\FreeDSx\Socket\Pdu;
+
+final class MessageTest extends TestCase
+{
+ private Message $subject;
+
+ protected function setUp(): void
+ {
+ $this->subject = new Message(new Pdu(new IntegerType(1)));
+ }
+
+ public function test_it_should_get_the_message(): void
+ {
+ self::assertEquals(
+ new Pdu(new IntegerType(1)),
+ $this->subject->getMessage(),
+ );
+ }
+
+ public function test_it_should_have_no_last_position_data_by_default(): void
+ {
+ self::assertNull($this->subject->getLastPosition());
+ }
+
+ public function test_it_should_get_the_last_position(): void
+ {
+ $this->subject = new Message(new Pdu(new IntegerType(1)), 2);
+
+ self::assertSame(
+ 2,
+ $this->subject->getLastPosition(),
+ );
+ }
+}
diff --git a/tests/spec/FreeDSx/Socket/RequiresUnixTransport.php b/tests/unit/RequiresUnixTransport.php
similarity index 68%
rename from tests/spec/FreeDSx/Socket/RequiresUnixTransport.php
rename to tests/unit/RequiresUnixTransport.php
index b0550d6..5febef1 100644
--- a/tests/spec/FreeDSx/Socket/RequiresUnixTransport.php
+++ b/tests/unit/RequiresUnixTransport.php
@@ -1,5 +1,7 @@
beConstructedWith(['servers' => ['foo', 'bar']]);
- }
+ private ?string $unixPath = null;
- function letGo(): void
+ protected function tearDown(): void
{
if (is_resource($this->unixServer)) {
fclose($this->unixServer);
@@ -46,22 +37,21 @@ function letGo(): void
}
}
- function it_is_initializable()
- {
- $this->shouldHaveType(SocketPool::class);
- }
-
- function it_should_respect_the_transport_type_when_connecting()
+ public function test_it_should_respect_the_transport_type_when_connecting(): void
{
$this->requireUnixTransport();
$this->unixPath = sys_get_temp_dir() . '/freedsx_socket_pool_' . uniqid('', true) . '.sock';
- $this->unixServer = stream_socket_server('unix://' . $this->unixPath);
+ $server = stream_socket_server('unix://' . $this->unixPath);
+ if ($server === false) {
+ self::fail('Failed to create unix socket server.');
+ }
+ $this->unixServer = $server;
- $this->beConstructedWith([
+ $subject = new SocketPool([
'servers' => [$this->unixPath],
'transport' => 'unix',
]);
- $this->connect()->isConnected()->shouldBeEqualTo(true);
+ self::assertTrue($subject->connect()->isConnected());
}
}
diff --git a/tests/unit/SocketServerTest.php b/tests/unit/SocketServerTest.php
new file mode 100644
index 0000000..43f6d1b
--- /dev/null
+++ b/tests/unit/SocketServerTest.php
@@ -0,0 +1,105 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Tests\Unit\FreeDSx\Socket;
+
+use FreeDSx\Socket\Exception\ConnectionException;
+use FreeDSx\Socket\Socket;
+use FreeDSx\Socket\SocketServer;
+use PHPUnit\Framework\TestCase;
+
+final class SocketServerTest extends TestCase
+{
+ use RequiresUnixTransport;
+
+ private string $testSocket = '';
+
+ private ?SocketServer $subject = null;
+
+ protected function setUp(): void
+ {
+ $this->testSocket = sys_get_temp_dir() . '/phpunit.socket';
+ }
+
+ protected function tearDown(): void
+ {
+ $this->subject?->close();
+ if ($this->testSocket !== '' && file_exists($this->testSocket)) {
+ @unlink($this->testSocket);
+ }
+ }
+
+ public function test_it_should_throw_a_connection_exception_if_it_cannot_listen_on_the_ip_and_port(): void
+ {
+ $this->subject = new SocketServer([]);
+
+ $this->expectException(ConnectionException::class);
+
+ $this->subject->listen('1.2.3.4', 389);
+ }
+
+ public function test_it_should_return_null_if_there_is_no_client_on_accept(): void
+ {
+ $this->subject = SocketServer::bind('0.0.0.0', 33389);
+
+ self::assertNull($this->subject->accept(0));
+ }
+
+ public function test_it_should_construct_a_tcp_based_socket_server(): void
+ {
+ $this->subject = SocketServer::bindTcp('0.0.0.0', 33389);
+
+ self::assertSame(
+ 'tcp',
+ $this->subject->getOptions()['transport']
+ );
+ self::assertTrue($this->subject->isConnected());
+ $this->subject->close();
+ self::assertFalse($this->subject->isConnected());
+ }
+
+ public function test_it_should_construct_a_udp_based_socket_server(): void
+ {
+ $this->subject = SocketServer::bindUdp('0.0.0.0', 33389);
+
+ self::assertSame('udp', $this->subject->getOptions()['transport']);
+ }
+
+ public function test_it_should_construct_a_unix_based_socket_server(): void
+ {
+ $this->requireUnixTransport();
+
+ $this->subject = SocketServer::bindUnix($this->testSocket);
+
+ self::assertSame(
+ 'unix',
+ $this->subject->getOptions()['transport']
+ );
+ self::assertTrue($this->subject->isConnected());
+ $this->subject->close();
+ self::assertFalse($this->subject->isConnected());
+ }
+
+ public function test_it_should_receive_data(): void
+ {
+ $this->subject = SocketServer::bindUdp('0.0.0.0', 33389);
+
+ $client = Socket::udp('127.0.0.1', ['port' => 33389]);
+ $client->write('foo');
+
+ self::assertSame(
+ 'foo',
+ $this->subject->receive()
+ );
+ }
+}
diff --git a/tests/unit/SocketTest.php b/tests/unit/SocketTest.php
new file mode 100644
index 0000000..7b3a7c8
--- /dev/null
+++ b/tests/unit/SocketTest.php
@@ -0,0 +1,220 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Tests\Unit\FreeDSx\Socket;
+
+use FreeDSx\Socket\Socket;
+use PHPUnit\Framework\TestCase;
+
+final class SocketTest extends TestCase
+{
+ use RequiresUnixTransport;
+
+ /**
+ * @var resource|null
+ */
+ private $local;
+
+ /**
+ * @var resource|null
+ */
+ private $remote;
+
+ /**
+ * @var resource|null
+ */
+ private $unixServer;
+
+ private ?string $unixPath = null;
+
+ protected function tearDown(): void
+ {
+ if (is_resource($this->remote)) {
+ fclose($this->remote);
+ }
+ if (is_resource($this->local)) {
+ fclose($this->local);
+ }
+ if (is_resource($this->unixServer)) {
+ fclose($this->unixServer);
+ }
+ if ($this->unixPath !== null && file_exists($this->unixPath)) {
+ @unlink($this->unixPath);
+ }
+ }
+
+ public function test_it_should_get_the_options_for_the_socket(): void
+ {
+ $subject = new Socket();
+
+ self::assertSame(
+ [
+ 'transport' => 'tcp',
+ 'port' => 389,
+ 'use_ssl' => false,
+ 'ssl_crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | STREAM_CRYPTO_METHOD_TLS_CLIENT,
+ 'ssl_ciphers' => 'DEFAULT',
+ 'ssl_validate_cert' => true,
+ 'ssl_allow_self_signed' => null,
+ 'ssl_ca_cert' => null,
+ 'ssl_peer_name' => null,
+ 'timeout_connect' => 3,
+ 'timeout_read' => 15,
+ 'buffer_size' => 8192,
+ ],
+ $subject->getOptions(),
+ );
+ }
+
+ public function test_it_should_create_a_socket(): void
+ {
+ $subject = Socket::create('www.google.com', ['port' => 80]);
+
+ self::assertTrue($subject->isConnected());
+ }
+
+ public function test_it_should_create_a_unix_based_socket(): void
+ {
+ $path = $this->createUnixServer();
+
+ $subject = Socket::unix($path);
+
+ self::assertSame('unix', $subject->getOptions()['transport']);
+ }
+
+ public function test_it_should_create_a_tcp_based_socket(): void
+ {
+ self::assertSame(
+ 'tcp',
+ Socket::tcp('www.google.com', ['port' => 80])->getOptions()['transport'],
+ );
+ }
+
+ public function test_it_should_create_a_udp_based_socket(): void
+ {
+ self::assertSame(
+ 'udp',
+ Socket::udp('8.8.8.8', ['port' => 53])->getOptions()['transport'],
+ );
+ }
+
+ public function test_it_should_have_a_default_buffer_size_of_65507_for_UDP(): void
+ {
+ self::assertSame(
+ 65507,
+ Socket::udp('8.8.8.8', ['port' => 53])->getOptions()['buffer_size'],
+ );
+ }
+
+ public function test_it_should_tell_whether_or_not_it_is_connected_for_tcp(): void
+ {
+ $subject = Socket::tcp('www.google.com', ['port' => 80]);
+
+ self::assertTrue($subject->isConnected());
+ $subject->close();
+ self::assertFalse($subject->isConnected());
+ }
+
+ public function test_it_should_tell_whether_or_not_it_is_connected_for_udp(): void
+ {
+ $subject = Socket::udp('www.google.com', ['port' => 53]);
+
+ self::assertTrue($subject->isConnected());
+ $subject->close();
+ self::assertFalse($subject->isConnected());
+ }
+
+ public function test_it_should_tell_whether_it_is_connected_for_unix(): void
+ {
+ $path = $this->createUnixServer();
+ $subject = Socket::unix($path);
+
+ self::assertTrue($subject->isConnected());
+ $subject->close();
+ self::assertFalse($subject->isConnected());
+ }
+
+ public function test_it_should_return_at_most_buffer_size_bytes_per_read(): void
+ {
+ [$local, $remote] = $this->createSocketPair();
+ fwrite($remote, '0123456789');
+
+ $subject = new Socket($local, ['buffer_size' => 4]);
+
+ self::assertSame('0123', $subject->read());
+ self::assertSame('4567', $subject->read());
+ self::assertSame('89', $subject->read());
+ }
+
+ public function test_it_should_return_false_on_a_non_blocking_read_when_no_data_is_available(): void
+ {
+ [$local] = $this->createSocketPair();
+ $subject = new Socket($local);
+
+ self::assertFalse($subject->read(false));
+ }
+
+ public function test_it_should_return_false_on_a_blocking_read_when_the_peer_has_closed(): void
+ {
+ [$local, $remote] = $this->createSocketPair();
+ fclose($remote);
+ $subject = new Socket($local);
+
+ self::assertFalse($subject->read());
+ }
+
+ public function test_it_should_leave_the_socket_in_blocking_mode_after_a_non_blocking_read(): void
+ {
+ [$local] = $this->createSocketPair();
+ $subject = new Socket($local);
+
+ $subject->read(false);
+
+ self::assertTrue(stream_get_meta_data($local)['blocked']);
+ }
+
+ /**
+ * @return array{0: resource, 1: resource}
+ */
+ private function createSocketPair(): array
+ {
+ $domain = DIRECTORY_SEPARATOR === '\\'
+ ? STREAM_PF_INET
+ : STREAM_PF_UNIX;
+
+ $pair = stream_socket_pair(
+ $domain,
+ STREAM_SOCK_STREAM,
+ STREAM_IPPROTO_IP,
+ );
+ if ($pair === false) {
+ self::fail('Failed to create socket pair.');
+ }
+ [$this->local, $this->remote] = $pair;
+
+ return [$this->local, $this->remote];
+ }
+
+ private function createUnixServer(): string
+ {
+ $this->requireUnixTransport();
+ $this->unixPath = sys_get_temp_dir() . '/freedsx_socket_' . uniqid('', true) . '.sock';
+ $server = stream_socket_server('unix://' . $this->unixPath);
+ if ($server === false) {
+ self::fail('Failed to create unix socket server.');
+ }
+ $this->unixServer = $server;
+
+ return $this->unixPath;
+ }
+}