From fc66579bf8aaa8430aab6666e34190d15d77ce29 Mon Sep 17 00:00:00 2001 From: toimtoimtoim Date: Wed, 9 Jan 2019 21:16:04 +0200 Subject: [PATCH] fix DoubleWord->getUInt32 return type problem on 32bit arch --- .travis.yml | 8 +++- docker-run-test.sh | 21 ++++++++++ src/Packet/DoubleWord.php | 7 +++- src/Utils/Types.php | 4 -- tests/unit/Composer/Read/ReadAddressTest.php | 19 +++++++-- .../unit/Composer/Write/WriteAddressTest.php | 10 +++-- tests/unit/Packet/DoubleWordTest.php | 18 +++++++++ tests/unit/Packet/QuadWordTest.php | 12 ++++++ tests/unit/Utils/TypesTest.php | 40 +++++++++++-------- 9 files changed, 110 insertions(+), 29 deletions(-) create mode 100755 docker-run-test.sh diff --git a/.travis.yml b/.travis.yml index 87b06a0..192c7c5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,11 @@ php: - 7.0 - 7.1 - 7.2 + - 7.3 + +env: + - ARCH=64bit + - ARCH=32bit before_install: - travis_retry composer self-update @@ -18,7 +23,8 @@ install: - travis_retry composer update --no-interaction script: - - vendor/bin/phpunit --testsuite 'unit-tests' --coverage-clover=coverage.xml + - if [ "$ARCH" = "32bit" ]; then ./docker-run-test.sh $(phpenv version-name); fi + - if [ "$ARCH" = "64bit" ]; then ./vendor/bin/phpunit --testsuite 'unit-tests' --coverage-clover=coverage.xml; fi after_success: - bash <(curl -s https://codecov.io/bash) diff --git a/docker-run-test.sh b/docker-run-test.sh new file mode 100755 index 0000000..c39a7ea --- /dev/null +++ b/docker-run-test.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +PHP_VERSION=${1:-7.1} +PHPUNIT_OPTS=${*:2} + +# if ARCH env variable is not set then use 64bit as default +if [[ -z "${ARCH}" ]]; then + ARCH=64bit +fi + +if [[ "$ARCH" == "64bit" ]]; then + IMAGE=php +elif [[ "$ARCH" == "32bit" ]]; then + IMAGE=i386/php +else + echo Unknown arch value: ${ARCH} + exit 1 +fi + +echo Using ${ARCH} PHP-${PHP_VERSION} +docker run -i -v "${PWD}:/code" -w /code/ ${IMAGE}:${PHP_VERSION}-cli-alpine php /code/vendor/bin/phpunit ${PHPUNIT_OPTS} \ No newline at end of file diff --git a/src/Packet/DoubleWord.php b/src/Packet/DoubleWord.php index c6713e2..f86815c 100644 --- a/src/Packet/DoubleWord.php +++ b/src/Packet/DoubleWord.php @@ -16,10 +16,13 @@ protected function getByteLength(): int /** * @param int $endianness byte and word order for modbus binary data - * @return int + * + * NB: On 32bit php and having highest bit set method will return float instead of int value. This is due 32bit php supports only 32bit signed integers + * + * @return int|float * @throws \RuntimeException */ - public function getUInt32(int $endianness = null): int + public function getUInt32(int $endianness = null) { return Types::parseUInt32($this->getData(), $endianness); } diff --git a/src/Utils/Types.php b/src/Utils/Types.php index 94cc530..43375ee 100644 --- a/src/Utils/Types.php +++ b/src/Utils/Types.php @@ -543,10 +543,6 @@ private static function toInt32Internal($data, $endianness = null): string */ public static function toInt64($data, int $toEndian = null): string { - if (PHP_INT_SIZE !== 8) { - throw new InvalidArgumentException('64-bit format codes are not available for 32-bit versions of PHP'); - } - $words = [ ($data >> 48) & 0xFFFF, ($data >> 32) & 0xFFFF, diff --git a/tests/unit/Composer/Read/ReadAddressTest.php b/tests/unit/Composer/Read/ReadAddressTest.php index 3332bbe..3c982e7 100644 --- a/tests/unit/Composer/Read/ReadAddressTest.php +++ b/tests/unit/Composer/Read/ReadAddressTest.php @@ -4,8 +4,8 @@ use ModbusTcpClient\Composer\Address; -use ModbusTcpClient\Packet\ModbusFunction\ReadHoldingRegistersResponse; use ModbusTcpClient\Composer\Read\ReadAddress; +use ModbusTcpClient\Packet\ModbusFunction\ReadHoldingRegistersResponse; use PHPUnit\Framework\TestCase; class ReadAddressTest extends TestCase @@ -103,7 +103,12 @@ function (\Exception $exception, $address, $response) use (&$errCbAddress, &$err $value = $address->extract($responsePacket); - $this->assertEquals('ModbusTcpClient\Exception\OverflowException_64-bit PHP supports only up to 63-bit signed integers. Current input has 64th bit set and overflows. Hex: ffffffffffffffff', $value); + $error = 'ModbusTcpClient\Exception\OverflowException_64-bit PHP supports only up to 63-bit signed integers. Current input has 64th bit set and overflows. Hex: ffffffffffffffff'; + if (PHP_INT_SIZE === 4) { + $error = 'ModbusTcpClient\Exception\ParseException_64-bit format codes are not available for 32-bit versions of PHP'; + } + $this->assertEquals($error, $value); + $this->assertEquals($responsePacket, $errCbResponse); $this->assertEquals($address, $errCbAddress); } @@ -116,7 +121,7 @@ public function testErrorCallbackWhenCbFails() $errCbResponse = null; $address = new ReadAddress( 0, - Address::TYPE_UINT64, + Address::TYPE_INT16, null, function () { throw new \RuntimeException('catch me'); @@ -187,6 +192,10 @@ public function testExtractFloat() public function testExtractUInt64() { + if (PHP_INT_SIZE === 4) { + $this->markTestSkipped('32-bit version of PHP'); + } + $responsePacket = new ReadHoldingRegistersResponse("\x08\xFF\xFF\x7F\xFF\x00\x00\x00\x00", 3, 33152); $address = new ReadAddress(0, Address::TYPE_UINT64); @@ -197,6 +206,10 @@ public function testExtractUInt64() public function testExtractInt64() { + if (PHP_INT_SIZE === 4) { + $this->markTestSkipped('32-bit version of PHP'); + } + $responsePacket = new ReadHoldingRegistersResponse("\x08\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 3, 33152); $address = new ReadAddress(0, Address::TYPE_INT64); diff --git a/tests/unit/Composer/Write/WriteAddressTest.php b/tests/unit/Composer/Write/WriteAddressTest.php index 26a7a67..dcab352 100644 --- a/tests/unit/Composer/Write/WriteAddressTest.php +++ b/tests/unit/Composer/Write/WriteAddressTest.php @@ -20,8 +20,12 @@ public function testGetValue() /** * @dataProvider toBinaryProvider */ - public function testToBinary(string $expectedBinaryString, string $type, $value, $expectedException = null) + public function testToBinary(string $expectedBinaryString, string $type, $value, $expectedException = null, $skipOn32Bit = false) { + if ($skipOn32Bit && PHP_INT_SIZE === 4) { + $this->markTestSkipped('32-bit version of PHP'); + } + if ($expectedException !== null) { $this->expectException($expectedException); } @@ -37,8 +41,8 @@ public function toBinaryProvider() 'uint16 to binary' => ["\xFF\xFF", Address::TYPE_UINT16, 65535], 'int32 to binary' => ["\x00\x00\x80\x00", Address::TYPE_INT32, -2147483648], 'uint32 to binary' => ["\x00\x01\x00\x00", Address::TYPE_UINT32, 1], - 'int64 to binary' => ["\x00\x00\x00\x00\x00\x00\x80\x00", Address::TYPE_INT64, -9223372036854775808], - 'uint64 to binary' => ["\x00\x01\x00\x00\x00\x00\x00\x00", Address::TYPE_UINT64, 1], + 'int64 to binary' => ["\x00\x00\x00\x00\x00\x00\x80\x00", Address::TYPE_INT64, -9223372036854775808, null, true], + 'uint64 to binary' => ["\x00\x01\x00\x00\x00\x00\x00\x00", Address::TYPE_UINT64, 1, null, true], 'float to binary' => ["\xcc\xcd\x3f\xec", Address::TYPE_FLOAT, 1.85], 'string to binary' => ["\x00\x6E\x65\x72\xF8\x53", Address::TYPE_STRING, 'Søren', \ModbusTcpClient\Exception\InvalidArgumentException::class], ]; diff --git a/tests/unit/Packet/DoubleWordTest.php b/tests/unit/Packet/DoubleWordTest.php index 00de74d..161acc6 100644 --- a/tests/unit/Packet/DoubleWordTest.php +++ b/tests/unit/Packet/DoubleWordTest.php @@ -4,6 +4,7 @@ use ModbusTcpClient\Packet\DoubleWord; +use ModbusTcpClient\Utils\Endian; use PHPUnit\Framework\TestCase; class DoubleWordTest extends TestCase @@ -31,6 +32,23 @@ public function testShouldGetUInt32() $this->assertEquals(2147483647, $dWord->getUInt32()); } + public function testShouldGetUInt32Types() + { + $dWordUpperLimit = new DoubleWord("\xFF\xFF\xFF\xFF"); + $dWord = new DoubleWord("\x00\x00\x00\x01"); + + if (PHP_INT_SIZE === 8) { + $this->assertTrue(is_int($dWordUpperLimit->getUInt32(Endian::BIG_ENDIAN))); + $this->assertTrue(is_int($dWord->getUInt32(Endian::BIG_ENDIAN))); + } else { + $this->assertTrue(is_float($dWordUpperLimit->getUInt32(Endian::BIG_ENDIAN))); + $this->assertTrue(is_int($dWord->getUInt32(Endian::BIG_ENDIAN))); + } + + $this->assertEquals(4294967295, $dWordUpperLimit->getUInt32(Endian::BIG_ENDIAN)); + $this->assertEquals(1, $dWord->getUInt32(Endian::BIG_ENDIAN)); + } + public function testShouldGetInt32() { $dWord = new DoubleWord("\x00\x00\x80\x00"); diff --git a/tests/unit/Packet/QuadWordTest.php b/tests/unit/Packet/QuadWordTest.php index 9b9382a..bb6e7a7 100644 --- a/tests/unit/Packet/QuadWordTest.php +++ b/tests/unit/Packet/QuadWordTest.php @@ -32,6 +32,10 @@ public function testShouldNotConstructFromLongerData() */ public function testShouldOverflow() { + if (PHP_INT_SIZE === 4) { + $this->markTestSkipped('64-bit format codes are not available for 32-bit versions of PHP'); + } + // 64-bit PHP supports only up to 63-bit signed integers. Parsing this value results '-1' which is overflow $quadWord = new QuadWord("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"); @@ -40,6 +44,10 @@ public function testShouldOverflow() public function testShouldGetUInt64() { + if (PHP_INT_SIZE === 4) { + $this->markTestSkipped('64-bit format codes are not available for 32-bit versions of PHP'); + } + $quadWord = new QuadWord("\xFF\xFF\x7F\xFF\x00\x00\x00\x00"); $this->assertEquals(2147483647, $quadWord->getUInt64(Endian::BIG_ENDIAN_LOW_WORD_FIRST)); @@ -47,6 +55,10 @@ public function testShouldGetUInt64() public function testShouldGetInt64() { + if (PHP_INT_SIZE === 4) { + $this->markTestSkipped('64-bit format codes are not available for 32-bit versions of PHP'); + } + $quadWord = new QuadWord("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"); $this->assertEquals(-1, $quadWord->getInt64(Endian::BIG_ENDIAN_LOW_WORD_FIRST)); diff --git a/tests/unit/Utils/TypesTest.php b/tests/unit/Utils/TypesTest.php index e4a2fc3..fe722f2 100644 --- a/tests/unit/Utils/TypesTest.php +++ b/tests/unit/Utils/TypesTest.php @@ -500,7 +500,7 @@ public function toUint16Provider() /** * @dataProvider toInt32Provider */ - public function testShouldEncodeToBinaryInt32(string $expectedBinaryString, int $integer, int $endian, $expectedException = null) + public function testShouldEncodeToBinaryInt32(string $expectedBinaryString, $integer, int $endian, $expectedException = null) { if ($expectedException !== null) { $this->expectException($expectedException); @@ -530,7 +530,7 @@ public function toInt32Provider() /** * @dataProvider toUint32Provider */ - public function testShouldEncodeToBinaryUint32(string $expectedBinaryString, int $integer, int $endian, $expectedException = null) + public function testShouldEncodeToBinaryUint32(string $expectedBinaryString, $integer, int $endian, $expectedException = null) { if ($expectedException !== null) { $this->expectException($expectedException); @@ -562,8 +562,12 @@ public function toUint32Provider() /** * @dataProvider toInt64Provider */ - public function testShouldEncodeToBinaryInt64(string $expectedBinaryString, int $integer, int $endian) + public function testShouldEncodeToBinaryInt64(string $expectedBinaryString, $integer, int $endian, $skipOn32Bit = false) { + if ($skipOn32Bit && PHP_INT_SIZE === 4) { + $this->markTestSkipped('32-bit version of PHP'); + } + $this->assertEquals($expectedBinaryString, Types::toInt64($integer, $endian)); } @@ -571,26 +575,30 @@ public function toInt64Provider() { return [ 'BigEndianLowWordFirst: toInt64 1' => ["\x00\x01\x00\x00\x00\x00\x00\x00", 1, Endian::BIG_ENDIAN_LOW_WORD_FIRST], - 'BigEndianLowWordFirst: toInt64 2923517522' => ["\x56\x52\xAE\x41\x00\x00\x00\x0", 2923517522, Endian::BIG_ENDIAN_LOW_WORD_FIRST], + 'BigEndianLowWordFirst: toInt64 2923517522' => ["\x56\x52\xAE\x41\x00\x00\x00\x0", 2923517522, Endian::BIG_ENDIAN_LOW_WORD_FIRST, true], 'BigEndianLowWordFirst: toInt64 67305985' => ["\x02\x01\x04\x03\x00\x00\x00\x00", 67305985, Endian::BIG_ENDIAN_LOW_WORD_FIRST], - 'BigEndianLowWordFirst: toInt64 9223372036854775807' => ["\xFF\xFF\xFF\xFF\xFF\xFF\x7F\xFF", 9223372036854775807, Endian::BIG_ENDIAN_LOW_WORD_FIRST], - 'BigEndianLowWordFirst: toInt64 -9223372036854775808' => ["\x00\x00\x00\x00\x00\x00\x80\x00", -9223372036854775808, Endian::BIG_ENDIAN_LOW_WORD_FIRST], + 'BigEndianLowWordFirst: toInt64 9223372036854775807' => ["\xFF\xFF\xFF\xFF\xFF\xFF\x7F\xFF", 9223372036854775807, Endian::BIG_ENDIAN_LOW_WORD_FIRST, true], + 'BigEndianLowWordFirst: toInt64 -9223372036854775808' => ["\x00\x00\x00\x00\x00\x00\x80\x00", -9223372036854775808, Endian::BIG_ENDIAN_LOW_WORD_FIRST, true], 'BigEndian: toInt64 1' => ["\x00\x00\x00\x00\x00\x00\x00\x01", 1, Endian::BIG_ENDIAN], - 'BigEndian: toInt64 2923517522' => ["\x00\x00\x00\x00\xAE\x41\x56\x52", 2923517522, Endian::BIG_ENDIAN], + 'BigEndian: toInt64 2923517522' => ["\x00\x00\x00\x00\xAE\x41\x56\x52", 2923517522, Endian::BIG_ENDIAN, true], 'BigEndian: toInt64 67305985' => ["\x00\x00\x00\x00\x04\x03\x02\x01", 67305985, Endian::BIG_ENDIAN], - 'BigEndian: toInt64 9223372036854775807' => ["\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 9223372036854775807, Endian::BIG_ENDIAN], - 'BigEndian: toInt64 -9223372036854775808' => ["\x80\x00\x00\x00\x00\x00\x00\x00", -9223372036854775808, Endian::BIG_ENDIAN], + 'BigEndian: toInt64 9223372036854775807' => ["\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 9223372036854775807, Endian::BIG_ENDIAN, true], + 'BigEndian: toInt64 -9223372036854775808' => ["\x80\x00\x00\x00\x00\x00\x00\x00", -9223372036854775808, Endian::BIG_ENDIAN, true], - 'LittleEndian: toInt64 -9223372036854775808' => ["\x00\x80\x00\x00\x00\x00\x00\x00", -9223372036854775808, Endian::LITTLE_ENDIAN], + 'LittleEndian: toInt64 -9223372036854775808' => ["\x00\x80\x00\x00\x00\x00\x00\x00", -9223372036854775808, Endian::LITTLE_ENDIAN, true], ]; } /** * @dataProvider toUint64Provider */ - public function testShouldEncodeToBinaryUint64(string $expectedBinaryString, int $integer, int $endian, $expectedException = null) + public function testShouldEncodeToBinaryUint64(string $expectedBinaryString, $integer, int $endian, $expectedException = null, $skipOn32Bit = false) { + if ($skipOn32Bit && PHP_INT_SIZE === 4) { + $this->markTestSkipped('32-bit version of PHP'); + } + if ($expectedException !== null) { $this->expectException($expectedException); } @@ -601,19 +609,19 @@ public function toUint64Provider() { return [ 'BigEndianLowWordFirst: toInt64 1' => ["\x00\x01\x00\x00\x00\x00\x00\x00", 1, Endian::BIG_ENDIAN_LOW_WORD_FIRST], - 'BigEndianLowWordFirst: toInt64 2923517522' => ["\x56\x52\xAE\x41\x00\x00\x00\x0", 2923517522, Endian::BIG_ENDIAN_LOW_WORD_FIRST], + 'BigEndianLowWordFirst: toInt64 2923517522' => ["\x56\x52\xAE\x41\x00\x00\x00\x0", 2923517522, Endian::BIG_ENDIAN_LOW_WORD_FIRST, null, true], 'BigEndianLowWordFirst: toInt64 67305985' => ["\x02\x01\x04\x03\x00\x00\x00\x00", 67305985, Endian::BIG_ENDIAN_LOW_WORD_FIRST], - 'BigEndianLowWordFirst: toInt64 9223372036854775807' => ["\xFF\xFF\xFF\xFF\xFF\xFF\x7F\xFF", 9223372036854775807, Endian::BIG_ENDIAN_LOW_WORD_FIRST], + 'BigEndianLowWordFirst: toInt64 9223372036854775807' => ["\xFF\xFF\xFF\xFF\xFF\xFF\x7F\xFF", 9223372036854775807, Endian::BIG_ENDIAN_LOW_WORD_FIRST, null, true], 'BigEndianLowWordFirst: toInt64 -1 (underflow)' => ['', -1, Endian::BIG_ENDIAN_LOW_WORD_FIRST, \ModbusTcpClient\Exception\OverflowException::class], 'BigEndian: toInt64 1' => ["\x00\x00\x00\x00\x00\x00\x00\x01", 1, Endian::BIG_ENDIAN], - 'BigEndian: toInt64 2923517522' => ["\x00\x00\x00\x00\xAE\x41\x56\x52", 2923517522, Endian::BIG_ENDIAN], + 'BigEndian: toInt64 2923517522' => ["\x00\x00\x00\x00\xAE\x41\x56\x52", 2923517522, Endian::BIG_ENDIAN, null, true], 'BigEndian: toInt64 67305985' => ["\x00\x00\x00\x00\x04\x03\x02\x01", 67305985, Endian::BIG_ENDIAN], - 'BigEndian: toInt64 9223372036854775807' => ["\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 9223372036854775807, Endian::BIG_ENDIAN], + 'BigEndian: toInt64 9223372036854775807' => ["\x7F\xFF\xFF\xFF\xFF\xFF\xFF\xFF", 9223372036854775807, Endian::BIG_ENDIAN, null, true], 'BigEndian: toInt64 -1 (underflow)' => ['', -1, Endian::BIG_ENDIAN, \ModbusTcpClient\Exception\OverflowException::class], - 'LittleEndian: toInt64 9223372036854775807' => ["\xFF\x7F\xFF\xFF\xFF\xFF\xFF\xFF", 9223372036854775807, Endian::LITTLE_ENDIAN], + 'LittleEndian: toInt64 9223372036854775807' => ["\xFF\x7F\xFF\xFF\xFF\xFF\xFF\xFF", 9223372036854775807, Endian::LITTLE_ENDIAN, null, true], ]; }