Merge branch 'mbonneau-psr7-ms-tests' into psr7-multi-streamer

This commit is contained in:
Chris Boden 2016-01-10 10:42:56 -05:00
commit fb00882664
15 changed files with 910 additions and 138 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
composer.lock
vendor
tests/ab/reports
reports

24
.travis.yml Normal file
View File

@ -0,0 +1,24 @@
language: php
php:
- 5.4
- 5.5
- 5.6
- 7
- hhvm
matrix:
allow_failures:
- php: hhvm
before_install:
- export PATH=$HOME/.local/bin:$PATH
- pip install autobahntestsuite --user `whoami`
- pip list autobahntestsuite --user `whoami`
before_script:
- composer install
- sh tests/ab/run_ab_tests.sh
script:
- phpunit

27
phpunit.xml.dist Normal file
View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
forceCoversAnnotation="true"
mapTestClassNameToCoveredClassName="true"
bootstrap="tests/bootstrap.php"
colors="true"
backupGlobals="false"
backupStaticAttributes="false"
syntaxCheck="false"
stopOnError="false"
>
<testsuites>
<testsuite name="tests">
<directory>tests</directory>
<exclude>
<directory>test/ab</directory>
</exclude>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./src/</directory>
</whitelist>
</filter>
</phpunit>

View File

@ -92,7 +92,9 @@ class RequestVerifier {
* @return bool
*/
public function verifyConnection(array $connectionHeader) {
return (1 === count($connectionHeader) && 'upgrade' === strtolower(($connectionHeader[0])));
return count(array_filter($connectionHeader, function ($x) {
return 'upgrade' === strtolower($x);
})) > 0;
}
/**

View File

@ -40,6 +40,8 @@ class Message implements \IteratorAggregate, MessageInterface {
*/
public function addFrame(FrameInterface $fragment) {
$this->_frames->push($fragment);
return $this;
}
/**

30
tests/AbResultsTest.php Normal file
View File

@ -0,0 +1,30 @@
<?php
namespace Ratchet\RFC6455\Test;
class AbResultsTest extends \PHPUnit_Framework_TestCase {
private function verifyAutobahnResults($fileName) {
if (!file_exists($fileName)) {
return $this->markTestSkipped('Autobahn TestSuite results not found');
}
$resultsJson = file_get_contents($fileName);
$results = json_decode($resultsJson);
$agentName = array_keys(get_object_vars($results))[0];
foreach ($results->$agentName as $name => $result) {
if ($result->behavior === "INFORMATIONAL") {
continue;
}
$this->assertTrue(in_array($result->behavior, ["OK", "NON-STRICT"]), "Autobahn test case " . $name . " in " . $fileName);
}
}
public function testAutobahnClientResults() {
$this->verifyAutobahnResults(__DIR__ . '/ab/reports/clients/index.json');
}
public function testAutobahnServerResults() {
$this->verifyAutobahnResults(__DIR__ . '/ab/reports/servers/index.json');
}
}

View File

@ -1,67 +0,0 @@
<?php
class AbConnectionContext implements Ratchet\RFC6455\Messaging\Streaming\ContextInterface {
private $_frame;
private $_message;
protected $maskPayload;
/**
* @var \React\Stream\Stream
*/
protected $_conn;
public function __construct(\React\Stream\Stream $connectionContext, $maskPayload = false) {
$this->_conn = $connectionContext;
$this->maskPayload = $maskPayload;
}
public function setFrame(\Ratchet\RFC6455\Messaging\Protocol\FrameInterface $frame = null) {
$this->_frame = $frame;
}
public function getFrame() {
return $this->_frame;
}
public function setMessage(\Ratchet\RFC6455\Messaging\Protocol\MessageInterface $message = null) {
$this->_message = $message;
}
public function getMessage() {
return $this->_message;
}
public function onMessage(\Ratchet\RFC6455\Messaging\Protocol\MessageInterface $msg) {
$frame = new \Ratchet\RFC6455\Messaging\Protocol\Frame($msg->getPayload(), true, $msg[0]->getOpcode());
if ($this->maskPayload) {
$frame->maskPayload();
}
$this->_conn->write($frame->getContents());
}
public function onPing(\Ratchet\RFC6455\Messaging\Protocol\FrameInterface $frame) {
$pong = new \Ratchet\RFC6455\Messaging\Protocol\Frame($frame->getPayload(), true, \Ratchet\RFC6455\Messaging\Protocol\Frame::OP_PONG);
if ($this->maskPayload) {
$pong->maskPayload();
}
$this->_conn->write($pong->getContents());
}
public function onPong(\Ratchet\RFC6455\Messaging\Protocol\FrameInterface $msg) {
// TODO: Implement onPong() method.
}
public function onClose($code = 1000) {
$frame = new \Ratchet\RFC6455\Messaging\Protocol\Frame(
pack('n', $code),
true,
\Ratchet\RFC6455\Messaging\Protocol\Frame::OP_CLOSE
);
if ($this->maskPayload) {
$frame->maskPayload();
}
$this->_conn->end($frame->getContents());
}
}

