diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..2f0f4a0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,20 @@ +language: php + +sudo: false + +cache: + directories: + - $HOME/.composer/cache + +php: + - 5.6 + - 7.0 + +before_install: + - travis_retry composer self-update + +install: + - travis_retry composer update --no-interaction + +script: + - composer test-ci \ No newline at end of file diff --git a/README.md b/README.md index f478cd8..1c522f0 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,12 @@ Implementation of the basic functionality of the Modbus TCP and UDP based protocol using PHP. -**NOTE: This is a fork to fix & update the library code (and code alone). Notably, the tests are probably all broken.** +**NOTE: This is a fork to fix & update the library code (and code alone).** -> **What's new** -> -> This fork adds a namespace and fixes issues encountered when porting to PHP 7 +##What's new +* This fork adds a namespace and fixes issues encountered when porting to PHP 7 +* Fixes/replaces old MS Windows specific tests ## Implemented features @@ -26,6 +26,7 @@ Implementation of the basic functionality of the Modbus TCP and UDP based protoc ## Requirements * The PHP extension php_sockets.dll should be enabled (server php.ini file) + * PHP 5.3.2 (5.6 for tests) ## Example @@ -53,6 +54,19 @@ Use the `setTimeout($seconds)` and `setSocketTimeout($read_timeout_sec, $write_t Most of the code is (to some extent) commented and documented with PhpDoc. You should get useful tooltips in your IDE. +## Tests + +To run the test suite, you need install the dependencies via composer, then +run PHPUnit. + +NB: PHP 5.6+ is required for tests + + composer install + composer test # or under Windows vendor\bin\phunit.bat + +To report test coverage (created inside ./report/html): + + composer test-coverage ## GoogleCode legacy docs & downloads diff --git a/composer.json b/composer.json index d52d076..9adf523 100644 --- a/composer.json +++ b/composer.json @@ -32,5 +32,10 @@ "psr-4": { "Tests\\": "tests/" } + }, + "scripts": { + "test": "vendor/bin/phpunit", + "test-ci": "vendor/bin/phpunit --coverage-clover report/coverage.xml", + "test-coverage": "vendor/bin/phpunit --coverage-html report/html" } } diff --git a/examples/example_750841_Mmemory.php b/examples/example_750841_Mmemory.php index 6fa8f90..4cb5f03 100644 --- a/examples/example_750841_Mmemory.php +++ b/examples/example_750841_Mmemory.php @@ -1,12 +1,10 @@ - - - + + + diff --git a/examples/example_datatype.php b/examples/example_datatype.php index aeb0a7b..402bdf9 100644 --- a/examples/example_datatype.php +++ b/examples/example_datatype.php @@ -1,12 +1,10 @@ ./tests/* - - - - - src diff --git a/tests/ModbusMaster/Fc15WriteMultipleCoilsTest.php b/tests/ModbusMaster/Fc15WriteMultipleCoilsTest.php index bc59828..2d7a178 100644 --- a/tests/ModbusMaster/Fc15WriteMultipleCoilsTest.php +++ b/tests/ModbusMaster/Fc15WriteMultipleCoilsTest.php @@ -18,4 +18,24 @@ class Fc15WriteMultipleCoilsTest extends MockServerTestCase $packetWithoutTransactionId = substr($clientData[0], 4); $this->assertEquals('00000008000f300000030105', $packetWithoutTransactionId); } + + public function testFc15WriteMultipleCoilsWithMultiWordPacket() + { + $mockResponse = 'a51100000006000f00000020'; + $clientData = static::executeWithMockServer($mockResponse, function ($port) { + $modbus = new ModbusMaster('127.0.0.1', 'TCP'); + $modbus->port = $port; + + $this->assertTrue($modbus->fc15(0, 0, + [ + 1, 0, 1, 1, 0, 1, 1, 1, + 1, 1, 1, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + ])); + }); + + $packetWithoutTransactionId = substr($clientData[0], 4); + $this->assertEquals('0000000b000f0000002004ed0ff0ff', $packetWithoutTransactionId); + } } \ No newline at end of file diff --git a/tests/ModbusMaster/MockServerTestCase.php b/tests/ModbusMaster/MockServerTestCase.php index b6e93fd..ba80b51 100644 --- a/tests/ModbusMaster/MockServerTestCase.php +++ b/tests/ModbusMaster/MockServerTestCase.php @@ -23,6 +23,11 @@ abstract class MockServerTestCase extends TestCase $clientData[] = $output; }); + if (strpos(PHP_OS, 'WIN') === false || getenv('MOCKSERVER_TIMEOUT_USEC') !== false) { + // wait to spin up. needed for linux. unnessecary on Windows 10. + // Ugly but even with 150ms sleep test run faster on Linux + usleep(getenv('MOCKSERVER_TIMEOUT_USEC') ?: 150000); + } $closure($port); }); diff --git a/tests/ModbusMaster/ModbusExceptionTest.php b/tests/ModbusMaster/ModbusExceptionTest.php index 0d31651..aa1c6df 100644 --- a/tests/ModbusMaster/ModbusExceptionTest.php +++ b/tests/ModbusMaster/ModbusExceptionTest.php @@ -1,38 +1,42 @@ expectException(\Exception::class); - $this->expectExceptionMessage("Unknown socket protocol, should be 'TCP' or 'UDP'"); - $modbus = new ModbusMaster('127.0.0.1', 'Mismatch'); $modbus->readCoils(0, 256, 1); } + /** + * @expectedException \Exception + * @expectedExceptionMessage socket_connect() failed. Reason: ()No connection could be made because the target machine actively refused it + */ public function testPortClosedException() { - $this->expectException(\Exception::class); - $this->expectExceptionMessage('socket_connect() failed. Reason: ()No connection could be made because the target machine actively refused it.'); - $modbus = new ModbusMasterTcp('127.0.0.1'); $modbus->setSocketTimeout(0.2, 0.2); $modbus->readCoils(0, 256, 1); } + /** + * @expectedException \Exception + * @expectedExceptionMessageRegExp /Watchdog time expired \[ 0\.5 sec \]!!! Connection to 127\.0\.0\.1:.* is not established/ + */ public function testTimeoutException() { - $this->expectException(\Exception::class); $mockResponse = '89130000000400010101'; // respond with 1 byte (00000001 bits set) [1] static::executeWithMockServer($mockResponse, function ($port) { - $this->expectExceptionMessage("Watchdog time expired [ 0.5 sec ]!!! Connection to 127.0.0.1:{$port} is not established."); - $modbus = new ModbusMaster('127.0.0.1', 'UDP'); $modbus->port = $port; $modbus->setTimeout(0.5); @@ -42,12 +46,12 @@ class ModbusExceptionTest extends MockServerTestCase }, 'UDP', 1); } - + /** + * @expectedException \Exception + * @expectedExceptionMessage Modbus response error code: 3 (ILLEGAL DATA VALUE) + */ public function testThrowIllegalDataValueException() { - $this->expectException(\Exception::class); - $this->expectExceptionMessage('Modbus response error code: 3 (ILLEGAL DATA VALUE)'); - $mockResponse = 'da8700000003008303'; // respond with 1 WORD (2 bytes) [0, 3] $clientData = static::executeWithMockServer($mockResponse, function ($port) { $modbus = new ModbusMaster('127.0.0.1', 'TCP'); diff --git a/tests/ModbusMaster/UdpFc1ReadCoilsTest.php b/tests/ModbusMaster/UdpFc1ReadCoilsTest.php index 78a6e34..a2d563e 100644 --- a/tests/ModbusMaster/UdpFc1ReadCoilsTest.php +++ b/tests/ModbusMaster/UdpFc1ReadCoilsTest.php @@ -13,7 +13,7 @@ class UdpFc1ReadCoilsTest extends MockServerTestCase $modbus = new ModbusMasterUdp('127.0.0.1'); $modbus->port = $port; - usleep(50000); // no idea how to fix this. wait for server to "warm" up or modbus UDP socket will timeout. does not occur with TCP + usleep(150000); // no idea how to fix this. wait for server to "warm" up or modbus UDP socket will timeout. does not occur with TCP $this->assertEquals([1], $modbus->readCoils(0, 256, 1)); }, 'UDP'); diff --git a/tests/PhpType/Bytes2MixedTest.php b/tests/PhpType/Bytes2MixedTest.php index fcaa25b..3692910 100644 --- a/tests/PhpType/Bytes2MixedTest.php +++ b/tests/PhpType/Bytes2MixedTest.php @@ -6,7 +6,7 @@ use PHPUnit\Framework\TestCase; class PhpTypeBytes2Mixed extends TestCase { - const DATA = [ + private $data = [ "0" => 125, // 32098 (DINT) "1" => 98, "2" => 0, @@ -31,15 +31,15 @@ class PhpTypeBytes2Mixed extends TestCase public function testUnsignedInt() { - $this->assertEquals(32098, PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 4))); + $this->assertEquals(32098, PhpType::bytes2unsignedInt(array_slice($this->data, 0, 4))); } public function testSignedInt() { - $this->assertEquals(0, PhpType::bytes2signedInt(array_slice(self::DATA, 4, 4))); - $this->assertEquals(0, PhpType::bytes2signedInt(array_slice(self::DATA, 8, 4))); - $this->assertEquals(-1, PhpType::bytes2signedInt(array_slice(self::DATA, 12, 4))); - $this->assertEquals(-25000, PhpType::bytes2signedInt(array_slice(self::DATA, 16, 2))); - $this->assertEquals(25000, PhpType::bytes2signedInt(array_slice(self::DATA, 18, 2))); + $this->assertEquals(0, PhpType::bytes2signedInt(array_slice($this->data, 4, 4))); + $this->assertEquals(0, PhpType::bytes2signedInt(array_slice($this->data, 8, 4))); + $this->assertEquals(-1, PhpType::bytes2signedInt(array_slice($this->data, 12, 4))); + $this->assertEquals(-25000, PhpType::bytes2signedInt(array_slice($this->data, 16, 2))); + $this->assertEquals(25000, PhpType::bytes2signedInt(array_slice($this->data, 18, 2))); } } diff --git a/tests/PhpType/Bytes2RealTest.php b/tests/PhpType/Bytes2RealTest.php index 2fd7b80..1bb7458 100644 --- a/tests/PhpType/Bytes2RealTest.php +++ b/tests/PhpType/Bytes2RealTest.php @@ -7,7 +7,7 @@ use PHPUnit_Framework_TestCase; class Bytes2Real extends PHPUnit_Framework_TestCase { - const DATA = [ + private $data = [ 0 => 0, 1 => 0, 2 => 68, @@ -24,8 +24,8 @@ class Bytes2Real extends PHPUnit_Framework_TestCase public function testByte2Real() { - $this->assertEquals(1000, PhpType::bytes2float(array_slice(self::DATA, 0, 4))); - $this->assertEquals(2000, PhpType::bytes2float(array_slice(self::DATA, 4, 4))); - $this->assertEquals(1.25, PhpType::bytes2float(array_slice(self::DATA, 8, 4))); + $this->assertEquals(1000, PhpType::bytes2float(array_slice($this->data, 0, 4))); + $this->assertEquals(2000, PhpType::bytes2float(array_slice($this->data, 4, 4))); + $this->assertEquals(1.25, PhpType::bytes2float(array_slice($this->data, 8, 4))); } } diff --git a/tests/PhpType/Bytes2SignedIntTest.php b/tests/PhpType/Bytes2SignedIntTest.php index 362a30d..dcf4467 100644 --- a/tests/PhpType/Bytes2SignedIntTest.php +++ b/tests/PhpType/Bytes2SignedIntTest.php @@ -6,7 +6,7 @@ use PHPUnit\Framework\TestCase; class Bytes2SignedInt extends TestCase { - const DATA = [ + private $data = [ 0xFF, // -1 0xFF, 0xFF, @@ -31,12 +31,12 @@ class Bytes2SignedInt extends TestCase public function testByte2SignedInt() { - $this->assertEquals(-1, PhpType::bytes2signedInt(array_slice(self::DATA, 0, 2))); - $this->assertEquals(-1, PhpType::bytes2signedInt(array_slice(self::DATA, 0, 4))); + $this->assertEquals(-1, PhpType::bytes2signedInt(array_slice($this->data, 0, 2))); + $this->assertEquals(-1, PhpType::bytes2signedInt(array_slice($this->data, 0, 4))); - $this->assertEquals(0, PhpType::bytes2signedInt(array_slice(self::DATA, 4, 4))); - $this->assertEquals(1, PhpType::bytes2signedInt(array_slice(self::DATA, 8, 4))); - $this->assertEquals(-2147483648, PhpType::bytes2signedInt(array_slice(self::DATA, 12, 4))); - $this->assertEquals(2147483647, PhpType::bytes2signedInt(array_slice(self::DATA, 16, 4))); + $this->assertEquals(0, PhpType::bytes2signedInt(array_slice($this->data, 4, 4))); + $this->assertEquals(1, PhpType::bytes2signedInt(array_slice($this->data, 8, 4))); + $this->assertEquals(-2147483648, PhpType::bytes2signedInt(array_slice($this->data, 12, 4))); + $this->assertEquals(2147483647, PhpType::bytes2signedInt(array_slice($this->data, 16, 4))); } } diff --git a/tests/PhpType/Bytes2StringTest.php b/tests/PhpType/Bytes2StringTest.php index 8b5d788..851c331 100644 --- a/tests/PhpType/Bytes2StringTest.php +++ b/tests/PhpType/Bytes2StringTest.php @@ -6,7 +6,7 @@ use PHPUnit\Framework\TestCase; class Bytes2String extends TestCase { - const DATA = [ // String "Hello word!" + private $data = [ // String "Hello word!" 0x48, //H 0x65, //e 0x6c, //l @@ -26,7 +26,7 @@ class Bytes2String extends TestCase public function testBytesToString() { - $this->assertEquals('eHll oowlr!da', PhpType::bytes2string(self::DATA)); - $this->assertEquals('Hello world!', PhpType::bytes2string(self::DATA, true)); + $this->assertEquals('eHll oowlr!da', PhpType::bytes2string($this->data)); + $this->assertEquals('Hello world!', PhpType::bytes2string($this->data, true)); } } diff --git a/tests/PhpType/Bytes2UnSignedIntTest.php b/tests/PhpType/Bytes2UnSignedIntTest.php index 2c424fc..14e2698 100644 --- a/tests/PhpType/Bytes2UnSignedIntTest.php +++ b/tests/PhpType/Bytes2UnSignedIntTest.php @@ -6,7 +6,7 @@ use PHPUnit\Framework\TestCase; class Bytes2UnSignedIntTest extends TestCase { - const DATA = [ + private $data = [ 0xFF, // -1 0xFF, 0xFF, @@ -31,12 +31,12 @@ class Bytes2UnSignedIntTest extends TestCase public function testByte2SignedInt() { - $this->assertEquals(65535, PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 2))); - $this->assertEquals(4294967295, PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 4))); + $this->assertEquals(65535, PhpType::bytes2unsignedInt(array_slice($this->data, 0, 2))); + $this->assertEquals(4294967295, PhpType::bytes2unsignedInt(array_slice($this->data, 0, 4))); - $this->assertEquals(0, PhpType::bytes2unsignedInt(array_slice(self::DATA, 4, 4))); - $this->assertEquals(1, PhpType::bytes2unsignedInt(array_slice(self::DATA, 8, 4))); - $this->assertEquals(2147483648, PhpType::bytes2unsignedInt(array_slice(self::DATA, 12, 4))); - $this->assertEquals(2147483647, PhpType::bytes2unsignedInt(array_slice(self::DATA, 16, 4))); + $this->assertEquals(0, PhpType::bytes2unsignedInt(array_slice($this->data, 4, 4))); + $this->assertEquals(1, PhpType::bytes2unsignedInt(array_slice($this->data, 8, 4))); + $this->assertEquals(2147483648, PhpType::bytes2unsignedInt(array_slice($this->data, 12, 4))); + $this->assertEquals(2147483647, PhpType::bytes2unsignedInt(array_slice($this->data, 16, 4))); } } diff --git a/tests/PhpType/PhpTypeArrayExceptionWithTextArrayTest.php b/tests/PhpType/PhpTypeArrayExceptionWithTextArrayTest.php index 0565a13..62efe58 100644 --- a/tests/PhpType/PhpTypeArrayExceptionWithTextArrayTest.php +++ b/tests/PhpType/PhpTypeArrayExceptionWithTextArrayTest.php @@ -6,22 +6,26 @@ use PHPUnit\Framework\TestCase; class PhpTypeArrayExceptionWithTextArrayTest extends TestCase { - const DATA = [ + private $data = [ "0" => 100, // 32098 (DINT) "1" => "e", "2" => 0, "3" => 0 ]; + /** + * @expectedException \Exception + */ public function testExceptionWhenSize2ContainsString() { - $this->expectException(\Exception::class); - PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 2)); + PhpType::bytes2unsignedInt(array_slice($this->data, 0, 2)); } + /** + * @expectedException \Exception + */ public function testExceptionWhenSize4ContainsString() { - $this->expectException(\Exception::class); - PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 4)); + PhpType::bytes2unsignedInt(array_slice($this->data, 0, 4)); } } diff --git a/tests/PhpType/PhpTypeArraySizeExceptionsTest.php b/tests/PhpType/PhpTypeArraySizeExceptionsTest.php index 6003ad4..1782fc2 100644 --- a/tests/PhpType/PhpTypeArraySizeExceptionsTest.php +++ b/tests/PhpType/PhpTypeArraySizeExceptionsTest.php @@ -6,7 +6,7 @@ use PHPUnit\Framework\TestCase; class PhpTypeArraySizeExceptionsTest extends TestCase { - const DATA = [ + private $data = [ "0" => 100, // 32098 (DINT) "1" => 2, "2" => 0, @@ -15,32 +15,38 @@ class PhpTypeArraySizeExceptionsTest extends TestCase "5" => 2 ]; + /** + * @expectedException \Exception + */ public function testExceptionWhenSizeShort() { - $this->expectException(\Exception::class); - PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 1)); + PhpType::bytes2unsignedInt(array_slice($this->data, 0, 1)); } + /** + * @expectedException \Exception + */ public function testExceptionWhenSizeShort3() { - $this->expectException(\Exception::class); - PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 3)); + PhpType::bytes2unsignedInt(array_slice($this->data, 0, 3)); } + /** + * @expectedException \Exception + */ public function testExceptionWhenSizeLong() { - $this->expectException(\Exception::class); - PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 5)); + PhpType::bytes2unsignedInt(array_slice($this->data, 0, 5)); } public function testNoExceptionWhenSize2() { - $this->assertEquals(25602, PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 2))); + $this->assertEquals(25602, PhpType::bytes2unsignedInt(array_slice($this->data, 0, 2))); } public function testNoExceptionWhenSize4() { - $this->assertEquals(25602, PhpType::bytes2unsignedInt(array_slice(self::DATA, 0, 4))); + $this->assertEquals(25602, PhpType::bytes2unsignedInt(array_slice($this->data, 0, 4))); } } \ No newline at end of file