From 0c86b11bd1816603ded2c0003b8168f7934ee430 Mon Sep 17 00:00:00 2001 From: khelle Date: Sun, 25 Jun 2017 12:03:46 +0200 Subject: [PATCH] Added tests --- test/Callback.php | 9 + test/TModule.php | 160 +++++ test/TModule/ChannelCompositeTest.php | 642 +++++++++++++++++++ test/TModule/ChannelModelTest.php | 765 +++++++++++++++++++++++ test/TModule/ChannelTest.php | 645 +++++++++++++++++++ test/TUnit.php | 211 +++++++ test/TUnit/ZmqDealerTest.php | 201 ++++++ test/_Simulation/Event.php | 42 ++ test/_Simulation/EventCollection.php | 8 + test/_Simulation/Simulation.php | 256 ++++++++ test/_Simulation/SimulationInterface.php | 55 ++ test/bootstrap.php | 11 + 12 files changed, 3005 insertions(+) create mode 100644 test/Callback.php create mode 100644 test/TModule.php create mode 100644 test/TModule/ChannelCompositeTest.php create mode 100644 test/TModule/ChannelModelTest.php create mode 100644 test/TModule/ChannelTest.php create mode 100644 test/TUnit.php create mode 100644 test/TUnit/ZmqDealerTest.php create mode 100644 test/_Simulation/Event.php create mode 100644 test/_Simulation/EventCollection.php create mode 100644 test/_Simulation/Simulation.php create mode 100644 test/_Simulation/SimulationInterface.php create mode 100644 test/bootstrap.php diff --git a/test/Callback.php b/test/Callback.php new file mode 100644 index 0000000..d400867 --- /dev/null +++ b/test/Callback.php @@ -0,0 +1,9 @@ +loop = null; + $this->sim = null; + } + + /** + * + */ + public function tearDown() + { + unset($this->sim); + unset($this->loop); + } + + /** + * @return LoopInterface|null + */ + public function getLoop() + { + return $this->loop; + } + + /** + * Run test scenario as simulation. + * + * @param callable(Simulation) $scenario + * @return TModule + */ + public function simulate(callable $scenario) + { + try + { + $this->loop = new Loop(new SelectLoop); + $this->loop->erase(true); + + $this->sim = new Simulation($this->loop); + $this->sim->setScenario($scenario); + $this->sim->begin(); + } + catch (Exception $ex) + { + $this->fail($ex->getMessage()); + } + + return $this; + } + + /** + * @param $events + * @param int $flags + * @return TModule + */ + public function expect($events, $flags = Simulation::EVENTS_COMPARE_IN_ORDER) + { + $expectedEvents = []; + + foreach ($events as $event) + { + $data = isset($event[1]) ? $event[1] : []; + $expectedEvents[] = new Event($event[0], $data); + } + + $this->assertEvents( + $this->sim->getExpectations(), + $expectedEvents, + $flags + ); + + return $this; + } + + /** + * @param Event[] $actualEvents + * @param Event[] $expectedEvents + * @param int $flags + */ + public function assertEvents($actualEvents = [], $expectedEvents = [], $flags = Simulation::EVENTS_COMPARE_IN_ORDER) + { + $count = max(count($actualEvents), count($expectedEvents)); + + if ($flags === Simulation::EVENTS_COMPARE_RANDOMLY) + { + sort($actualEvents); + sort($expectedEvents); + } + + for ($i=0; $i<$count; $i++) + { + if (!isset($actualEvents[$i])) + { + $this->fail( + sprintf(self::MSG_EVENT_GET_ASSERTION_FAILED, $i, $expectedEvents[$i]->name(), 'null') + ); + } + else if (!isset($expectedEvents[$i])) + { + $this->fail( + sprintf(self::MSG_EVENT_GET_ASSERTION_FAILED, $i, 'null', $actualEvents[$i]->name()) + ); + } + + $actualEvent = $actualEvents[$i]; + $expectedEvent = $expectedEvents[$i]; + + $this->assertSame( + $expectedEvent->name(), + $actualEvent->name(), + sprintf(self::MSG_EVENT_NAME_ASSERTION_FAILED, $i) + ); + $this->assertSame( + $expectedEvent->data(), + $actualEvent->data(), + sprintf(self::MSG_EVENT_DATA_ASSERTION_FAILED, $i) + ); + } + } +} diff --git a/test/TModule/ChannelCompositeTest.php b/test/TModule/ChannelCompositeTest.php new file mode 100644 index 0000000..be5fe9e --- /dev/null +++ b/test/TModule/ChannelCompositeTest.php @@ -0,0 +1,642 @@ +markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + $sim->delayOnce('pass', 3, function() use($sim) { + $sim->done(); + }); + + $master = $this->createComposite($data['master'], $loop); + $slaver = $this->createComposite($data['slave1'], $loop); + + $master->on('start', function() use($sim, $master, $slaver) { + $master->push(self::ALIAS_B, self::MSG_2, Channel::MODE_BUFFER); + $master->push(self::ALIAS_B, [ self::MSG_3, self::MSG_4 ], Channel::MODE_BUFFER); + $slaver->start(); + }); + + $master->on('input', function($alias, $message) use($sim, $master, $slaver) { + $sim->expect('input', [ $alias, $message->getMessage() ]); + $sim->emit('pass'); + }); + $slaver->on('input', function($alias, $message) use($sim) { + $sim->expect('input', [ $alias, $message->getMessage() ]); + $sim->emit('pass'); + }); + + $sim->onStart(function() use($slaver, $master) { + $master->start(); + }); + $sim->onStop(function() use($slaver, $master) { + $master->stop(); + $slaver->stop(); + usleep(200e3); + }); + $slaver->push(self::ALIAS_A, self::MSG_1, Channel::MODE_BUFFER); + }) + ->expect([ + [ 'input', [ self::ALIAS_B, self::MSG_1 ] ], + [ 'input', [ self::ALIAS_A, self::MSG_2 ] ], + [ 'input', [ self::ALIAS_A, [ self::MSG_3, self::MSG_4 ] ] ] + ], Simulation::EVENTS_COMPARE_RANDOMLY); + } + + /** + * @dataProvider modelProvider + */ + public function testChannelBox_PushesAndReceivesData_InPairWithOnlineBuffer($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + $sim->delayOnce('pass', 3, function() use($sim) { + $sim->done(); + }); + + $master = $this->createComposite($data['master'], $loop); + $slaver = $this->createComposite($data['slave1'], $loop); + + $master->on('start', function() use($master, $slaver) { + $master->push(self::ALIAS_B, self::MSG_1, Channel::MODE_BUFFER_ONLINE); + $master->push(self::ALIAS_B, self::MSG_2, Channel::MODE_BUFFER_ONLINE); + $master->push(self::ALIAS_B, [ self::MSG_3, self::MSG_4 ], Channel::MODE_BUFFER_ONLINE); + $slaver->start(); + }); + + $slaver->on('input', function($alias, $message) use($sim) { + $sim->expect('input', [ $alias, $message->getMessage() ]); + $sim->emit('pass'); + }); + + $sim->onStart(function() use($slaver, $master) { + $master->start(); + }); + $sim->onStop(function() use($slaver, $master) { + $master->stop(); + $slaver->stop(); + usleep(200e3); + }); + }) + ->expect([ + [ 'input', [ self::ALIAS_A, self::MSG_1 ] ], + [ 'input', [ self::ALIAS_A, self::MSG_2 ] ], + [ 'input', [ self::ALIAS_A, [ self::MSG_3, self::MSG_4 ] ] ] + ], Simulation::EVENTS_COMPARE_RANDOMLY); + } + + /** + * @dataProvider modelProvider + */ + public function testChannelBox_PushesAndReceivesData_InPairWithOfflineBuffer($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + $sim->delayOnce('pass', 3, function() use($sim) { + $sim->done(); + }); + + $master = $this->createComposite($data['master'], $loop); + $slaver = $this->createComposite($data['slave1'], $loop); + + $slaver->on('input', function($alias, $message) use($sim) { + $sim->expect('input', [ $alias, $message->getMessage() ]); + $sim->emit('pass'); + }); + + $sim->onStart(function() use($slaver, $master) { + $master->start(); + $slaver->start(); + }); + $sim->onStop(function() use($slaver, $master) { + $master->stop(); + $slaver->stop(); + usleep(200e3); + }); + + $master->push(self::ALIAS_B, self::MSG_1, Channel::MODE_BUFFER_OFFLINE); + $master->push(self::ALIAS_B, self::MSG_2, Channel::MODE_BUFFER_OFFLINE); + $master->push(self::ALIAS_B, [ self::MSG_3, self::MSG_4 ], Channel::MODE_BUFFER_OFFLINE); + }) + ->expect([ + [ 'input', [ self::ALIAS_A, self::MSG_1 ] ], + [ 'input', [ self::ALIAS_A, self::MSG_2 ] ], + [ 'input', [ self::ALIAS_A, [ self::MSG_3, self::MSG_4 ] ] ] + ], Simulation::EVENTS_COMPARE_RANDOMLY); + } + + /** + * @dataProvider modelProvider + */ + public function testChannelBox_PushesAndReceivesData_InPairWithoutBuffer($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + $sim->delayOnce('pass', 3, function() use($sim) { + $sim->done(); + }); + + $master = $this->createComposite($data['master'], $loop); + $slaver = $this->createComposite($data['slave1'], $loop); + + $master->on('start', function() use($master, $slaver) { + $master->push(self::ALIAS_B, self::MSG_2, Channel::MODE_STANDARD); + $slaver->start(); + }); + $slaver->on('start', function() use($master, $loop) { + $loop->addTimer(0.25, function() use($master) { + $master->push(self::ALIAS_B, self::MSG_3, Channel::MODE_STANDARD); + }); + }); + + $slaver->on('input', function($alias, $message) use($sim) { + $sim->expect('input', [ $alias, $message->getMessage() ]); + $sim->done(); + }); + + $sim->onStart(function() use($slaver, $master) { + $master->start(); + $slaver->start(); + }); + $sim->onStop(function() use($slaver, $master) { + $master->stop(); + $slaver->stop(); + usleep(200e3); + }); + + $master->push(self::ALIAS_B, self::MSG_1, Channel::MODE_STANDARD); + }) + ->expect([ + [ 'input', [ self::ALIAS_A, self::MSG_3 ] ] + ]); + } + + /** + * @dataProvider modelProvider + */ + public function testChannelBox_SendsAndReceivesData_InPairWithBuffer($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + $sim->delayOnce('pass', 3, function() use($sim) { + $sim->done(); + }); + + $master = $this->createComposite($data['master'], $loop); + $slaver = $this->createComposite($data['slave1'], $loop); + + $master->on('start', function() use($sim, $master, $slaver) { + $master->send(self::ALIAS_B, self::MSG_2, Channel::MODE_BUFFER); + $master->send(self::ALIAS_B, [ self::MSG_3, self::MSG_4 ], Channel::MODE_BUFFER); + $slaver->start(); + }); + + $master->on('input', function($alias, $message) use($sim, $master, $slaver) { + $sim->expect('input', [ $alias, $message->getMessage() ]); + $sim->emit('pass'); + }); + $slaver->on('input', function($alias, $message) use($sim) { + $sim->expect('input', [ $alias, $message->getMessage() ]); + $sim->emit('pass'); + }); + + $sim->onStart(function() use($slaver, $master) { + $master->start(); + }); + $sim->onStop(function() use($slaver, $master) { + $master->stop(); + $slaver->stop(); + usleep(200e3); + }); + + $slaver->send(self::ALIAS_A, self::MSG_1, Channel::MODE_BUFFER); + }) + ->expect([ + [ 'input', [ self::ALIAS_B, self::MSG_1 ] ], + [ 'input', [ self::ALIAS_A, self::MSG_2 ] ], + [ 'input', [ self::ALIAS_A, [ self::MSG_3, self::MSG_4 ] ] ] + ], Simulation::EVENTS_COMPARE_RANDOMLY); + } + + /** + * @dataProvider modelProvider + */ + public function testChannelBox_SendsAndReceivesData_InPairWithOnlineBuffer($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + $sim->delayOnce('pass', 3, function() use($sim) { + $sim->done(); + }); + + $master = $this->createComposite($data['master'], $loop); + $slaver = $this->createComposite($data['slave1'], $loop); + + $master->on('start', function() use($master, $slaver) { + $master->send(self::ALIAS_B, self::MSG_1, Channel::MODE_BUFFER_ONLINE); + $master->send(self::ALIAS_B, self::MSG_2, Channel::MODE_BUFFER_ONLINE); + $master->send(self::ALIAS_B, [ self::MSG_3, self::MSG_4 ], Channel::MODE_BUFFER_ONLINE); + $slaver->start(); + }); + + $slaver->on('input', function($alias, $message) use($sim) { + $sim->expect('input', [ $alias, $message->getMessage() ]); + $sim->emit('pass'); + }); + + $sim->onStart(function() use($slaver, $master) { + $master->start(); + }); + $sim->onStop(function() use($slaver, $master) { + $master->stop(); + $slaver->stop(); + usleep(200e3); + }); + }) + ->expect([ + [ 'input', [ self::ALIAS_A, self::MSG_1 ] ], + [ 'input', [ self::ALIAS_A, self::MSG_2 ] ], + [ 'input', [ self::ALIAS_A, [ self::MSG_3, self::MSG_4 ] ] ] + ], Simulation::EVENTS_COMPARE_RANDOMLY); + } + + /** + * @dataProvider modelProvider + */ + public function testChannelBox_SendsAndReceivesData_InPairWithOfflineBuffer($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + $sim->delayOnce('pass', 3, function() use($sim) { + $sim->done(); + }); + + $master = $this->createComposite($data['master'], $loop); + $slaver = $this->createComposite($data['slave1'], $loop); + + $slaver->on('input', function($alias, $message) use($sim) { + $sim->expect('input', [ $alias, $message->getMessage() ]); + $sim->emit('pass'); + }); + + $sim->onStart(function() use($slaver, $master) { + $master->start(); + $slaver->start(); + }); + $sim->onStop(function() use($slaver, $master) { + $master->stop(); + $slaver->stop(); + usleep(200e3); + }); + + $master->send(self::ALIAS_B, self::MSG_1, Channel::MODE_BUFFER_OFFLINE); + $master->send(self::ALIAS_B, self::MSG_2, Channel::MODE_BUFFER_OFFLINE); + $master->send(self::ALIAS_B, [ self::MSG_3, self::MSG_4 ], Channel::MODE_BUFFER_OFFLINE); + }) + ->expect([ + [ 'input', [ self::ALIAS_A, self::MSG_1 ] ], + [ 'input', [ self::ALIAS_A, self::MSG_2 ] ], + [ 'input', [ self::ALIAS_A, [ self::MSG_3, self::MSG_4 ] ] ] + ], Simulation::EVENTS_COMPARE_RANDOMLY); + } + + /** + * @dataProvider modelProvider + */ + public function testChannelBox_SendsAndReceivesData_InPairWithoutBuffer($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + $sim->delayOnce('pass', 3, function() use($sim) { + $sim->done(); + }); + + $master = $this->createComposite($data['master'], $loop); + $slaver = $this->createComposite($data['slave1'], $loop); + + $master->on('start', function() use($master, $slaver) { + $master->send(self::ALIAS_B, self::MSG_2, Channel::MODE_STANDARD); + $slaver->start(); + }); + $slaver->on('start', function() use($master, $loop) { + $loop->addTimer(0.25, function() use($master) { + $master->send(self::ALIAS_B, self::MSG_3, Channel::MODE_STANDARD); + }); + }); + + $slaver->on('input', function($alias, $message) use($sim) { + $sim->expect('input', [ $alias, $message->getMessage() ]); + $sim->done(); + }); + + $sim->onStart(function() use($slaver, $master) { + $master->start(); + $slaver->start(); + }); + $sim->onStop(function() use($slaver, $master) { + $master->stop(); + $slaver->stop(); + usleep(200e3); + }); + + $master->send(self::ALIAS_B, self::MSG_1, Channel::MODE_STANDARD); + }) + ->expect([ + [ 'input', [ self::ALIAS_A, self::MSG_3 ] ] + ]); + } + + + /** + * @dataProvider modelProvider + */ + public function testChannelBox_EmitsInputAndOutputEvents_InPair($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + $sim->delayOnce('pass', 2, function() use($sim) { + $sim->done(); + }); + + $master = $this->createComposite($data['master'], $loop); + $slaver = $this->createComposite($data['slave1'], $loop); + + $master->on('input', function($alias, $message) use($sim, $master, $slaver) { + $sim->expect('input', [ $alias, $message->getMessage() ]); + $sim->emit('pass'); + }); + $slaver->on('output', function($alias, $message) use($sim) { + $sim->expect('output', [ $alias, $message->getMessage() ]); + $sim->emit('pass'); + }); + + $sim->onStart(function() use($slaver, $master) { + $master->start(); + $slaver->start(); + }); + $sim->onStop(function() use($slaver, $master) { + $master->stop(); + $slaver->stop(); + usleep(200e3); + }); + + $slaver->push(self::ALIAS_A, self::MSG_1, Channel::MODE_BUFFER); + }) + ->expect([ + [ 'output', [ self::ALIAS_A, self::MSG_1 ] ], + [ 'input' , [ self::ALIAS_B, self::MSG_1 ] ], + ]); + } + + /** + * @return string[][] + */ + public function modelProvider() + { + $channels = []; + + if (class_exists('ZMQ')) { + $channels[] = $this->getZmqData(); + } else { + $channels[] = [[]]; + } + + return $channels; + } + + /** + * @return mixed[][] + */ + public function getZmqData() + { + return [ + [ + 'master' => [ + 'name' => self::ALIAS_A, + 'buses' => [ + 'bus1' => [ + 'class' => '\Dazzle\ChannelZmq\ZmqDealer', + 'config' => [ + 'id' => self::ALIAS_A, + 'host' => [ self::ALIAS_A ], + 'type' => Channel::BINDER, + 'endpoint' => 'tcp://127.0.0.1:2080' + ] + ], + 'bus2' => [ + 'class' => '\Dazzle\ChannelZmq\ZmqDealer', + 'config' => [ + 'id' => self::ALIAS_A, + 'host' => [ self::ALIAS_A ], + 'type' => Channel::BINDER, + 'endpoint' => 'tcp://127.0.0.1:2081' + ] + ] + ] + ], + 'slave1' => [ + 'name' => self::ALIAS_B, + 'buses' => [ + 'bus1' => [ + 'class' => '\Dazzle\ChannelZmq\ZmqDealer', + 'config' => [ + 'id' => self::ALIAS_B, + 'host' => [ self::ALIAS_A ], + 'type' => Channel::CONNECTOR, + 'endpoint' => 'tcp://127.0.0.1:2080' + ] + ] + ] + ], + 'slave2' => [ + 'name' => self::ALIAS_C, + 'buses' => [ + 'bus1' => [ + 'class' => '\Dazzle\ChannelZmq\ZmqDealer', + 'config' => [ + 'id' => self::ALIAS_C, + 'host' => [ self::ALIAS_A ], + 'type' => Channel::CONNECTOR, + 'endpoint' => 'tcp://127.0.0.1:2081' + ] + ] + ] + ] + ] + ]; + } + + public function createComposite($data, LoopInterface $loop) + { + $name = $data['name']; + $buses = []; + + foreach ($data['buses'] as $busName=>$bus) + { + $buses[$busName] = $this->createChannel($bus, $loop); + } + + $router = new RouterComposite([ + 'input' => $input = new Router(), + 'output' => $output = new Router() + ]); + + $channel = new ChannelComposite($name, $buses, $router, $loop); + + $router = $channel->getInput(); + $router->addDefault( + new RuleHandler(function($params) use($channel) { + $channel->pull( + $params['alias'], + $params['protocol'] + ); + }) + ); + + $router = $channel->getOutput(); + $router->addDefault( + new RuleHandler(function($params) use($channel) { + $channel->push( + $params['alias'], + $params['protocol'], + $params['flags'], + $params['success'], + $params['failure'], + $params['cancel'], + $params['timeout'] + ); + }) + ); + + return $channel; + } + + /** + * @param mixed $data + * @param LoopInterface $loop + * @return ChannelInterface + */ + public function createChannel($data, LoopInterface $loop) + { + $name = $data['config']['id']; + $model = (new ReflectionClass($data['class']))->newInstance($loop, $data['config']); + $router = new RouterComposite([ + 'input' => $input = new Router(), + 'output' => $output = new Router() + ]); + $encoder = new Encoder(new JsonParser); + + $channel = new Channel($name, $model, $router, $encoder, $loop); + + $router = $channel->getInput(); + $router->addDefault( + new RuleHandler(function($params) use($channel) { + $channel->pull( + $params['alias'], + $params['protocol'] + ); + }) + ); + + $router = $channel->getOutput(); + $router->addDefault( + new RuleHandler(function($params) use($channel) { + $channel->push( + $params['alias'], + $params['protocol'], + $params['flags'], + $params['success'], + $params['failure'], + $params['cancel'], + $params['timeout'] + ); + }) + ); + + return $channel; + } +} diff --git a/test/TModule/ChannelModelTest.php b/test/TModule/ChannelModelTest.php new file mode 100644 index 0000000..c1e92e4 --- /dev/null +++ b/test/TModule/ChannelModelTest.php @@ -0,0 +1,765 @@ +markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + + $master = $this->createModel($data['master'], $loop); + + $master->on('start', function() use($sim) { + $sim->expect('start'); + }); + $master->on('stop', function() use($sim) { + $sim->expect('stop'); + }); + + $master->delayOnce('start', 2, function() use($sim) { + $sim->fail('Start have been called twice, but it should not be possible!'); + }); + + $sim->onStart(function() use($sim, $master) { + $master->start(); + $master->start(); + $sim->done(); + }); + $sim->onStop(function() use($master) { + $master->stop(); + }); + }) + ->expect([ + [ 'start' ], + [ 'stop' ] + ]); + } + + /** + * @dataProvider modelProvider + */ + public function testChannelModel_DoesNotAllowStoppingMultipleTimesInRow($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + + $master = $this->createModel($data['master'], $loop); + + $master->on('start', function() use($sim) { + $sim->expect('start'); + }); + $master->on('stop', function() use($sim) { + $sim->expect('stop'); + }); + + $master->delayOnce('stop', 2, function() use($sim) { + $sim->fail('Stop have been called twice, but it should not be possible!'); + }); + + $sim->onStart(function() use($sim, $master) { + $master->start(); + $sim->done(); + }); + $sim->onStop(function() use($master) { + $master->stop(); + $master->stop(); + }); + }) + ->expect([ + [ 'start' ], + [ 'stop' ] + ]); + } + + /** + * @dataProvider modelProvider + */ + public function testChannelModel_ConnectsEvenInWrongOrder($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + $sim->delayOnce('pass', 3, function() use($sim) { + $sim->done(); + }); + + $master = $this->createModel($data['master'], $loop); + $slaver = $this->createModel($data['slave1'], $loop); + + $slaver->on('start', function() use($master, $slaver) { + $master->start(); + }); + + $master->on('recv', function($alias, $message) use($sim, $master, $slaver) { + $sim->done(); + }); + + $sim->onStart(function() use($slaver, $master) { + $slaver->start(); + }); + $sim->onStop(function() use($slaver, $master) { + $master->stop(); + $slaver->stop(); + }); + + $slaver->unicast(self::ALIAS_A, self::MSG_1, Channel::MODE_BUFFER); + }); + } + + /** + * @dataProvider modelProvider + */ + public function testChannelModel_SendsAndReceivesDataCorrectlyThroughUnicast_InPairWithBuffer($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + $sim->delayOnce('pass', 3, function() use($sim) { + $sim->done(); + }); + + $master = $this->createModel($data['master'], $loop); + $slaver = $this->createModel($data['slave1'], $loop); + + $master->on('start', function() use($master, $slaver) { + $master->unicast(self::ALIAS_B, self::MSG_2, Channel::MODE_BUFFER); + $master->unicast(self::ALIAS_B, [ self::MSG_3, self::MSG_4 ], Channel::MODE_BUFFER); + $slaver->start(); + }); + + $master->on('recv', function($alias, $message) use($sim, $master, $slaver) { + $sim->expect('recv', [ $alias, $message ]); + $sim->emit('pass'); + }); + $slaver->on('recv', function($alias, $message) use($sim) { + $sim->expect('recv', [ $alias, $message ]); + $sim->emit('pass'); + }); + + $sim->onStart(function() use($slaver, $master) { + $master->start(); + }); + $sim->onStop(function() use($slaver, $master) { + $master->stop(); + $slaver->stop(); + }); + + $slaver->unicast(self::ALIAS_A, self::MSG_1, Channel::MODE_BUFFER); + }) + ->expect([ + [ 'recv', [ self::ALIAS_B, [ self::MSG_1 ] ] ], + [ 'recv', [ self::ALIAS_A, [ self::MSG_2 ] ] ], + [ 'recv', [ self::ALIAS_A, [ self::MSG_3, self::MSG_4 ] ] ] + ], Simulation::EVENTS_COMPARE_RANDOMLY); + } + + /** + * @dataProvider modelProvider + */ + public function testChannelModel_SendsAndReceivesDataCorrectlyThroughUnicast_InPairWithOnlineBuffer($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + $sim->delayOnce('pass', 3, function() use($sim) { + $sim->done(); + }); + + $master = $this->createModel($data['master'], $loop); + $slaver = $this->createModel($data['slave1'], $loop); + + $master->on('start', function() use($master, $slaver) { + $master->unicast(self::ALIAS_B, self::MSG_1, Channel::MODE_BUFFER_ONLINE); + $master->unicast(self::ALIAS_B, self::MSG_2, Channel::MODE_BUFFER_ONLINE); + $master->unicast(self::ALIAS_B, [ self::MSG_3, self::MSG_4 ], Channel::MODE_BUFFER_ONLINE); + $slaver->start(); + }); + + $slaver->on('recv', function($alias, $message) use($sim) { + $sim->expect('recv', [ $alias, $message ]); + $sim->emit('pass'); + }); + + $sim->onStart(function() use($slaver, $master) { + $master->start(); + }); + $sim->onStop(function() use($slaver, $master) { + $master->stop(); + $slaver->stop(); + }); + }) + ->expect([ + [ 'recv', [ self::ALIAS_A, [ self::MSG_1 ] ] ], + [ 'recv', [ self::ALIAS_A, [ self::MSG_2 ] ] ], + [ 'recv', [ self::ALIAS_A, [ self::MSG_3, self::MSG_4 ] ] ] + ], Simulation::EVENTS_COMPARE_RANDOMLY); + } + + /** + * @dataProvider modelProvider + */ + public function testChannelModel_SendsAndReceivesDataCorrectlyThroughUnicast_InPairWithOfflineBuffer($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + $sim->delayOnce('pass', 3, function() use($sim) { + $sim->done(); + }); + + $master = $this->createModel($data['master'], $loop); + $slaver = $this->createModel($data['slave1'], $loop); + + $slaver->on('recv', function($alias, $message) use($sim) { + $sim->expect('recv', [ $alias, $message ]); + $sim->emit('pass'); + }); + + $sim->onStart(function() use($slaver, $master) { + $master->start(); + $slaver->start(); + }); + $sim->onStop(function() use($slaver, $master) { + $master->stop(); + $slaver->stop(); + }); + + $master->unicast(self::ALIAS_B, self::MSG_1, Channel::MODE_BUFFER_OFFLINE); + $master->unicast(self::ALIAS_B, self::MSG_2, Channel::MODE_BUFFER_OFFLINE); + $master->unicast(self::ALIAS_B, [ self::MSG_3, self::MSG_4 ], Channel::MODE_BUFFER_OFFLINE); + }) + ->expect([ + [ 'recv', [ self::ALIAS_A, [ self::MSG_1 ] ] ], + [ 'recv', [ self::ALIAS_A, [ self::MSG_2 ] ] ], + [ 'recv', [ self::ALIAS_A, [ self::MSG_3, self::MSG_4 ] ] ] + ], Simulation::EVENTS_COMPARE_RANDOMLY); + } + + /** + * @dataProvider modelProvider + */ + public function testChannelModel_SendsAndReceivesDataCorrectlyThroughUnicast_InPairWithoutBuffer($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + $sim->delayOnce('pass', 3, function() use($sim) { + $sim->done(); + }); + + $master = $this->createModel($data['master'], $loop); + $slaver = $this->createModel($data['slave1'], $loop); + + $master->on('start', function() use($master, $slaver) { + $master->unicast(self::ALIAS_B, self::MSG_2, Channel::MODE_STANDARD); + $slaver->start(); + }); + $slaver->on('start', function() use($master, $loop) { + $loop->addTimer(0.25, function() use($master) { + $master->unicast(self::ALIAS_B, self::MSG_3, Channel::MODE_STANDARD); + }); + }); + + $slaver->on('recv', function($alias, $message) use($sim) { + $sim->expect('recv', [ $alias, $message ]); + $sim->done(); + }); + + $sim->onStart(function() use($master, $slaver) { + $master->start(); + $slaver->start(); + }); + $sim->onStop(function() use($master, $slaver) { + $master->stop(); + $slaver->stop(); + }); + + $master->unicast(self::ALIAS_B, self::MSG_1, Channel::MODE_STANDARD); + }) + ->expect([ + [ 'recv', [ self::ALIAS_A, [ self::MSG_3 ] ] ] + ]); + } + + /** + * @dataProvider modelProvider + */ + public function testChannelModel_SendsAndReceivesDataCorrectlyThroughBroadcast_InThreesomeWithoutBuffer($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + $sim->delayOnce('pass', 2, function() use($sim) { + $sim->done(); + }); + + $master = $this->createModel($data['master'], $loop); + $slave1 = $this->createModel($data['slave1'], $loop); + $slave2 = $this->createModel($data['slave2'], $loop); + + $master->on('start', function() use($master, $slave1, $slave2) { + $master->broadcast([ self::MSG_2 ]); + $slave1->start(); + $slave2->start(); + }); + $slave1->on('start', function() use($sim) { + $sim->emit('up'); + }); + $slave2->on('start', function() use($sim) { + $sim->emit('up'); + }); + + $sim->delayOnce('up', 2, function() use($loop, $master) { + $loop->addTimer(0.25, function() use($master) { + $master->broadcast([ self::MSG_3 ]); + }); + }); + + $slave1->on('recv', function($alias, $message) use($sim) { + $sim->expect('recv', [ $alias, $message ]); + $sim->emit('pass'); + }); + $slave2->on('recv', function($alias, $message) use($sim) { + $sim->expect('recv', [ $alias, $message ]); + $sim->emit('pass'); + }); + + $sim->onStart(function() use($master) { + $master->start(); + }); + $sim->onStop(function() use($master, $slave1, $slave2) { + $master->stop(); + $slave1->stop(); + $slave2->stop(); + }); + + $master->broadcast([ self::MSG_1 ]); + }) + ->expect([ + [ 'recv', [ self::ALIAS_A, [ self::MSG_3 ] ] ], + [ 'recv', [ self::ALIAS_A, [ self::MSG_3 ] ] ] + ]); + } + + /** + * @dataProvider modelProvider + */ + public function testChannelModel_EmitsConnectAndDisconnectEvents_InThreesome($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + + $data['master']['config']['heartbeatKeepalive'] = 100; + + $master = $this->createModel($data['master'], $loop); + $slave1 = $this->createModel($data['slave1'], $loop); + $slave2 = $this->createModel($data['slave2'], $loop); + + $master->on('start', function() use($master, $slave1, $slave2) { + $master->broadcast([ self::MSG_2 ]); + $slave1->start(); + $slave2->start(); + }); + $master->on('connect', function($name) use($sim) { + $sim->expect('connect', [ $name ]); + }); + $master->on('disconnect', function($name) use($sim) { + $sim->expect('disconnect', [ $name ]); + }); + + $sim->delayOnce('down', 2, function() use($sim, $loop) { + $loop->addTimer(0.5, function() use($sim) { + $sim->done(); + }); + }); + + $slave1->on('start', function() use($sim, $slave1, $loop) { + $loop->addTimer(0.25, function() use($slave1) { + $slave1->stop(); + }); + }); + $slave1->on('stop', function() use($sim) { + $sim->emit('down'); + }); + + $slave2->on('start', function() use($sim, $slave2, $loop) { + $loop->addTimer(0.25, function() use($slave2) { + $slave2->stop(); + }); + }); + $slave2->on('stop', function() use($sim) { + $sim->emit('down'); + }); + + $sim->onStart(function() use($master) { + $master->start(); + }); + $sim->onStop(function() use($master, $slave1, $slave2) { + $master->stop(); + }); + }) + ->expect([ + [ 'connect', [ self::ALIAS_B ] ], + [ 'connect', [ self::ALIAS_C ] ], + [ 'disconnect', [ self::ALIAS_B ] ], + [ 'disconnect', [ self::ALIAS_C ] ], + ]); + } + + /** + * @dataProvider modelProvider + */ + public function testChannelModel_EmitsRecvAndSendEvents_InPair($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + $sim->delayOnce('pass', 2, function() use($sim) { + $sim->done(); + }); + + $master = $this->createModel($data['master'], $loop); + $slaver = $this->createModel($data['slave1'], $loop); + + $master->on('recv', function($alias, $message) use($sim, $master, $slaver) { + $sim->expect('recv', [ $alias, $message ]); + $sim->emit('pass'); + }); + $slaver->on('send', function($alias, $message) use($sim) { + $sim->expect('send', [ $alias, $message ]); + $sim->emit('pass'); + }); + + $sim->onStart(function() use($slaver, $master) { + $master->start(); + $slaver->start(); + }); + $sim->onStop(function() use($slaver, $master) { + $master->stop(); + $slaver->stop(); + }); + + $slaver->unicast(self::ALIAS_A, self::MSG_1, Channel::MODE_BUFFER); + }) + ->expect([ + [ 'send', [ self::ALIAS_A, [ self::MSG_1 ] ] ], + [ 'recv', [ self::ALIAS_B, [ self::MSG_1 ] ] ], + ]); + } + + /** + * @dataProvider modelProvider + */ + public function testChannelModel_IsAwareOfItsOwnConnection($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + + $master = $this->createModel($data['master'], $loop); + + $master->on('start', function() use($sim, $master) { + if (!$master->isStarted()) + { + $sim->fail('Master should be marked as connected.'); + } + $master->stop(); + }); + $master->on('stop', function() use($sim, $master) { + if (!$master->isStopped()) + { + $sim->fail('Master should be marked as disconnected.'); + } + $sim->done(); + }); + + $sim->onStart(function() use($master) { + $master->start(); + }); + }); + } + + /** + * @dataProvider modelProvider + */ + public function testChannelModel_IsAwareOfConnectedModels_InThreesome($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + + $data['master']['config']['heartbeatKeepalive'] = 100; + + $master = $this->createModel($data['master'], $loop); + $slave1 = $this->createModel($data['slave1'], $loop); + $slave2 = $this->createModel($data['slave2'], $loop); + + $master->on('start', function() use($master, $slave1, $slave2) { + $slave1->start(); + $slave2->start(); + }); + $master->on('connect', function($name) use($sim, $master) { + $sim->expect('connect', [ $name, $master->isConnected($name) ]); + $sim->emit('connect'); + }); + $master->on('disconnect', function($name) use($sim, $master) { + $sim->expect('disconnect', [ $name, $master->isConnected($name) ]); + $sim->emit('disconnect'); + }); + + $sim->delayOnce('connect', 2, function() use($sim, $master) { + $sim->expect('getConnected', [ $master->getConnected() ]); + }); + $sim->delayOnce('disconnect', 2, function() use($sim, $master) { + $sim->expect('getConnected', [ $master->getConnected() ]); + }); + + $sim->delayOnce('down', 2, function() use($sim, $loop) { + $loop->addTimer(0.5, function() use($sim) { + $sim->done(); + }); + }); + + $slave1->on('start', function() use($sim, $slave1, $loop) { + $loop->addTimer(0.25, function() use($slave1) { + $slave1->stop(); + }); + }); + $slave1->on('stop', function() use($sim) { + $sim->emit('down'); + }); + + $slave2->on('start', function() use($sim, $slave2, $loop) { + $loop->addTimer(0.25, function() use($slave2) { + $slave2->stop(); + }); + }); + $slave2->on('stop', function() use($sim) { + $sim->emit('down'); + }); + + $sim->onStart(function() use($master) { + $master->start(); + }); + $sim->onStop(function() use($master, $slave1, $slave2) { + $master->stop(); + }); + }) + ->expect([ + [ 'connect', [ self::ALIAS_B, true ] ], + [ 'connect', [ self::ALIAS_C, true ] ], + [ 'getConnected', [ [ self::ALIAS_B, self::ALIAS_C ] ] ], + [ 'disconnect', [ self::ALIAS_B, false ] ], + [ 'disconnect', [ self::ALIAS_C, false ] ], + [ 'getConnected', [ [] ] ], + ]); + } + + /** + * @dataProvider modelProvider + */ + public function testChannelModel_AllowsReconnections_InThreesome($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + + $data['master']['config']['heartbeatKeepalive'] = 100; + + $master = $this->createModel($data['master'], $loop); + $slave1 = $this->createModel($data['slave1'], $loop); + + $master->times('connect', 2, function($name) use($sim, $slave1) { + $sim->expect('connect', [ $name ]); + $slave1->stop(); + }); + + $slave1->once('stop', function() use($sim, $slave1, $loop) { + $loop->addTimer(0.25, function() use($slave1) { + $slave1->start(); + }); + }); + $slave1->delayOnce('stop', 2, function() use($sim) { + $sim->done(); + }); + + $sim->onStart(function() use($master, $slave1) { + $master->start(); + $slave1->start(); + }); + $sim->onStop(function() use($master, $slave1) { + $master->stop(); + $slave1->stop(); + }); + }) + ->expect([ + [ 'connect', [ self::ALIAS_B ] ], + [ 'connect', [ self::ALIAS_B ] ] + ]); + } + + /** + * @return string[][] + */ + public function modelProvider() + { + $channels = []; + + if (class_exists('ZMQ')) { + $channels[] = $this->getZmqData(); + } else { + $channels[] = [[]]; + } + + return $channels; + } + + /** + * @return mixed[][] + */ + public function getZmqData() + { + return [ + [ + 'master' => [ + 'class' => '\Dazzle\ChannelZmq\ZmqDealer', + 'config' => [ + 'id' => self::ALIAS_A, + 'host' => [ self::ALIAS_A ], + 'type' => Channel::BINDER, + 'endpoint' => 'tcp://127.0.0.1:2080' + ] + ], + 'slave1' => [ + 'class' => '\Dazzle\ChannelZmq\ZmqDealer', + 'config' => [ + 'id' => self::ALIAS_B, + 'host' => [ self::ALIAS_A ], + 'type' => Channel::CONNECTOR, + 'endpoint' => 'tcp://127.0.0.1:2080' + ] + ], + 'slave2' => [ + 'class' => '\Dazzle\ChannelZmq\ZmqDealer', + 'config' => [ + 'id' => self::ALIAS_C, + 'host' => [ self::ALIAS_A ], + 'type' => Channel::CONNECTOR, + 'endpoint' => 'tcp://127.0.0.1:2080' + ] + ] + ] + ]; + } + + /** + * @param mixed $data + * @param LoopInterface $loop + * @return ModelInterface + */ + public function createModel($data, LoopInterface $loop) + { + return (new ReflectionClass($data['class']))->newInstance($loop, $data['config']); + } +} diff --git a/test/TModule/ChannelTest.php b/test/TModule/ChannelTest.php new file mode 100644 index 0000000..a36a7b7 --- /dev/null +++ b/test/TModule/ChannelTest.php @@ -0,0 +1,645 @@ +markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + $sim->delayOnce('pass', 3, function() use($sim) { + $sim->done(); + }); + + $master = $this->createChannel($data['master'], $loop); + $slaver = $this->createChannel($data['slave1'], $loop); + + $master->on('start', function() use($sim, $master, $slaver) { + $master->push(self::ALIAS_B, self::MSG_2, Channel::MODE_BUFFER); + $master->push(self::ALIAS_B, [ self::MSG_3, self::MSG_4 ], Channel::MODE_BUFFER); + $slaver->start(); + }); + + $master->on('input', function($alias, $message) use($sim, $master, $slaver) { + $sim->expect('input', [ $alias, $message->getMessage() ]); + $sim->emit('pass'); + }); + $slaver->on('input', function($alias, $message) use($sim) { + $sim->expect('input', [ $alias, $message->getMessage() ]); + $sim->emit('pass'); + }); + + $sim->onStart(function() use($slaver, $master) { + $master->start(); + }); + $sim->onStop(function() use($slaver, $master) { + $master->stop(); + $slaver->stop(); + usleep(200e3); + }); + + $slaver->push(self::ALIAS_A, self::MSG_1, Channel::MODE_BUFFER); + }) + ->expect([ + [ 'input', [ self::ALIAS_B, self::MSG_1 ] ], + [ 'input', [ self::ALIAS_A, self::MSG_2 ] ], + [ 'input', [ self::ALIAS_A, [ self::MSG_3, self::MSG_4 ] ] ] + ], Simulation::EVENTS_COMPARE_RANDOMLY); + } + + /** + * @dataProvider modelProvider + */ + public function testChannel_PushesAndReceivesData_InPairWithOnlineBuffer($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + $sim->delayOnce('pass', 3, function() use($sim) { + $sim->done(); + }); + + $master = $this->createChannel($data['master'], $loop); + $slaver = $this->createChannel($data['slave1'], $loop); + + $master->on('start', function() use($master, $slaver) { + $master->push(self::ALIAS_B, self::MSG_1, Channel::MODE_BUFFER_ONLINE); + $master->push(self::ALIAS_B, self::MSG_2, Channel::MODE_BUFFER_ONLINE); + $master->push(self::ALIAS_B, [ self::MSG_3, self::MSG_4 ], Channel::MODE_BUFFER_ONLINE); + $slaver->start(); + }); + + $slaver->on('input', function($alias, $message) use($sim) { + $sim->expect('input', [ $alias, $message->getMessage() ]); + $sim->emit('pass'); + }); + + $sim->onStart(function() use($slaver, $master) { + $master->start(); + }); + $sim->onStop(function() use($slaver, $master) { + $master->stop(); + $slaver->stop(); + usleep(200e3); + }); + }) + ->expect([ + [ 'input', [ self::ALIAS_A, self::MSG_1 ] ], + [ 'input', [ self::ALIAS_A, self::MSG_2 ] ], + [ 'input', [ self::ALIAS_A, [ self::MSG_3, self::MSG_4 ] ] ] + ], Simulation::EVENTS_COMPARE_RANDOMLY); + } + + /** + * @dataProvider modelProvider + */ + public function testChannel_PushesAndReceivesData_InPairWithOfflineBuffer($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + $sim->delayOnce('pass', 3, function() use($sim) { + $sim->done(); + }); + + $master = $this->createChannel($data['master'], $loop); + $slaver = $this->createChannel($data['slave1'], $loop); + + $slaver->on('input', function($alias, $message) use($sim) { + $sim->expect('input', [ $alias, $message->getMessage() ]); + $sim->emit('pass'); + }); + + $sim->onStart(function() use($slaver, $master) { + $master->start(); + $slaver->start(); + }); + $sim->onStop(function() use($slaver, $master) { + $master->stop(); + $slaver->stop(); + usleep(200e3); + }); + + $master->push(self::ALIAS_B, self::MSG_1, Channel::MODE_BUFFER_OFFLINE); + $master->push(self::ALIAS_B, self::MSG_2, Channel::MODE_BUFFER_OFFLINE); + $master->push(self::ALIAS_B, [ self::MSG_3, self::MSG_4 ], Channel::MODE_BUFFER_OFFLINE); + }) + ->expect([ + [ 'input', [ self::ALIAS_A, self::MSG_1 ] ], + [ 'input', [ self::ALIAS_A, self::MSG_2 ] ], + [ 'input', [ self::ALIAS_A, [ self::MSG_3, self::MSG_4 ] ] ] + ], Simulation::EVENTS_COMPARE_RANDOMLY); + } + + /** + * @dataProvider modelProvider + */ + public function testChannel_PushesAndReceivesData_InPairWithoutBuffer($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + $sim->delayOnce('pass', 3, function() use($sim) { + $sim->done(); + }); + + $master = $this->createChannel($data['master'], $loop); + $slaver = $this->createChannel($data['slave1'], $loop); + + $master->on('start', function() use($master, $slaver) { + $master->push(self::ALIAS_B, self::MSG_2, Channel::MODE_STANDARD); + $slaver->start(); + }); + $slaver->on('start', function() use($master, $loop) { + $loop->addTimer(0.25, function() use($master) { + $master->push(self::ALIAS_B, self::MSG_3, Channel::MODE_STANDARD); + }); + }); + + $slaver->on('input', function($alias, $message) use($sim) { + $sim->expect('input', [ $alias, $message->getMessage() ]); + $sim->done(); + }); + + $sim->onStart(function() use($slaver, $master) { + $master->start(); + $slaver->start(); + }); + $sim->onStop(function() use($slaver, $master) { + $master->stop(); + $slaver->stop(); + usleep(200e3); + }); + + $master->push(self::ALIAS_B, self::MSG_1, Channel::MODE_STANDARD); + }) + ->expect([ + [ 'input', [ self::ALIAS_A, self::MSG_3 ] ] + ]); + } + + /** + * @dataProvider modelProvider + */ + public function testChannel_SendsAndReceivesData_InPairWithBuffer($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + $sim->delayOnce('pass', 3, function() use($sim) { + $sim->done(); + }); + + $master = $this->createChannel($data['master'], $loop); + $slaver = $this->createChannel($data['slave1'], $loop); + + $master->on('start', function() use($sim, $master, $slaver) { + $master->send(self::ALIAS_B, self::MSG_2, Channel::MODE_BUFFER); + $master->send(self::ALIAS_B, [ self::MSG_3, self::MSG_4 ], Channel::MODE_BUFFER); + $slaver->start(); + }); + + $master->on('input', function($alias, $message) use($sim, $master, $slaver) { + $sim->expect('input', [ $alias, $message->getMessage() ]); + $sim->emit('pass'); + }); + $slaver->on('input', function($alias, $message) use($sim) { + $sim->expect('input', [ $alias, $message->getMessage() ]); + $sim->emit('pass'); + }); + + $sim->onStart(function() use($slaver, $master) { + $master->start(); + }); + $sim->onStop(function() use($slaver, $master) { + $master->stop(); + $slaver->stop(); + usleep(200e3); + }); + + $slaver->send(self::ALIAS_A, self::MSG_1, Channel::MODE_BUFFER); + }) + ->expect([ + [ 'input', [ self::ALIAS_B, self::MSG_1 ] ], + [ 'input', [ self::ALIAS_A, self::MSG_2 ] ], + [ 'input', [ self::ALIAS_A, [ self::MSG_3, self::MSG_4 ] ] ] + ], Simulation::EVENTS_COMPARE_RANDOMLY); + } + + /** + * @dataProvider modelProvider + */ + public function testChannel_SendsAndReceivesData_InPairWithOnlineBuffer($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + $sim->delayOnce('pass', 3, function() use($sim) { + $sim->done(); + }); + + $master = $this->createChannel($data['master'], $loop); + $slaver = $this->createChannel($data['slave1'], $loop); + + $master->on('start', function() use($master, $slaver) { + $master->send(self::ALIAS_B, self::MSG_1, Channel::MODE_BUFFER_ONLINE); + $master->send(self::ALIAS_B, self::MSG_2, Channel::MODE_BUFFER_ONLINE); + $master->send(self::ALIAS_B, [ self::MSG_3, self::MSG_4 ], Channel::MODE_BUFFER_ONLINE); + $slaver->start(); + }); + + $slaver->on('input', function($alias, $message) use($sim) { + $sim->expect('input', [ $alias, $message->getMessage() ]); + $sim->emit('pass'); + }); + + $sim->onStart(function() use($slaver, $master) { + $master->start(); + }); + $sim->onStop(function() use($slaver, $master) { + $master->stop(); + $slaver->stop(); + usleep(200e3); + }); + + + }) + ->expect([ + [ 'input', [ self::ALIAS_A, self::MSG_1 ] ], + [ 'input', [ self::ALIAS_A, self::MSG_2 ] ], + [ 'input', [ self::ALIAS_A, [ self::MSG_3, self::MSG_4 ] ] ] + ]); + } + + /** + * @dataProvider modelProvider + */ + public function testChannel_SendsAndReceivesData_InPairWithOfflineBuffer($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + $sim->delayOnce('pass', 3, function() use($sim) { + $sim->done(); + }); + + $master = $this->createChannel($data['master'], $loop); + $slaver = $this->createChannel($data['slave1'], $loop); + + $slaver->on('input', function($alias, $message) use($sim) { + $sim->expect('input', [ $alias, $message->getMessage() ]); + $sim->emit('pass'); + }); + + $sim->onStart(function() use($slaver, $master) { + $master->start(); + $slaver->start(); + }); + $sim->onStop(function() use($slaver, $master) { + $master->stop(); + $slaver->stop(); + usleep(200e3); + }); + + $master->send(self::ALIAS_B, self::MSG_1, Channel::MODE_BUFFER_OFFLINE); + $master->send(self::ALIAS_B, self::MSG_2, Channel::MODE_BUFFER_OFFLINE); + $master->send(self::ALIAS_B, [ self::MSG_3, self::MSG_4 ], Channel::MODE_BUFFER_OFFLINE); + }) + ->expect([ + [ 'input', [ self::ALIAS_A, self::MSG_1 ] ], + [ 'input', [ self::ALIAS_A, self::MSG_2 ] ], + [ 'input', [ self::ALIAS_A, [ self::MSG_3, self::MSG_4 ] ] ] + ], Simulation::EVENTS_COMPARE_RANDOMLY); + } + + /** + * @dataProvider modelProvider + */ + public function testChannel_SendsAndReceivesData_InPairWithoutBuffer($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + $sim->delayOnce('pass', 3, function() use($sim) { + $sim->done(); + }); + + $master = $this->createChannel($data['master'], $loop); + $slaver = $this->createChannel($data['slave1'], $loop); + + $master->on('start', function() use($master, $slaver) { + $master->send(self::ALIAS_B, self::MSG_2, Channel::MODE_STANDARD); + $slaver->start(); + }); + $slaver->on('start', function() use($master, $loop) { + $loop->addTimer(0.25, function() use($master) { + $master->send(self::ALIAS_B, self::MSG_3, Channel::MODE_STANDARD); + }); + }); + + $slaver->on('input', function($alias, $message) use($sim) { + $sim->expect('input', [ $alias, $message->getMessage() ]); + $sim->done(); + }); + + $sim->onStart(function() use($slaver, $master) { + $master->start(); + $slaver->start(); + }); + $sim->onStop(function() use($slaver, $master) { + $master->stop(); + $slaver->stop(); + usleep(200e3); + }); + + $master->send(self::ALIAS_B, self::MSG_1, Channel::MODE_STANDARD); + }) + ->expect([ + [ 'input', [ self::ALIAS_A, self::MSG_3 ] ] + ]); + } + + /** + * @dataProvider modelProvider + */ + public function testChannel_EmitsConnectAndDisconnectEvents_InThreesome($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + + $data['master']['config']['heartbeatKeepalive'] = 100; + + $master = $this->createChannel($data['master'], $loop); + $slave1 = $this->createChannel($data['slave1'], $loop); + $slave2 = $this->createChannel($data['slave2'], $loop); + + $master->on('start', function() use($master, $slave1, $slave2) { + $slave1->start(); + $slave2->start(); + }); + $master->on('connect', function($name) use($sim) { + $sim->expect('connect', [ $name ]); + }); + $master->on('disconnect', function($name) use($sim) { + $sim->expect('disconnect', [ $name ]); + }); + + $sim->delayOnce('down', 2, function() use($sim, $loop) { + $loop->addTimer(0.5, function() use($sim) { + $sim->done(); + }); + }); + + $slave1->on('start', function() use($sim, $slave1, $loop) { + $loop->addTimer(0.25, function() use($slave1) { + $slave1->stop(); + }); + }); + $slave1->on('stop', function() use($sim) { + $sim->emit('down'); + }); + + $slave2->on('start', function() use($sim, $slave2, $loop) { + $loop->addTimer(0.25, function() use($slave2) { + $slave2->stop(); + }); + }); + $slave2->on('stop', function() use($sim) { + $sim->emit('down'); + }); + + $sim->onStart(function() use($master) { + $master->start(); + }); + $sim->onStop(function() use($master, $slave1, $slave2) { + $master->stop(); + usleep(200e3); + }); + }) + ->expect([ + [ 'connect', [ self::ALIAS_B ] ], + [ 'connect', [ self::ALIAS_C ] ], + [ 'disconnect', [ self::ALIAS_B ] ], + [ 'disconnect', [ self::ALIAS_C ] ], + ]); + } + + /** + * @dataProvider modelProvider + */ + public function testChannel_EmitsInputAndOutputEvents_InPair($data) + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $this + ->simulate(function(SimulationInterface $sim) use($data) { + $loop = $sim->getLoop(); + $sim->delayOnce('pass', 2, function() use($sim) { + $sim->done(); + }); + + $master = $this->createChannel($data['master'], $loop); + $slaver = $this->createChannel($data['slave1'], $loop); + + $master->on('input', function($alias, $message) use($sim, $master, $slaver) { + $sim->expect('input', [ $alias, $message->getMessage() ]); + $sim->emit('pass'); + }); + $slaver->on('output', function($alias, $message) use($sim) { + $sim->expect('output', [ $alias, $message->getMessage() ]); + $sim->emit('pass'); + }); + + $sim->onStart(function() use($slaver, $master) { + $master->start(); + $slaver->start(); + }); + $sim->onStop(function() use($slaver, $master) { + $master->stop(); + $slaver->stop(); + usleep(200e3); + }); + + $slaver->push(self::ALIAS_A, self::MSG_1, Channel::MODE_BUFFER); + }) + ->expect([ + [ 'output', [ self::ALIAS_A, self::MSG_1 ] ], + [ 'input' , [ self::ALIAS_B, self::MSG_1 ] ], + ]); + } + + /** + * @return string[][] + */ + public function modelProvider() + { + $channels = []; + + if (class_exists('ZMQ')) { + $channels[] = $this->getZmqData(); + } else { + $channels[] = [[]]; + } + + return $channels; + } + + /** + * @return mixed[][] + */ + public function getZmqData() + { + return [ + [ + 'master' => [ + 'class' => '\Dazzle\ChannelZmq\ZmqDealer', + 'config' => [ + 'id' => self::ALIAS_A, + 'host' => [ self::ALIAS_A ], + 'type' => Channel::BINDER, + 'endpoint' => 'tcp://127.0.0.1:2080' + ] + ], + 'slave1' => [ + 'class' => '\Dazzle\ChannelZmq\ZmqDealer', + 'config' => [ + 'id' => self::ALIAS_B, + 'host' => [ self::ALIAS_A ], + 'type' => Channel::CONNECTOR, + 'endpoint' => 'tcp://127.0.0.1:2080' + ] + ], + 'slave2' => [ + 'class' => '\Dazzle\ChannelZmq\ZmqDealer', + 'config' => [ + 'id' => self::ALIAS_C, + 'host' => [ self::ALIAS_A ], + 'type' => Channel::CONNECTOR, + 'endpoint' => 'tcp://127.0.0.1:2080' + ] + ] + ] + ]; + } + + /** + * @param mixed $data + * @param LoopInterface $loop + * @return ChannelInterface + */ + public function createChannel($data, LoopInterface $loop) + { + $name = $data['config']['id']; + $model = (new ReflectionClass($data['class']))->newInstance($loop, $data['config']); + $router = new RouterComposite([ + 'input' => $input = new Router(), + 'output' => $output = new Router() + ]); + $encoder = new Encoder(new JsonParser); + + $channel = new Channel($name, $model, $router, $encoder, $loop); + + $router = $channel->getInput(); + $router->addDefault( + new RuleHandler(function($params) use($channel) { + $channel->pull( + $params['alias'], + $params['protocol'] + ); + }) + ); + + $router = $channel->getOutput(); + $router->addDefault( + new RuleHandler(function($params) use($channel) { + $channel->push( + $params['alias'], + $params['protocol'], + $params['flags'], + $params['success'], + $params['failure'], + $params['cancel'], + $params['timeout'] + ); + }) + ); + + return $channel; + } +} diff --git a/test/TUnit.php b/test/TUnit.php new file mode 100644 index 0000000..4428c59 --- /dev/null +++ b/test/TUnit.php @@ -0,0 +1,211 @@ +exactly(2); + } + + /** + * Creates a callback that must be called $amount times or the test will fail. + * + * @param $amount + * @return callable|\PHPUnit_Framework_MockObject_MockObject + */ + public function expectCallableExactly($amount) + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->exactly($amount)) + ->method('__invoke'); + + return $mock; + } + + /** + * Creates a callback that must be called once. + * + * @return callable|\PHPUnit_Framework_MockObject_MockObject + */ + public function expectCallableOnce() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke'); + + return $mock; + } + + /** + * Creates a callback that must be called twice. + * + * @return callable|\PHPUnit_Framework_MockObject_MockObject + */ + public function expectCallableTwice() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->exactly(2)) + ->method('__invoke'); + + return $mock; + } + + /** + * Creates a callable that must not be called once. + * + * @return callable|\PHPUnit_Framework_MockObject_MockObject + */ + public function expectCallableNever() + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->never()) + ->method('__invoke'); + + return $mock; + } + + /** + * Creates a callable mock. + * + * @return callable|\PHPUnit_Framework_MockObject_MockObject + */ + public function createCallableMock() + { + return $this->getMock(Callback::class); + } + + /** + * @return LoopInterface|\PHPUnit_Framework_MockObject_MockObject + */ + public function createLoopMock() + { + return $this->getMock('Dazzle\Loop\LoopInterface'); + } + + /** + * @return LoopInterface|\PHPUnit_Framework_MockObject_MockObject + */ + public function createWritableLoopMock() + { + $loop = $this->createLoopMock(); + $loop + ->expects($this->once()) + ->method('addWriteStream') + ->will($this->returnCallback(function($stream, $listener) { + call_user_func($listener, $stream); + })); + + return $loop; + } + + /** + * @return LoopInterface|\PHPUnit_Framework_MockObject_MockObject + */ + public function createReadableLoopMock() + { + $loop = $this->createLoopMock(); + $loop + ->expects($this->once()) + ->method('addReadStream') + ->will($this->returnCallback(function($stream, $listener) { + call_user_func($listener, $stream); + })); + + return $loop; + } + + /** + * Check if protected property exists. + * + * @param object $object + * @param string $property + * @return bool + */ + public function existsProtectedProperty($object, $property) + { + $reflection = new ReflectionClass($object); + return $reflection->hasProperty($property); + } + + /** + * Get protected property from given object via reflection. + * + * @param object $object + * @param string $property + * @return mixed + */ + public function getProtectedProperty($object, $property) + { + $reflection = new ReflectionClass($object); + $reflection_property = $reflection->getProperty($property); + $reflection_property->setAccessible(true); + + return $reflection_property->getValue($object); + } + + /** + * Set protected property on a given object via reflection. + * + * @param object $object + * @param string $property + * @param mixed $value + * @return object + */ + public function setProtectedProperty($object, $property, $value) + { + $reflection = new ReflectionClass($object); + $reflection_property = $reflection->getProperty($property); + $reflection_property->setAccessible(true); + $reflection_property->setValue($object, $value); + + return $object; + } + + /** + * Call protected method on a given object via reflection. + * + * @param object|string $objectOrClass + * @param string $method + * @param mixed[] $args + * @return mixed + */ + public function callProtectedMethod($objectOrClass, $method, $args = []) + { + $reflection = new ReflectionClass($objectOrClass); + $reflectionMethod = $reflection->getMethod($method); + $reflectionMethod->setAccessible(true); + $reflectionTarget = is_object($objectOrClass) ? $objectOrClass : null; + + return $reflectionMethod->invokeArgs($reflectionTarget, $args); + } +} diff --git a/test/TUnit/ZmqDealerTest.php b/test/TUnit/ZmqDealerTest.php new file mode 100644 index 0000000..1024149 --- /dev/null +++ b/test/TUnit/ZmqDealerTest.php @@ -0,0 +1,201 @@ +markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $model = $this->createBinder(); + + $this->assertInstanceOf(ZmqDealer::class, $model); + $this->assertInstanceOf(ZmqModel::class, $model); + $this->assertInstanceOf(ModelInterface::class, $model); + } + + /** + * + */ + public function testApiDestrcutor_CreatesInstance() + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $model = $this->createBinder(); + unset($model); + } + + /** + * + */ + public function testProtectedApiGetSocketType_GetsSocketType() + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $model = $this->createBinder(); + $result = $this->callProtectedMethod($model, 'getSocketType'); + + $this->assertSame(\ZMQ::SOCKET_DEALER, $result); + } + + /** + * + */ + public function testProtectedApiParseBinderMessage_ParsesBinderMessage() + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $model = $this->createBinder(); + $result = $this->callProtectedMethod($model, 'parseBinderMessage', [[ 'from', 'id', 'type', 'msg1', 'msg2' ]]); + + $this->assertSame([ 'id', 'type', [ 'msg1', 'msg2' ] ], $result); + } + + /** + * + */ + public function testProtectedApiParseConnectorMessage_ParsesConnectorMessage() + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $model = $this->createConnector(); + $result = $this->callProtectedMethod($model, 'parseConnectorMessage', [[ 'from', 'id', 'type', 'msg1', 'msg2' ]]); + + $this->assertSame([ 'id', 'type', [ 'msg1', 'msg2' ] ], $result); + } + + /** + * + */ + public function testProtectedApiPrepareBinderMessage_PreparesBinderMessage() + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $model = $this->createBinder(); + $this->setProtectedProperty($model, 'id', 'model'); + $result = $this->callProtectedMethod($model, 'prepareBinderMessage', [ 'id', 'type' ]); + + $this->assertSame([ 'id', 'model', 'type' ], $result); + } + + /** + * + */ + public function testProtectedApiPrepareConnectorMessage_PreparesConnectorMessage() + { + if (!class_exists('ZMQ')) + { + $this->markTestSkipped('This test is not able to be run without ZMQ extension.'); + } + + $model = $this->createConnector(); + $this->setProtectedProperty($model, 'id', 'model'); + $result = $this->callProtectedMethod($model, 'prepareConnectorMessage', [ 'id', 'type' ]); + + $this->assertSame([ 'id', 'model', 'type' ], $result); + } + + /** + * @param mixed[] $params + * @param string[]|null $methods + * @return ZmqDealer|\PHPUnit_Framework_MockObject_MockObject + */ + public function createConnector($params = [], $methods = []) + { + $params['type'] = ZmqModel::CONNECTOR; + return $this->createModel($params, $methods); + } + + /** + * @param mixed[] $params + * @param string[]|null $methods + * @return ZmqDealer|\PHPUnit_Framework_MockObject_MockObject + */ + public function createBinder($params = [], $methods = []) + { + $params['type'] = ZmqModel::BINDER; + return $this->createModel($params, $methods); + } + + /** + * @param mixed[] $params + * @param string[] $methods + * @return ZmqDealer|\PHPUnit_Framework_MockObject_MockObject + */ + public function createModel($params = [], $methods = []) + { + $params = array_merge( + [ + 'id' => 'model', + 'endpoint' => 'tcp://127.0.0.1:2080', + 'type' => $params['type'], + 'host' => 'model', + + ], + $params + ); + + $methods = array_merge( + [ + 'removeEventListener' + ], + $methods + ); + + $loop = $this->getMock(Loop::class, $methods, [], '', false); + $model = $this->getMock(ZmqDealer::class, $methods, [ $loop, $params ], '', false); + $model + ->expects($this->any()) + ->method('removeEventListener'); + + $this->model = $model; + + return $model; + } + + /** + * @param string[]|null $methods + * @return Loop|\PHPUnit_Framework_MockObject_MockObject + */ + public function createLoop($methods = null) + { + $mock = $this->getMock(Loop::class, $methods, [], '', false); + + $this->setProtectedProperty($this->model, 'loop', $mock); + + return $mock; + } +} diff --git a/test/_Simulation/Event.php b/test/_Simulation/Event.php new file mode 100644 index 0000000..7d9f59f --- /dev/null +++ b/test/_Simulation/Event.php @@ -0,0 +1,42 @@ +name = $name; + $this->data = $data; + } + + /** + * @return mixed + */ + public function name() + { + return $this->name; + } + + /** + * @return mixed[] + */ + public function data() + { + return $this->data; + } +} diff --git a/test/_Simulation/EventCollection.php b/test/_Simulation/EventCollection.php new file mode 100644 index 0000000..2f9e07a --- /dev/null +++ b/test/_Simulation/EventCollection.php @@ -0,0 +1,8 @@ +loop = $loop; + $this->scenario = function() {}; + $this->events = []; + $this->failureMessage = null; + $this->startCallback = function() {}; + $this->stopCallback = function() {}; + $this->stopFlags = false; + } + + /** + * + */ + public function __destruct() + { + $this->stop(); + + unset($loop); + unset($this->scenario); + unset($this->events); + unset($this->failureMessage); + unset($this->startCallback); + unset($this->stopCallback); + unset($this->stopFlags); + } + + /** + * @param callable(SimulationInterface) $scenario + */ + public function setScenario(callable $scenario) + { + $this->scenario = $scenario; + } + + /** + * @return callable(SimulationInterface)|null $scenario + */ + public function getScenario() + { + return $this->scenario; + } + + /** + * @return LoopInterface + */ + public function getLoop() + { + return $this->loop; + } + + /** + * + */ + public function begin() + { + $this->start(); + } + + /** + * + */ + public function done() + { + $this->stop(); + } + + /** + * + */ + public function fail($message) + { + $this->failureMessage = $message; + $this->stop(); + } + + /** + * @param mixed $expected + * @param mixed $actual + * @param string $message + */ + public function assertSame($expected, $actual, $message = "Assertion failed, expected \"%s\" got \"%s\"") + { + if ($expected !== $actual) + { + $stringExpected = (is_object($expected) || is_array($expected)) ? json_encode($expected) : (string) $expected; + $stringActual = (is_object($actual) || is_array($actual)) ? json_encode($actual) : (string) $actual; + + $this->fail(sprintf($message, $stringExpected, $stringActual)); + } + } + + /** + * @param string $name + * @param mixed $data + */ + public function expect($name, $data = []) + { + $this->events[] = new Event($name, $data); + } + + /** + * @return Event[] + */ + public function getExpectations() + { + return $this->events; + } + + /** + * @param callable $callable + */ + public function onStart(callable $callable) + { + $this->startCallback = $callable; + } + + /** + * @param callable $callable + */ + public function onStop(callable $callable) + { + $this->stopCallback = $callable; + } + + /** + * @param string $model + * @param mixed[] $config + * @return object + */ + public function reflect($model, $config = []) + { + foreach ($config as $key=>$value) + { + if ($value === 'Dazzle\Loop\Loop' || $value === 'Dazzle\Loop\LoopInterface') + { + $config[$key] = $this->getLoop(); + } + } + + return (new ReflectionClass($model))->newInstanceArgs($config); + } + + /** + * @throws Exception + */ + private function start() + { + $sim = $this; + + $scenario = $this->scenario; + $scenario($sim); + + if ($this->stopFlags === true) + { + return; + } + + $onStart = $this->startCallback; + $loop = $this->loop; + + $loop->onStart(function() use($sim, $onStart) { + $onStart($sim); + }); + $loop->addTimer(5, function() use($sim) { + $sim->fail('Timeout for test has been reached.'); + }); + + $loop->start(); + + if ($sim->failureMessage !== null) + { + throw new Exception($sim->failureMessage); + } + } + + /** + * + */ + private function stop() + { + if ($this->loop !== null && $this->loop->isRunning()) + { + $this->loop->stop(); + } + + if ($this->stopFlags === false) + { + $callable = $this->stopCallback; + $callable($this); + } + + $this->stopFlags = true; + } +} diff --git a/test/_Simulation/SimulationInterface.php b/test/_Simulation/SimulationInterface.php new file mode 100644 index 0000000..6c608f7 --- /dev/null +++ b/test/_Simulation/SimulationInterface.php @@ -0,0 +1,55 @@ +