View File

@ -4,34 +4,11 @@ use Ratchet\RFC6455\Messaging\Protocol\Frame;
use Ratchet\RFC6455\Messaging\Protocol\Message;
require __DIR__ . '/../bootstrap.php';
require __DIR__ . '/AbConnectionContext.php';
define('AGENT', 'RatchetRFC/0.0.0');
$testServer = "127.0.0.1";
class EmConnectionContext extends AbConnectionContext implements \Evenement\EventEmitterInterface, Ratchet\RFC6455\Messaging\Streaming\ContextInterface {
use \Evenement\EventEmitterTrait;
public function onMessage(\Ratchet\RFC6455\Messaging\Protocol\MessageInterface $msg) {
$this->emit('message', [$msg]);
}
public function sendMessage(Frame $frame) {
if ($this->maskPayload) {
$frame->maskPayload();
}
$this->_conn->write($frame->getContents());
}
public function close($closeCode = Frame::CLOSE_NORMAL) {
$closeFrame = new Frame(pack('n', $closeCode), true, Frame::OP_CLOSE);
$closeFrame->maskPayload();
$this->_conn->end($closeFrame->getContents());
}
}
$loop = React\EventLoop\Factory::create();
$dnsResolverFactory = new React\Dns\Resolver\Factory();
@ -39,6 +16,32 @@ $dnsResolver = $dnsResolverFactory->createCached('8.8.8.8', $loop);
$factory = new \React\SocketClient\Connector($loop, $dnsResolver);
function echoStreamerFactory($conn)
{
return new \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer(
new \Ratchet\RFC6455\Encoding\Validator,
new \Ratchet\RFC6455\Messaging\Protocol\CloseFrameChecker,
function (\Ratchet\RFC6455\Messaging\Protocol\MessageInterface $msg) use ($conn) {
/** @var Frame $frame */
foreach ($msg as $frame) {
$frame->maskPayload();
}
$conn->write($msg->getContents());
},
function (\Ratchet\RFC6455\Messaging\Protocol\FrameInterface $frame) use ($conn) {
switch ($frame->getOpcode()) {
case Frame::OP_PING:
return $conn->write((new Frame($frame->getPayload(), true, Frame::OP_PONG))->maskPayload()->getContents());
break;
case Frame::OP_CLOSE:
return $conn->end((new Frame($frame->getPayload(), true, Frame::OP_CLOSE))->maskPayload()->getContents());
break;
}
},
false
);
}
function getTestCases() {
global $factory;
global $testServer;
@ -52,12 +55,10 @@ function getTestCases() {
$rawResponse = "";
$response = null;
$ms = new \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer(new \Ratchet\RFC6455\Encoding\Validator(), true);
/** @var \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer $ms */
$ms = null;
/** @var EmConnectionContext $context */
$context = null;
$stream->on('data', function ($data) use ($stream, &$rawResponse, &$response, $ms, $cn, $deferred, &$context) {
$stream->on('data', function ($data) use ($stream, &$rawResponse, &$response, &$ms, $cn, $deferred, &$context) {
if ($response === null) {
$rawResponse .= $data;
$pos = strpos($rawResponse, "\r\n\r\n");
@ -70,19 +71,23 @@ function getTestCases() {
$stream->end();
$deferred->reject();
} else {
$context = new EmConnectionContext($stream, true);
$context->on('message', function (Message $msg) use ($stream, $deferred, $context) {
$ms = new \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer(
new \Ratchet\RFC6455\Encoding\Validator,
new \Ratchet\RFC6455\Messaging\Protocol\CloseFrameChecker,
function (\Ratchet\RFC6455\Messaging\Protocol\MessageInterface $msg) use ($deferred, $stream) {
$deferred->resolve($msg->getPayload());
$context->close();
});
$stream->close();
},
null,
false
);
}
}
}
// feed the message streamer
if ($response && $context) {
$ms->onData($data, $context);
if ($ms) {
$ms->onData($data);
}
});
@ -108,12 +113,9 @@ function runTest($case)
$rawResponse = "";
$response = null;
$ms = new \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer(new \Ratchet\RFC6455\Encoding\Validator(), true);
$ms = null;
/** @var AbConnectionContext $context */
$context = null;
$stream->on('data', function ($data) use ($stream, &$rawResponse, &$response, $ms, $cn, $deferred, &$context) {
$stream->on('data', function ($data) use ($stream, &$rawResponse, &$response, &$ms, $cn, $deferred, &$context) {
if ($response === null) {
$rawResponse .= $data;
$pos = strpos($rawResponse, "\r\n\r\n");
@ -126,14 +128,14 @@ function runTest($case)
$stream->end();
$deferred->reject();
} else {
$context = new AbConnectionContext($stream, true);
$ms = echoStreamerFactory($stream);
}
}
}
// feed the message streamer
if ($response && $context) {
$ms->onData($data, $context);
if ($ms) {
$ms->onData($data);
}
});
@ -154,18 +156,17 @@ function createReport() {
$deferred = new Deferred();
$factory->create($testServer, 9001)->then(function (\React\Stream\Stream $stream) use ($deferred) {
$cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator('/updateReports?agent=' . AGENT);
$reportPath = "/updateReports?agent=" . AGENT . "&shutdownOnComplete=true";
$cn = new \Ratchet\RFC6455\Handshake\ClientNegotiator($reportPath);
$cnRequest = $cn->getRequest();
$rawResponse = "";
$response = null;
$ms = new \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer(new \Ratchet\RFC6455\Encoding\Validator(), true);
/** @var \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer $ms */
$ms = null;
/** @var EmConnectionContext $context */
$context = null;
$stream->on('data', function ($data) use ($stream, &$rawResponse, &$response, $ms, $cn, $deferred, &$context) {
$stream->on('data', function ($data) use ($stream, &$rawResponse, &$response, &$ms, $cn, $deferred, &$context) {
if ($response === null) {
$rawResponse .= $data;
$pos = strpos($rawResponse, "\r\n\r\n");
@ -178,19 +179,23 @@ function createReport() {
$stream->end();
$deferred->reject();
} else {
$context = new EmConnectionContext($stream, true);
$context->on('message', function (Message $msg) use ($stream, $deferred, $context) {
$ms = new \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer(
new \Ratchet\RFC6455\Encoding\Validator,
new \Ratchet\RFC6455\Messaging\Protocol\CloseFrameChecker,
function (\Ratchet\RFC6455\Messaging\Protocol\MessageInterface $msg) use ($deferred, $stream) {
$deferred->resolve($msg->getPayload());
$context->close();
});
$stream->close();
},
null,
false
);
}
}
}
// feed the message streamer
if ($response && $context) {
$ms->onData($data, $context);
if ($ms) {
$ms->onData($data);
}
});
@ -204,8 +209,6 @@ function createReport() {
$testPromises = [];
getTestCases()->then(function ($count) use ($loop) {
echo "Running " . $count . " test cases.\n";
$allDeferred = new Deferred();
$runNextCase = function () use (&$i, &$runNextCase, $count, $allDeferred) {
@ -214,7 +217,6 @@ getTestCases()->then(function ($count) use ($loop) {
$allDeferred->resolve();
return;
}
echo "Running " . $i . "\n";
runTest($i)->then($runNextCase);
};
@ -222,7 +224,6 @@ getTestCases()->then(function ($count) use ($loop) {
$runNextCase();
$allDeferred->promise()->then(function () {
echo "Generating report...\n";
createReport();
});
});

View File

@ -2,11 +2,11 @@
"options": {"failByDrop": false},
"outdir": "./reports/servers",
"servers": [
{"agent": "AutobahnServer",
"servers": [{
"agent": "RatchetRFC/0.1.0",
"url": "ws://localhost:9001",
"options": {"version": 18}}
],
"options": {"version": 18}
}],
"cases": ["*"],
"exclude-cases": ["12.*","13.*"],
"exclude-agent-cases": {}

14
tests/ab/run_ab_tests.sh Normal file
View File

@ -0,0 +1,14 @@
cd tests/ab
wstest -m fuzzingserver -s fuzzingserver.json &
sleep 5
php clientRunner.php
sleep 2
php startServer.php &
sleep 3
wstest -m fuzzingclient -s fuzzingclient.json
sleep 2
killall php wstest

View File

@ -6,6 +6,7 @@ use Ratchet\RFC6455\Messaging\Protocol\Frame;
require_once __DIR__ . "/../bootstrap.php";
$loop = \React\EventLoop\Factory::create();
$socket = new \React\Socket\Server($loop);
$server = new \React\Http\Server($socket);

View File

@ -4,15 +4,16 @@
* Find the auto loader file
*/
$files = [
__DIR__ . '/../../../../vendor/autoload.php',
__DIR__ . '/../../../vendor/autoload.php',
__DIR__ . '/../../vendor/autoload.php',
__DIR__ . '/../vendor/autoload.php',
__DIR__ . '/../../vendor/autoload.php',
__DIR__ . '/../../../vendor/autoload.php',
__DIR__ . '/../../../../vendor/autoload.php',
];
foreach ($files as $file) {
if (file_exists($file)) {
require $file;
$loader = require $file;
$loader->addPsr4('Ratchet\\RFC6455\\Test\\', __DIR__);
break;
}
}

View File

@ -0,0 +1,174 @@
<?php
namespace Ratchet\RFC6455\Test\Unit\Handshake;
use Ratchet\RFC6455\Handshake\RequestVerifier;
/**
* covers Ratchet\WebSocket\Version\RFC6455\HandshakeVerifier
*/
class RequestVerifierTest extends \PHPUnit_Framework_TestCase {
/**
* @var RequestVerifier
*/
protected $_v;
public function setUp() {
$this->_v = new RequestVerifier();
}
public static function methodProvider() {
return array(
array(true, 'GET'),
array(true, 'get'),
array(true, 'Get'),
array(false, 'POST'),
array(false, 'DELETE'),
array(false, 'PUT'),
array(false, 'PATCH')
);
}
/**
* @dataProvider methodProvider
*/
public function testMethodMustBeGet($result, $in) {
$this->assertEquals($result, $this->_v->verifyMethod($in));
}
public static function httpVersionProvider() {
return array(
array(true, 1.1),
array(true, '1.1'),
array(true, 1.2),
array(true, '1.2'),
array(true, 2),
array(true, '2'),
array(true, '2.0'),
array(false, '1.0'),
array(false, 1),
array(false, '0.9'),
array(false, ''),
array(false, 'hello')
);
}
/**
* @dataProvider httpVersionProvider
*/
public function testHttpVersionIsAtLeast1Point1($expected, $in) {
$this->assertEquals($expected, $this->_v->verifyHTTPVersion($in));
}
public static function uRIProvider() {
return array(
array(true, '/chat'),
array(true, '/hello/world?key=val'),
array(false, '/chat#bad'),
array(false, 'nope'),
array(false, '/ ಠ_ಠ '),
array(false, '/✖')
);
}
/**
* @dataProvider URIProvider
*/
public function testRequestUri($expected, $in) {
$this->assertEquals($expected, $this->_v->verifyRequestURI($in));
}
public static function hostProvider() {
return array(
array(true, ['server.example.com']),
array(false, [])
);
}
/**
* @dataProvider HostProvider
*/
public function testVerifyHostIsSet($expected, $in) {
$this->assertEquals($expected, $this->_v->verifyHost($in));
}
public static function upgradeProvider() {
return array(
array(true, ['websocket']),
array(true, ['Websocket']),
array(true, ['webSocket']),
array(false, []),
array(false, [''])
);
}
/**
* @dataProvider upgradeProvider
*/
public function testVerifyUpgradeIsWebSocket($expected, $val) {
$this->assertEquals($expected, $this->_v->verifyUpgradeRequest($val));
}
public static function connectionProvider() {
return array(
array(true, ['Upgrade']),
array(true, ['upgrade']),
array(true, ['keep-alive', 'Upgrade']),
array(true, ['Upgrade', 'keep-alive']),
array(true, ['keep-alive', 'Upgrade', 'something']),
array(false, ['']),
array(false, [])
);
}
/**
* @dataProvider connectionProvider
*/
public function testConnectionHeaderVerification($expected, $val) {
$this->assertEquals($expected, $this->_v->verifyConnection($val));
}
public static function keyProvider() {
return array(
array(true, ['hkfa1L7uwN6DCo4IS3iWAw==']),
array(true, ['765vVoQpKSGJwPzJIMM2GA==']),
array(true, ['AQIDBAUGBwgJCgsMDQ4PEC==']),
array(true, ['axa2B/Yz2CdpfQAY2Q5P7w==']),
array(false, [0]),
array(false, ['Hello World']),
array(false, ['1234567890123456']),
array(false, ['123456789012345678901234']),
array(true, [base64_encode('UTF8allthngs+✓')]),
array(true, ['dGhlIHNhbXBsZSBub25jZQ==']),
array(false, []),
array(false, ['dGhlIHNhbXBsZSBub25jZQ==', 'Some other value']),
array(false, ['Some other value', 'dGhlIHNhbXBsZSBub25jZQ=='])
);
}
/**
* @dataProvider keyProvider
*/
public function testKeyIsBase64Encoded16BitNonce($expected, $val) {
$this->assertEquals($expected, $this->_v->verifyKey($val));
}
public static function versionProvider() {
return array(
array(true, [13]),
array(true, ['13']),
array(false, [12]),
array(false, [14]),
array(false, ['14']),
array(false, ['hi']),
array(false, ['']),
array(false, [])
);
}
/**
* @dataProvider versionProvider
*/
public function testVersionEquals13($expected, $in) {
$this->assertEquals($expected, $this->_v->verifyVersion($in));
}
}

View File

@ -0,0 +1,502 @@
<?php
namespace Ratchet\RFC6455\Test\Unit\Messaging\Protocol;
use Ratchet\RFC6455\Messaging\Protocol\Frame;
/**
* @todo getMaskingKey, getPayloadStartingByte don't have tests yet
* @todo Could use some clean up in general, I had to rush to fix a bug for a deadline, sorry.
*/
class FrameTest extends \PHPUnit_Framework_TestCase {
protected $_firstByteFinText = '10000001';
protected $_secondByteMaskedSPL = '11111101';
/** @var Frame */
protected $_frame;
protected $_packer;
public function setUp() {
$this->_frame = new Frame;
}
/**
* Encode the fake binary string to send over the wire
* @param string of 1's and 0's
* @return string
*/
public static function encode($in) {
if (strlen($in) > 8) {
$out = '';
while (strlen($in) >= 8) {
$out .= static::encode(substr($in, 0, 8));
$in = substr($in, 8);
}
return $out;
}
return chr(bindec($in));
}
/**
* This is a data provider
* param string The UTF8 message
* param string The WebSocket framed message, then base64_encoded
*/
public static function UnframeMessageProvider() {
return array(
array('Hello World!', 'gYydAIfa1WXrtvIg0LXvbOP7'),
array('!@#$%^&*()-=_+[]{}\|/.,<>`~', 'gZv+h96r38f9j9vZ+IHWrvOWoayF9oX6gtfRqfKXwOeg'),
array('ಠ_ಠ', 'gYfnSpu5B/g75gf4Ow=='),
array(
"The quick brown fox jumps over the lazy dog. All work and no play makes Chris a dull boy. I'm trying to get past 128 characters for a unit test here...",
'gf4Amahb14P8M7Kj2S6+4MN7tfHHLLmjzjSvo8IuuvPbe7j1zSn398A+9+/JIa6jzDSwrYh7lu/Ee6Ds2jD34sY/9+3He6fvySL37skwsvCIGL/xwSj34og/ou/Ee7Xs0XX3o+F8uqPcKa7qxjz398d7sObce6fi2y/3sppj9+DAOqXiyy+y8dt7sezae7aj3TW+94gvsvDce7/m2j75rYY='
)
);
}
public static function underflowProvider() {
return array(
array('isFinal', ''),
array('getRsv1', ''),
array('getRsv2', ''),
array('getRsv3', ''),
array('getOpcode', ''),
array('isMasked', '10000001'),
array('getPayloadLength', '10000001'),
array('getPayloadLength', '1000000111111110'),
array('getMaskingKey', '1000000110000111'),
array('getPayload', '100000011000000100011100101010101001100111110100')
);
}
/**
* @dataProvider underflowProvider
*
* covers Ratchet\WebSocket\Version\RFC6455\Frame::isFinal
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getRsv1
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getRsv2
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getRsv3
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getOpcode
* covers Ratchet\WebSocket\Version\RFC6455\Frame::isMasked
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayloadLength
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getMaskingKey
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayload
*/
public function testUnderflowExceptionFromAllTheMethodsMimickingBuffering($method, $bin) {
$this->setExpectedException('\UnderflowException');
if (!empty($bin)) {
$this->_frame->addBuffer(static::encode($bin));
}
call_user_func(array($this->_frame, $method));
}
/**
* A data provider for testing the first byte of a WebSocket frame
* param bool Given, is the byte indicate this is the final frame
* param int Given, what is the expected opcode
* param string of 0|1 Each character represents a bit in the byte
*/
public static function firstByteProvider() {
return array(
array(false, false, false, true, 8, '00011000'),
array(true, false, true, false, 10, '10101010'),
array(false, false, false, false, 15, '00001111'),
array(true, false, false, false, 1, '10000001'),
array(true, true, true, true, 15, '11111111'),
array(true, true, false, false, 7, '11000111')
);
}
/**
* @dataProvider firstByteProvider
* covers Ratchet\WebSocket\Version\RFC6455\Frame::isFinal
*/
public function testFinCodeFromBits($fin, $rsv1, $rsv2, $rsv3, $opcode, $bin) {
$this->_frame->addBuffer(static::encode($bin));
$this->assertEquals($fin, $this->_frame->isFinal());
}
/**
* @dataProvider firstByteProvider
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getRsv1
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getRsv2
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getRsv3
*/
public function testGetRsvFromBits($fin, $rsv1, $rsv2, $rsv3, $opcode, $bin) {
$this->_frame->addBuffer(static::encode($bin));
$this->assertEquals($rsv1, $this->_frame->getRsv1());
$this->assertEquals($rsv2, $this->_frame->getRsv2());
$this->assertEquals($rsv3, $this->_frame->getRsv3());
}
/**
* @dataProvider firstByteProvider
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getOpcode
*/
public function testOpcodeFromBits($fin, $rsv1, $rsv2, $rsv3, $opcode, $bin) {
$this->_frame->addBuffer(static::encode($bin));
$this->assertEquals($opcode, $this->_frame->getOpcode());
}
/**
* @dataProvider UnframeMessageProvider
* covers Ratchet\WebSocket\Version\RFC6455\Frame::isFinal
*/
public function testFinCodeFromFullMessage($msg, $encoded) {
$this->_frame->addBuffer(base64_decode($encoded));
$this->assertTrue($this->_frame->isFinal());
}
/**
* @dataProvider UnframeMessageProvider
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getOpcode
*/
public function testOpcodeFromFullMessage($msg, $encoded) {
$this->_frame->addBuffer(base64_decode($encoded));
$this->assertEquals(1, $this->_frame->getOpcode());
}
public static function payloadLengthDescriptionProvider() {
return array(
array(7, '01110101'),
array(7, '01111101'),
array(23, '01111110'),
array(71, '01111111'),
array(7, '00000000'), // Should this throw an exception? Can a payload be empty?
array(7, '00000001')
);
}
/**
* @dataProvider payloadLengthDescriptionProvider
* covers Ratchet\WebSocket\Version\RFC6455\Frame::addBuffer
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getFirstPayloadVal
*/
public function testFirstPayloadDesignationValue($bits, $bin) {
$this->_frame->addBuffer(static::encode($this->_firstByteFinText));
$this->_frame->addBuffer(static::encode($bin));
$ref = new \ReflectionClass($this->_frame);
$cb = $ref->getMethod('getFirstPayloadVal');
$cb->setAccessible(true);
$this->assertEquals(bindec($bin), $cb->invoke($this->_frame));
}
/**
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getFirstPayloadVal
*/
public function testFirstPayloadValUnderflow() {
$ref = new \ReflectionClass($this->_frame);
$cb = $ref->getMethod('getFirstPayloadVal');
$cb->setAccessible(true);
$this->setExpectedException('UnderflowException');
$cb->invoke($this->_frame);
}
/**
* @dataProvider payloadLengthDescriptionProvider
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getNumPayloadBits
*/
public function testDetermineHowManyBitsAreUsedToDescribePayload($expected_bits, $bin) {
$this->_frame->addBuffer(static::encode($this->_firstByteFinText));
$this->_frame->addBuffer(static::encode($bin));
$ref = new \ReflectionClass($this->_frame);
$cb = $ref->getMethod('getNumPayloadBits');
$cb->setAccessible(true);
$this->assertEquals($expected_bits, $cb->invoke($this->_frame));
}
/**
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getNumPayloadBits
*/
public function testgetNumPayloadBitsUnderflow() {
$ref = new \ReflectionClass($this->_frame);
$cb = $ref->getMethod('getNumPayloadBits');
$cb->setAccessible(true);
$this->setExpectedException('UnderflowException');
$cb->invoke($this->_frame);
}
public function secondByteProvider() {
return array(
array(true, 1, '10000001'),
array(false, 1, '00000001'),
array(true, 125, $this->_secondByteMaskedSPL)
);
}
/**
* @dataProvider secondByteProvider
* covers Ratchet\WebSocket\Version\RFC6455\Frame::isMasked
*/
public function testIsMaskedReturnsExpectedValue($masked, $payload_length, $bin) {
$this->_frame->addBuffer(static::encode($this->_firstByteFinText));
$this->_frame->addBuffer(static::encode($bin));
$this->assertEquals($masked, $this->_frame->isMasked());
}
/**
* @dataProvider UnframeMessageProvider
* covers Ratchet\WebSocket\Version\RFC6455\Frame::isMasked
*/
public function testIsMaskedFromFullMessage($msg, $encoded) {
$this->_frame->addBuffer(base64_decode($encoded));
$this->assertTrue($this->_frame->isMasked());
}
/**
* @dataProvider secondByteProvider
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayloadLength
*/
public function testGetPayloadLengthWhenOnlyFirstFrameIsUsed($masked, $payload_length, $bin) {
$this->_frame->addBuffer(static::encode($this->_firstByteFinText));
$this->_frame->addBuffer(static::encode($bin));
$this->assertEquals($payload_length, $this->_frame->getPayloadLength());
}
/**
* @dataProvider UnframeMessageProvider
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayloadLength
* @todo Not yet testing when second additional payload length descriptor
*/
public function testGetPayloadLengthFromFullMessage($msg, $encoded) {
$this->_frame->addBuffer(base64_decode($encoded));
$this->assertEquals(strlen($msg), $this->_frame->getPayloadLength());
}
public function maskingKeyProvider() {
$frame = new Frame;
return array(
array($frame->generateMaskingKey()),
array($frame->generateMaskingKey()),
array($frame->generateMaskingKey())
);
}
/**
* @dataProvider maskingKeyProvider
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getMaskingKey
* @todo I I wrote the dataProvider incorrectly, skipping for now
*/
public function testGetMaskingKey($mask) {
$this->_frame->addBuffer(static::encode($this->_firstByteFinText));
$this->_frame->addBuffer(static::encode($this->_secondByteMaskedSPL));
$this->_frame->addBuffer($mask);
$this->assertEquals($mask, $this->_frame->getMaskingKey());
}
/**
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getMaskingKey
*/
public function testGetMaskingKeyOnUnmaskedPayload() {
$frame = new Frame('Hello World!');
$this->assertEquals('', $frame->getMaskingKey());
}
/**
* @dataProvider UnframeMessageProvider
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayload
* @todo Move this test to bottom as it requires all methods of the class
*/
public function testUnframeFullMessage($unframed, $base_framed) {
$this->_frame->addBuffer(base64_decode($base_framed));
$this->assertEquals($unframed, $this->_frame->getPayload());
}
public static function messageFragmentProvider() {
return array(
array(false, '', '', '', '', '')
);
}
/**
* @dataProvider UnframeMessageProvider
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayload
*/
public function testCheckPiecingTogetherMessage($msg, $encoded) {
$framed = base64_decode($encoded);
for ($i = 0, $len = strlen($framed);$i < $len; $i++) {
$this->_frame->addBuffer(substr($framed, $i, 1));
}
$this->assertEquals($msg, $this->_frame->getPayload());
}
/**
* covers Ratchet\WebSocket\Version\RFC6455\Frame::__construct
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayloadLength
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayload
*/
public function testLongCreate() {
$len = 65525;
$pl = $this->generateRandomString($len);
$frame = new Frame($pl, true, Frame::OP_PING);
$this->assertTrue($frame->isFinal());
$this->assertEquals(Frame::OP_PING, $frame->getOpcode());
$this->assertFalse($frame->isMasked());
$this->assertEquals($len, $frame->getPayloadLength());
$this->assertEquals($pl, $frame->getPayload());
}
/**
* covers Ratchet\WebSocket\Version\RFC6455\Frame::__construct
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayloadLength
*/
public function testReallyLongCreate() {
$len = 65575;
$frame = new Frame($this->generateRandomString($len));
$this->assertEquals($len, $frame->getPayloadLength());
}
/**
* covers Ratchet\WebSocket\Version\RFC6455\Frame::__construct
* covers Ratchet\WebSocket\Version\RFC6455\Frame::extractOverflow
*/
public function testExtractOverflow() {
$string1 = $this->generateRandomString();
$frame1 = new Frame($string1);
$string2 = $this->generateRandomString();
$frame2 = new Frame($string2);
$cat = new Frame;
$cat->addBuffer($frame1->getContents() . $frame2->getContents());
$this->assertEquals($frame1->getContents(), $cat->getContents());
$this->assertEquals($string1, $cat->getPayload());
$uncat = new Frame;
$uncat->addBuffer($cat->extractOverflow());
$this->assertEquals($string1, $cat->getPayload());
$this->assertEquals($string2, $uncat->getPayload());
}
/**
* covers Ratchet\WebSocket\Version\RFC6455\Frame::extractOverflow
*/
public function testEmptyExtractOverflow() {
$string = $this->generateRandomString();
$frame = new Frame($string);
$this->assertEquals($string, $frame->getPayload());
$this->assertEquals('', $frame->extractOverflow());
$this->assertEquals($string, $frame->getPayload());
}
/**
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getContents
*/
public function testGetContents() {
$msg = 'The quick brown fox jumps over the lazy dog.';
$frame1 = new Frame($msg);
$frame2 = new Frame($msg);
$frame2->maskPayload();
$this->assertNotEquals($frame1->getContents(), $frame2->getContents());
$this->assertEquals(strlen($frame1->getContents()) + 4, strlen($frame2->getContents()));
}
/**
* covers Ratchet\WebSocket\Version\RFC6455\Frame::maskPayload
*/
public function testMasking() {
$msg = 'The quick brown fox jumps over the lazy dog.';
$frame = new Frame($msg);
$frame->maskPayload();
$this->assertTrue($frame->isMasked());
$this->assertEquals($msg, $frame->getPayload());
}
/**
* covers Ratchet\WebSocket\Version\RFC6455\Frame::unMaskPayload
*/
public function testUnMaskPayload() {
$string = $this->generateRandomString();
$frame = new Frame($string);
$frame->maskPayload()->unMaskPayload();
$this->assertFalse($frame->isMasked());
$this->assertEquals($string, $frame->getPayload());
}
/**
* covers Ratchet\WebSocket\Version\RFC6455\Frame::generateMaskingKey
*/
public function testGenerateMaskingKey() {
$dupe = false;
$done = array();
for ($i = 0; $i < 10; $i++) {
$new = $this->_frame->generateMaskingKey();
if (in_array($new, $done)) {
$dupe = true;
}
$done[] = $new;
}
$this->assertEquals(4, strlen($new));
$this->assertFalse($dupe);
}
/**
* covers Ratchet\WebSocket\Version\RFC6455\Frame::maskPayload
*/
public function testGivenMaskIsValid() {
$this->setExpectedException('InvalidArgumentException');
$this->_frame->maskPayload('hello world');
}
/**
* covers Ratchet\WebSocket\Version\RFC6455\Frame::maskPayload
*/
public function testGivenMaskIsValidAscii() {
if (!extension_loaded('mbstring')) {
$this->markTestSkipped("mbstring required for this test");
return;
}
$this->setExpectedException('OutOfBoundsException');
$this->_frame->maskPayload('x✖');
}
protected function generateRandomString($length = 10, $addSpaces = true, $addNumbers = true) {
$characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$%&/()=[]{}'; // ยง
$useChars = array();
for($i = 0; $i < $length; $i++) {
$useChars[] = $characters[mt_rand(0, strlen($characters) - 1)];
}
if($addSpaces === true) {
array_push($useChars, ' ', ' ', ' ', ' ', ' ', ' ');
}
if($addNumbers === true) {
array_push($useChars, rand(0, 9), rand(0, 9), rand(0, 9));
}
shuffle($useChars);
$randomString = trim(implode('', $useChars));
$randomString = substr($randomString, 0, $length);
return $randomString;
}
/**
* There was a frame boundary issue when the first 3 bytes of a frame with a payload greater than
* 126 was added to the frame buffer and then Frame::getPayloadLength was called. It would cause the frame
* to set the payload length to 126 and then not recalculate it once the full length information was available.
*
* This is fixed by setting the defPayLen back to -1 before the underflow exception is thrown.
*
* covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayloadLength
* covers Ratchet\WebSocket\Version\RFC6455\Frame::extractOverflow
*/
public function testFrameDeliveredOneByteAtATime() {
$startHeader = "\x01\x7e\x01\x00"; // header for a text frame of 256 - non-final
$framePayload = str_repeat("*", 256);
$rawOverflow = "xyz";
$rawFrame = $startHeader . $framePayload . $rawOverflow;
$frame = new Frame();
$payloadLen = 256;
for ($i = 0; $i < strlen($rawFrame); $i++) {
$frame->addBuffer($rawFrame[$i]);
try {
// payloadLen will
$payloadLen = $frame->getPayloadLength();
} catch (\UnderflowException $e) {
if ($i > 2) { // we should get an underflow on 0,1,2
$this->fail("Underflow exception when the frame length should be available");
}
}
if ($payloadLen !== 256) {
$this->fail("Payload length of " . $payloadLen . " should have been 256.");
}
}
// make sure the overflow is good
$this->assertEquals($rawOverflow, $frame->extractOverflow());
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace Ratchet\RFC6455\Test\Unit\Messaging\Protocol;
use Ratchet\RFC6455\Messaging\Protocol\Frame;
use Ratchet\RFC6455\Messaging\Protocol\Message;
/**
* covers Ratchet\WebSocket\Version\RFC6455\Message
*/
class MessageTest extends \PHPUnit_Framework_TestCase {
/** @var Message */
protected $message;
public function setUp() {
$this->message = new Message;
}
public function testNoFrames() {
$this->assertFalse($this->message->isCoalesced());
}
public function testNoFramesOpCode() {
$this->setExpectedException('UnderflowException');
$this->message->getOpCode();
}
public function testFragmentationPayload() {
$a = 'Hello ';
$b = 'World!';
$f1 = new Frame($a, false);
$f2 = new Frame($b, true, Frame::OP_CONTINUE);
$this->message->addFrame($f1)->addFrame($f2);
$this->assertEquals(strlen($a . $b), $this->message->getPayloadLength());
$this->assertEquals($a . $b, $this->message->getPayload());
}
public function testUnbufferedFragment() {
$this->message->addFrame(new Frame('The quick brow', false));
$this->setExpectedException('UnderflowException');
$this->message->getPayload();
}
public function testGetOpCode() {
$this->message
->addFrame(new Frame('The quick brow', false, Frame::OP_TEXT))
->addFrame(new Frame('n fox jumps ov', false, Frame::OP_CONTINUE))
->addFrame(new Frame('er the lazy dog', true, Frame::OP_CONTINUE))
;
$this->assertEquals(Frame::OP_TEXT, $this->message->getOpCode());
}
public function testGetUnBufferedPayloadLength() {
$this->message
->addFrame(new Frame('The quick brow', false, Frame::OP_TEXT))
->addFrame(new Frame('n fox jumps ov', false, Frame::OP_CONTINUE))
;
$this->assertEquals(28, $this->message->getPayloadLength());
}
}