From 1a1b2e1021ff52a0ea710ffee298df5f373d5d15 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Fri, 3 Feb 2017 21:28:48 -0500 Subject: [PATCH 01/19] Preliminary marble testing --- test/Rx/Functional/FunctionalTestCase.php | 81 +++++++++++++++++++++++ test/Rx/Functional/MarbleTest.php | 64 ++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 test/Rx/Functional/MarbleTest.php diff --git a/test/Rx/Functional/FunctionalTestCase.php b/test/Rx/Functional/FunctionalTestCase.php index 0393c35d..8ab17edf 100644 --- a/test/Rx/Functional/FunctionalTestCase.php +++ b/test/Rx/Functional/FunctionalTestCase.php @@ -3,10 +3,13 @@ namespace Rx\Functional; use PHPUnit_Framework_ExpectationFailedException; +use Rx\Notification; +use Rx\Observable; use Rx\Scheduler\VirtualTimeScheduler; use Rx\TestCase; use Rx\Testing\ColdObservable; use Rx\Testing\HotObservable; +use Rx\Testing\Recorded; use Rx\Testing\Subscription; use Rx\Testing\TestScheduler; @@ -90,6 +93,16 @@ protected function createColdObservable(array $events) return new ColdObservable($this->scheduler, $events); } + protected function createCold(string $events, array $eventMap = [], \Exception $customError = null) + { + return new ColdObservable($this->scheduler, $this->convertMarblesToMessages($events, $eventMap, $customError)); + } + + protected function createHot(string $events, array $eventMap = [], \Exception $customError = null) + { + return new HotObservable($this->scheduler, $this->convertMarblesToMessages($events, $eventMap, $customError, 200)); + } + protected function createHotObservable(array $events) { return new HotObservable($this->scheduler, $events); @@ -99,4 +112,72 @@ protected function createTestScheduler() { return new TestScheduler(); } + + protected function convertMarblesToMessages(string $marbles, array $eventMap = [], \Exception $customError = null, $subscribePoint = 0) + { + var_dump($eventMap); + /** @var Recorded $events */ + $events = []; + $zero = 0; + + for ($i = 0; $i < strlen($marbles); $i++) { + switch ($marbles[$i]) { + case '-': // nothing + continue; + case '#': // error + $events[] = onError($i * 10, $customError ?? new \Exception()); + continue; + case '^': // this is the subscribe point + $zero = $i * 10; + continue; + case '|': // + $events[] = onCompleted($i * 10); + continue; + default: + $events[] = onNext($i * 10, isset($eventMap[$i]) ? $eventMap[$i] : $marbles[$i]); + continue; + } + } + + if ($subscribePoint != 0) { // zero is cold + $oldEvents = $events; + $events = []; + /** @var Recorded $event */ + foreach ($oldEvents as $event) { + $events[] = new Recorded($event->getTime() + $subscribePoint - $zero, $event->getValue()); + } + } + + return $events; + } + + protected function convertMessagesToMarbles($messages) + { + $output = ''; + $lastTime = 199; + + /** @var Recorded $message */ + foreach ($messages as $message) { + $time = $message->getTime(); + /** @var Notification $value */ + $value = $message->getValue(); + $output .= str_repeat('-', ($time - $lastTime - 1) / 10); + + $lastTime = $time; + + $value->accept( + function ($x) use (&$output) { + $output .= $x; + }, + function (\Exception $e) use (&$output) { + $output .= '#'; + }, + function () use (&$output) { + $output .= '|'; + } + ); + } + + return $output; + } } diff --git a/test/Rx/Functional/MarbleTest.php b/test/Rx/Functional/MarbleTest.php new file mode 100644 index 00000000..925b38b5 --- /dev/null +++ b/test/Rx/Functional/MarbleTest.php @@ -0,0 +1,64 @@ +createCold('----1----3---|'); + $h = $this->createHot('--^--a--b---|'); + + $result = $this->scheduler->startWithCreate(function () use ($c) { + return $c; + }); + + $this->assertMessages([ + onNext(240, '1'), + onNext(290, '3'), + onCompleted(330) + ], $result->getMessages()); + + var_dump($result->getMessages()); + } + + public function testHotMarble() + { + $h = $this->createHot('-1-^--a--b---|'); + + $result = $this->scheduler->startWithCreate(function () use ($h) { + return $h; + }); + + $this->assertMessages([ + onNext(230, 'a'), + onNext(260, 'b'), + onCompleted(300) + ], $result->getMessages()); + + var_dump($result->getMessages()); + } + + public function testMessageConversion() + { + $messages = [ + onNext(230, 'a'), + onNext(260, 'b'), + onCompleted(300) + ]; + + $this->assertEquals('--a--b---|', $this->convertMessagesToMarbles($messages)); + } + + public function testSomethingElse() + { + $cold = '--1--2--|'; + $expected = '--2--3--|'; + + $results = $this->scheduler->startWithCreate(function () use ($cold) { + return $this->createCold($cold)->map(function ($x) { return $x + 1; }); + }); + + $this->assertEquals($expected, $this->convertMessagesToMarbles($results->getMessages())); + } +} \ No newline at end of file From fbeff95aac12a36844992dc3f88078668cbfb9dc Mon Sep 17 00:00:00 2001 From: Martin Sikora Date: Fri, 17 Feb 2017 19:37:09 +0100 Subject: [PATCH 02/19] more marble testing features --- test/Rx/Functional/FunctionalTestCase.php | 68 +++++++++++- test/Rx/Functional/MarbleTest.php | 121 +++++++++++++++++++++- test/Rx/MarbleDiagramError.php | 7 ++ 3 files changed, 186 insertions(+), 10 deletions(-) create mode 100644 test/Rx/MarbleDiagramError.php diff --git a/test/Rx/Functional/FunctionalTestCase.php b/test/Rx/Functional/FunctionalTestCase.php index 8ab17edf..3cc5f8fd 100644 --- a/test/Rx/Functional/FunctionalTestCase.php +++ b/test/Rx/Functional/FunctionalTestCase.php @@ -2,11 +2,9 @@ namespace Rx\Functional; -use PHPUnit_Framework_ExpectationFailedException; use Rx\Notification; -use Rx\Observable; -use Rx\Scheduler\VirtualTimeScheduler; use Rx\TestCase; +use Rx\MarbleDiagramError; use Rx\Testing\ColdObservable; use Rx\Testing\HotObservable; use Rx\Testing\Recorded; @@ -23,6 +21,10 @@ public function setup() $this->scheduler = $this->createTestScheduler(); } + /** + * @param Recorded[] $expected + * @param Recorded[] $recorded + */ public function assertMessages(array $expected, array $recorded) { if (count($expected) !== count($recorded)) { @@ -38,6 +40,27 @@ public function assertMessages(array $expected, array $recorded) $this->assertTrue(true); // success } + /** + * @param Recorded[] $expected + * @param Recorded[] $recorded + */ + public function assertMessagesNotEqual(array $expected, array $recorded) + { + if (count($expected) !== count($recorded)) { + $this->assertTrue(true); + return; + } + + for ($i = 0, $count = count($expected); $i < $count; $i++) { + if (!$expected[$i]->equals($recorded[$i])) { + $this->assertTrue(true); + return; + } + } + + $this->fail('Expected messages do match the actual'); + } + public function assertSubscription(HotObservable $observable, Subscription $expected) { $subscriptionCount = count($observable->getSubscriptions()); @@ -115,13 +138,13 @@ protected function createTestScheduler() protected function convertMarblesToMessages(string $marbles, array $eventMap = [], \Exception $customError = null, $subscribePoint = 0) { - var_dump($eventMap); /** @var Recorded $events */ $events = []; $zero = 0; for ($i = 0; $i < strlen($marbles); $i++) { switch ($marbles[$i]) { + case ' ': case '-': // nothing continue; case '#': // error @@ -134,7 +157,8 @@ protected function convertMarblesToMessages(string $marbles, array $eventMap = [ $events[] = onCompleted($i * 10); continue; default: - $events[] = onNext($i * 10, isset($eventMap[$i]) ? $eventMap[$i] : $marbles[$i]); + $eventKey = $marbles[$i]; + $events[] = onNext($i * 10, isset($eventMap[$eventKey]) ? $eventMap[$eventKey] : $marbles[$i]); continue; } } @@ -180,4 +204,38 @@ function () use (&$output) { return $output; } + + protected function convertMarblesToSubscriptions(string $marbles, $startTime = 0) + { + $latestSubscription = null; + $events = []; + + for ($i = 0; $i < strlen($marbles); $i++) { + switch ($marbles[$i]) { + case ' ': + case '-': + break; + case '^': // subscribe + if ($latestSubscription) { + throw new MarbleDiagramError('Trying to subscribe before unsubscribing the previous subscription.'); + } + $latestSubscription = $startTime + $i * 10; + continue; + case '!': // unsubscribe + if (!$latestSubscription) { + throw new MarbleDiagramError('Trying to unsubscribe before subscribing.'); + } + $events[] = new Subscription($latestSubscription, $startTime + $i * 10); + $latestSubscription = null; + break; + default: + throw new MarbleDiagramError('Only "^" and "!" markers are allowed in this diagram.'); + continue; + } + } + if ($latestSubscription) { + $events[] = new Subscription($latestSubscription); + } + return $events; + } } diff --git a/test/Rx/Functional/MarbleTest.php b/test/Rx/Functional/MarbleTest.php index 925b38b5..b7bb7512 100644 --- a/test/Rx/Functional/MarbleTest.php +++ b/test/Rx/Functional/MarbleTest.php @@ -2,12 +2,13 @@ namespace Rx\Functional; +use Rx\Testing\Subscription; + class MarbleTest extends FunctionalTestCase { public function testColdMarble() { $c = $this->createCold('----1----3---|'); - $h = $this->createHot('--^--a--b---|'); $result = $this->scheduler->startWithCreate(function () use ($c) { return $c; @@ -18,8 +19,6 @@ public function testColdMarble() onNext(290, '3'), onCompleted(330) ], $result->getMessages()); - - var_dump($result->getMessages()); } public function testHotMarble() @@ -35,8 +34,17 @@ public function testHotMarble() onNext(260, 'b'), onCompleted(300) ], $result->getMessages()); + } - var_dump($result->getMessages()); + public function testColdMarbleWithEqualMessages() + { + $marbles1 = '----1-^--a--b---| '; + $marbles2 = ' 1-^--a--b---|---'; + + $this->assertMessages( + $this->convertMarblesToMessages($marbles1), + $this->convertMarblesToMessages($marbles2) + ); } public function testMessageConversion() @@ -47,7 +55,7 @@ public function testMessageConversion() onCompleted(300) ]; - $this->assertEquals('--a--b---|', $this->convertMessagesToMarbles($messages)); + $this->assertEquals('---a--b---|', $this->convertMessagesToMarbles($messages)); } public function testSomethingElse() @@ -61,4 +69,107 @@ public function testSomethingElse() $this->assertEquals($expected, $this->convertMessagesToMarbles($results->getMessages())); } + + public function testMarbleValues() + { + $marbles = '--a--b--c--|'; + $values = [ + 'a' => 42, + 'b' => 'xyz', + 'c' => [1, 2, 3], + ]; + + $messages = $this->convertMarblesToMessages($marbles, $values); + + $this->assertMessages([ + onNext(20, 42), + onNext(50, 'xyz'), + onNext(80, [1, 2, 3]), + onCompleted(110) + ], $messages); + } + + public function testMarbleValuesDontMatch() + { + $marbles = '--a--b--c--|'; + $values = [ + 'a' => 42, + 'b' => 'xyz', + 'c' => [1, 2, 3], + ]; + + $messages = $this->convertMarblesToMessages($marbles, $values); + + $this->assertMessagesNotEqual([ + onNext(20, 42), + onNext(50, 'xyz'), + onNext(80, [1, 5, 3]), // wrong + onCompleted(110) + ], $messages); + } + + public function testMarbleWithMissingValues() + { + $marbles = '--a--b--c--|'; + $values = [ + 'a' => 42, + ]; + + $messages = $this->convertMarblesToMessages($marbles, $values); + + $this->assertMessages([ + onNext(20, 42), + onNext(50, 'b'), + onNext(80, 'c'), + onCompleted(110) + ], $messages); + } + + public function testSubscriptions() + { + $marbles = '--^-----!---^!--'; + + $subscriptions = $this->convertMarblesToSubscriptions($marbles, 200); + $this->assertSubscriptions([ + new Subscription(220, 280), + new Subscription(320, 330), + ], $subscriptions); + } + + public function testSubscriptionsMissingUnsubscribeMarker() + { + $marbles = '--^--'; + + $subscriptions = $this->convertMarblesToSubscriptions($marbles); + $this->assertSubscriptions([ + new Subscription(20), + ], $subscriptions); + } + + /** + * @expectedException \Rx\MarbleDiagramError + */ + public function testSubscriptionsInvalidMarkers() + { + $marbles = '--^--a--!-'; + $this->convertMarblesToSubscriptions($marbles); + } + + /** + * @expectedException \Rx\MarbleDiagramError + */ + public function testSubscriptionsMultipleSubscribeMarkers() + { + $marbles = '--^-^---!-'; + $this->convertMarblesToSubscriptions($marbles); + } + + /** + * @expectedException \Rx\MarbleDiagramError + */ + public function testSubscriptionsMultipleUnsubscribeMarkers() + { + $marbles = '--^---!-!-'; + $this->convertMarblesToSubscriptions($marbles); + } } \ No newline at end of file diff --git a/test/Rx/MarbleDiagramError.php b/test/Rx/MarbleDiagramError.php new file mode 100644 index 00000000..f931adfe --- /dev/null +++ b/test/Rx/MarbleDiagramError.php @@ -0,0 +1,7 @@ + Date: Fri, 17 Feb 2017 19:49:19 +0100 Subject: [PATCH 03/19] fix correct type for str_repeat() --- test/Rx/Functional/FunctionalTestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Rx/Functional/FunctionalTestCase.php b/test/Rx/Functional/FunctionalTestCase.php index 3cc5f8fd..67db26ef 100644 --- a/test/Rx/Functional/FunctionalTestCase.php +++ b/test/Rx/Functional/FunctionalTestCase.php @@ -185,7 +185,7 @@ protected function convertMessagesToMarbles($messages) $time = $message->getTime(); /** @var Notification $value */ $value = $message->getValue(); - $output .= str_repeat('-', ($time - $lastTime - 1) / 10); + $output .= str_repeat('-', floor(($time - $lastTime - 1) / 10)); $lastTime = $time; From 637d3fe9ed5c509b3e99b192e1d7e46d9f6d6587 Mon Sep 17 00:00:00 2001 From: Martin Sikora Date: Fri, 17 Feb 2017 23:23:55 +0100 Subject: [PATCH 04/19] added marker groups support --- test/Rx/Functional/FunctionalTestCase.php | 67 ++++++++++++++------ test/Rx/Functional/MarbleTest.php | 75 +++++++++++++++++++++++ 2 files changed, 122 insertions(+), 20 deletions(-) diff --git a/test/Rx/Functional/FunctionalTestCase.php b/test/Rx/Functional/FunctionalTestCase.php index 67db26ef..d4162b25 100644 --- a/test/Rx/Functional/FunctionalTestCase.php +++ b/test/Rx/Functional/FunctionalTestCase.php @@ -16,6 +16,8 @@ abstract class FunctionalTestCase extends TestCase /** @var TestScheduler */ protected $scheduler; + const TIME_FACTOR = 10; + public function setup() { $this->scheduler = $this->createTestScheduler(); @@ -140,38 +142,48 @@ protected function convertMarblesToMessages(string $marbles, array $eventMap = [ { /** @var Recorded $events */ $events = []; - $zero = 0; + $groupTime = -1; + + // calculate subscription time + $timeOffset = $subscribePoint; + $subMarker = strpos($marbles, '^'); + if ($subMarker !== false) { + $timeOffset -= $subMarker * self::TIME_FACTOR; + } for ($i = 0; $i < strlen($marbles); $i++) { + $now = $groupTime === -1 ? $timeOffset + $i * self::TIME_FACTOR : $groupTime++; + switch ($marbles[$i]) { case ' ': + case '^': case '-': // nothing continue; case '#': // error - $events[] = onError($i * 10, $customError ?? new \Exception()); + $events[] = onError($now, $customError ?? new \Exception()); continue; - case '^': // this is the subscribe point - $zero = $i * 10; + case '|': + $events[] = onCompleted($now); continue; - case '|': // - $events[] = onCompleted($i * 10); + case '(': + if ($groupTime !== -1) { + throw new MarbleDiagramError('We\'re already inside a group'); + } + $groupTime = $now; + continue; + case ')': + if ($groupTime === -1) { + throw new MarbleDiagramError('We\'re already outside of a group'); + } + $groupTime = -1; continue; default: $eventKey = $marbles[$i]; - $events[] = onNext($i * 10, isset($eventMap[$eventKey]) ? $eventMap[$eventKey] : $marbles[$i]); + $events[] = onNext($now, isset($eventMap[$eventKey]) ? $eventMap[$eventKey] : $marbles[$i]); continue; } } - if ($subscribePoint != 0) { // zero is cold - $oldEvents = $events; - $events = []; - /** @var Recorded $event */ - foreach ($oldEvents as $event) { - $events[] = new Recorded($event->getTime() + $subscribePoint - $zero, $event->getValue()); - } - } - return $events; } @@ -185,7 +197,7 @@ protected function convertMessagesToMarbles($messages) $time = $message->getTime(); /** @var Notification $value */ $value = $message->getValue(); - $output .= str_repeat('-', floor(($time - $lastTime - 1) / 10)); + $output .= str_repeat('-', (int)(($time - $lastTime - 1) / self::TIME_FACTOR)); $lastTime = $time; @@ -209,23 +221,38 @@ protected function convertMarblesToSubscriptions(string $marbles, $startTime = 0 { $latestSubscription = null; $events = []; + $groupTime = -1; for ($i = 0; $i < strlen($marbles); $i++) { + $now = $groupTime === -1 ? $startTime + $i * self::TIME_FACTOR : $groupTime++; + switch ($marbles[$i]) { case ' ': case '-': - break; + continue; + case '(': + if ($groupTime !== -1) { + throw new MarbleDiagramError('We\'re already inside a group'); + } + $groupTime = $now; + continue; + case ')': + if ($groupTime === -1) { + throw new MarbleDiagramError('We\'re already outside of a group'); + } + $groupTime = -1; + continue; case '^': // subscribe if ($latestSubscription) { throw new MarbleDiagramError('Trying to subscribe before unsubscribing the previous subscription.'); } - $latestSubscription = $startTime + $i * 10; + $latestSubscription = $now; continue; case '!': // unsubscribe if (!$latestSubscription) { throw new MarbleDiagramError('Trying to unsubscribe before subscribing.'); } - $events[] = new Subscription($latestSubscription, $startTime + $i * 10); + $events[] = new Subscription($latestSubscription, $now); $latestSubscription = null; break; default: diff --git a/test/Rx/Functional/MarbleTest.php b/test/Rx/Functional/MarbleTest.php index b7bb7512..c3378270 100644 --- a/test/Rx/Functional/MarbleTest.php +++ b/test/Rx/Functional/MarbleTest.php @@ -2,6 +2,7 @@ namespace Rx\Functional; +use Rx\Exception\Exception; use Rx\Testing\Subscription; class MarbleTest extends FunctionalTestCase @@ -125,6 +126,69 @@ public function testMarbleWithMissingValues() ], $messages); } + public function testGroupedMarbleValues() + { + $marbles = '---(abc)--|'; + + $messages = $this->convertMarblesToMessages($marbles); + + $this->assertMessages([ + onNext(30, 'a'), + onNext(31, 'b'), + onNext(32, 'c'), + onCompleted(100) + ], $messages); + } + + public function testMultipleGroupedMarbleValues() + { + $marbles = '--(abc)---(dfa)--|'; + $values = [ + 'a' => 42, + ]; + + $messages = $this->convertMarblesToMessages($marbles, $values); + + $this->assertMessages([ + onNext(20, 42), + onNext(21, 'b'), + onNext(22, 'c'), + onNext(100, 'd'), + onNext(101, 'f'), + onNext(102, 42), + onCompleted(170) + ], $messages); + } + + public function testGroupedMarkerAndComplete() + { + $marbles = '--a---b--(c|)'; + + $messages = $this->convertMarblesToMessages($marbles); + + $this->assertMessages([ + onNext(20, 'a'), + onNext(60, 'b'), + onNext(90, 'c'), + onCompleted(91) + ], $messages); + } + + public function testGroupedMarkerAndError() + { + $marbles = '--a---(b#)--c--|'; + + $messages = $this->convertMarblesToMessages($marbles); + + $this->assertMessages([ + onNext(20, 'a'), + onNext(60, 'b'), + onError(61, new \Exception()), + onNext(120, 'c'), + onCompleted(150), + ], $messages); + } + public function testSubscriptions() { $marbles = '--^-----!---^!--'; @@ -146,6 +210,17 @@ public function testSubscriptionsMissingUnsubscribeMarker() ], $subscriptions); } + public function testSubscriptionsGroup() + { + $marbles = '--(^!)'; + + $subscriptions = $this->convertMarblesToSubscriptions($marbles); + + $this->assertSubscriptions([ + new Subscription(20, 21), + ], $subscriptions); + } + /** * @expectedException \Rx\MarbleDiagramError */ From 654614aa6d350485883972795f398105d43464f4 Mon Sep 17 00:00:00 2001 From: David Dan Date: Fri, 17 Feb 2017 20:48:39 -0500 Subject: [PATCH 05/19] Added expectObservable and expectSubscriptions to FunctionalTestCase --- test/Rx/Functional/FunctionalTestCase.php | 52 +++++++++++++++++++++++ test/Rx/Functional/MarbleTest.php | 16 +++++++ 2 files changed, 68 insertions(+) diff --git a/test/Rx/Functional/FunctionalTestCase.php b/test/Rx/Functional/FunctionalTestCase.php index d4162b25..9e66b6e8 100644 --- a/test/Rx/Functional/FunctionalTestCase.php +++ b/test/Rx/Functional/FunctionalTestCase.php @@ -3,6 +3,7 @@ namespace Rx\Functional; use Rx\Notification; +use Rx\Observable; use Rx\TestCase; use Rx\MarbleDiagramError; use Rx\Testing\ColdObservable; @@ -265,4 +266,55 @@ protected function convertMarblesToSubscriptions(string $marbles, $startTime = 0 } return $events; } + + + public function expectObservable(Observable $observable): FunctionalTestCase + { + $results = $this->scheduler->startWithCreate(function () use ($observable) { + return $observable; + }); + + $messages = $results->getMessages(); + + return new class($messages) extends FunctionalTestCase + { + private $messages; + + public function __construct(array $messages) + { + parent::__construct(); + $this->messages = $messages; + } + + public function toBe(string $expected, array $values = []) + { + $this->assertEquals( + $this->convertMarblesToMessages($expected, $values, null, 200), + $this->messages + ); + } + }; + } + + public function expectSubscriptions(array $subscriptions): FunctionalTestCase + { + return new class($subscriptions) extends FunctionalTestCase + { + private $subscriptions; + + public function __construct(array $subscriptions) + { + parent::__construct(); + $this->subscriptions = $subscriptions; + } + + public function toBe(string $subscriptionsMarbles) + { + $this->assertEquals( + $this->convertMarblesToSubscriptions($subscriptionsMarbles, 200), + $this->subscriptions + ); + } + }; + } } diff --git a/test/Rx/Functional/MarbleTest.php b/test/Rx/Functional/MarbleTest.php index c3378270..9de3a472 100644 --- a/test/Rx/Functional/MarbleTest.php +++ b/test/Rx/Functional/MarbleTest.php @@ -247,4 +247,20 @@ public function testSubscriptionsMultipleUnsubscribeMarkers() $marbles = '--^---!-!-'; $this->convertMarblesToSubscriptions($marbles); } + + public function testMapMarble() + { + $cold = '--1--2--|'; + $subs = '^ !'; + $expected = '--x--y--|'; + + $e1 = $this->createCold($cold); + + $r = $e1->map(function ($x) { + return $x + 1; + }); + + $this->expectObservable($r)->toBe($expected, ['x' => 2, 'y' => 3]); + $this->expectSubscriptions($e1->getSubscriptions())->toBe($subs); + } } \ No newline at end of file From 4b9191265ae77c5cf9a3579fedefe97c22e10bb8 Mon Sep 17 00:00:00 2001 From: David Dan Date: Fri, 17 Feb 2017 21:37:29 -0500 Subject: [PATCH 06/19] Added exception message support to expectObservable()->toBe() --- test/Rx/Functional/FunctionalTestCase.php | 10 ++++++---- test/Rx/Functional/MarbleTest.php | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/test/Rx/Functional/FunctionalTestCase.php b/test/Rx/Functional/FunctionalTestCase.php index 9e66b6e8..c45700bc 100644 --- a/test/Rx/Functional/FunctionalTestCase.php +++ b/test/Rx/Functional/FunctionalTestCase.php @@ -268,7 +268,7 @@ protected function convertMarblesToSubscriptions(string $marbles, $startTime = 0 } - public function expectObservable(Observable $observable): FunctionalTestCase + public function expectObservable(Observable $observable) { $results = $this->scheduler->startWithCreate(function () use ($observable) { return $observable; @@ -286,17 +286,19 @@ public function __construct(array $messages) $this->messages = $messages; } - public function toBe(string $expected, array $values = []) + public function toBe(string $expected, array $values = [], string $errorMessage = null) { + $error = $errorMessage ? new \Exception($errorMessage) : null; + $this->assertEquals( - $this->convertMarblesToMessages($expected, $values, null, 200), + $this->convertMarblesToMessages($expected, $values, $error, 200), $this->messages ); } }; } - public function expectSubscriptions(array $subscriptions): FunctionalTestCase + public function expectSubscriptions(array $subscriptions) { return new class($subscriptions) extends FunctionalTestCase { diff --git a/test/Rx/Functional/MarbleTest.php b/test/Rx/Functional/MarbleTest.php index 9de3a472..b889a425 100644 --- a/test/Rx/Functional/MarbleTest.php +++ b/test/Rx/Functional/MarbleTest.php @@ -263,4 +263,20 @@ public function testMapMarble() $this->expectObservable($r)->toBe($expected, ['x' => 2, 'y' => 3]); $this->expectSubscriptions($e1->getSubscriptions())->toBe($subs); } + + public function testMapErrorMarble() + { + $cold = '--x--|'; + $subs = '^ ! '; + $expected = '--# '; + + $e1 = $this->createCold($cold, ['x' => 42]); + + $r = $e1->map(function ($x) { + throw new \Exception('too bad'); + }); + + $this->expectObservable($r)->toBe($expected, [], 'too bad'); + $this->expectSubscriptions($e1->getSubscriptions())->toBe($subs); + } } \ No newline at end of file From b2a3ae64cf1f841b971de11e6e028a928ef87ef7 Mon Sep 17 00:00:00 2001 From: David Dan Date: Fri, 17 Feb 2017 22:09:55 -0500 Subject: [PATCH 07/19] Added dispose support to expectObservable() --- test/Rx/Functional/FunctionalTestCase.php | 39 ++++++++++++++++++++--- test/Rx/Functional/MarbleTest.php | 17 ++++++++++ 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/test/Rx/Functional/FunctionalTestCase.php b/test/Rx/Functional/FunctionalTestCase.php index c45700bc..17e5a9fd 100644 --- a/test/Rx/Functional/FunctionalTestCase.php +++ b/test/Rx/Functional/FunctionalTestCase.php @@ -267,12 +267,43 @@ protected function convertMarblesToSubscriptions(string $marbles, $startTime = 0 return $events; } + protected function convertMarblesToDisposeTime(string $marbles, $startTime = 0) + { + $groupTime = -1; + $disposeAt = 1000; + + for ($i = 0; $i < strlen($marbles); $i++) { + $now = $groupTime === -1 ? $startTime + $i * self::TIME_FACTOR : $groupTime++; + + switch ($marbles[$i]) { + case ' ': + continue; + case '!': // unsubscribe + $disposeAt = $now; + break; + default: + throw new MarbleDiagramError('Only " " and "!" markers are allowed in this diagram.'); + continue; + } + } - public function expectObservable(Observable $observable) + return $disposeAt; + } + + public function expectObservable(Observable $observable, string $disposeMarble = null) { - $results = $this->scheduler->startWithCreate(function () use ($observable) { - return $observable; - }); + + if ($disposeMarble) { + $disposeAt = $this->convertMarblesToDisposeTime($disposeMarble, 200); + + $results = $this->scheduler->startWithDispose(function () use ($observable) { + return $observable; + }, $disposeAt); + } else { + $results = $this->scheduler->startWithCreate(function () use ($observable) { + return $observable; + }); + } $messages = $results->getMessages(); diff --git a/test/Rx/Functional/MarbleTest.php b/test/Rx/Functional/MarbleTest.php index b889a425..17bdba1f 100644 --- a/test/Rx/Functional/MarbleTest.php +++ b/test/Rx/Functional/MarbleTest.php @@ -279,4 +279,21 @@ public function testMapErrorMarble() $this->expectObservable($r)->toBe($expected, [], 'too bad'); $this->expectSubscriptions($e1->getSubscriptions())->toBe($subs); } + + public function testMapDisposeMarble() + { + $cold = '--1--2--3--|'; + $unsub = ' ! '; + $subs = '^ ! '; + $expected = '--x--y- '; + + $e1 = $this->createCold($cold); + + $r = $e1->map(function ($x) { + return $x . '!'; + }); + + $this->expectObservable($r, $unsub)->toBe($expected, ['x' => '1!', 'y' => '2!']); + $this->expectSubscriptions($e1->getSubscriptions())->toBe($subs); + } } \ No newline at end of file From 3ea6d38db51e021bc6eaeb228f7820b218df8c59 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Mon, 20 Feb 2017 09:32:21 -0500 Subject: [PATCH 08/19] Rename MarbleDiagramError to MarbleDiagramException --- test/Rx/Functional/FunctionalTestCase.php | 18 +++++++++--------- test/Rx/Functional/MarbleTest.php | 6 +++--- test/Rx/MarbleDiagramError.php | 7 ------- test/Rx/MarbleDiagramException.php | 7 +++++++ 4 files changed, 19 insertions(+), 19 deletions(-) delete mode 100644 test/Rx/MarbleDiagramError.php create mode 100644 test/Rx/MarbleDiagramException.php diff --git a/test/Rx/Functional/FunctionalTestCase.php b/test/Rx/Functional/FunctionalTestCase.php index 17e5a9fd..3e8feabb 100644 --- a/test/Rx/Functional/FunctionalTestCase.php +++ b/test/Rx/Functional/FunctionalTestCase.php @@ -5,7 +5,7 @@ use Rx\Notification; use Rx\Observable; use Rx\TestCase; -use Rx\MarbleDiagramError; +use Rx\MarbleDiagramException; use Rx\Testing\ColdObservable; use Rx\Testing\HotObservable; use Rx\Testing\Recorded; @@ -168,13 +168,13 @@ protected function convertMarblesToMessages(string $marbles, array $eventMap = [ continue; case '(': if ($groupTime !== -1) { - throw new MarbleDiagramError('We\'re already inside a group'); + throw new MarbleDiagramException('We\'re already inside a group'); } $groupTime = $now; continue; case ')': if ($groupTime === -1) { - throw new MarbleDiagramError('We\'re already outside of a group'); + throw new MarbleDiagramException('We\'re already outside of a group'); } $groupTime = -1; continue; @@ -233,31 +233,31 @@ protected function convertMarblesToSubscriptions(string $marbles, $startTime = 0 continue; case '(': if ($groupTime !== -1) { - throw new MarbleDiagramError('We\'re already inside a group'); + throw new MarbleDiagramException('We\'re already inside a group'); } $groupTime = $now; continue; case ')': if ($groupTime === -1) { - throw new MarbleDiagramError('We\'re already outside of a group'); + throw new MarbleDiagramException('We\'re already outside of a group'); } $groupTime = -1; continue; case '^': // subscribe if ($latestSubscription) { - throw new MarbleDiagramError('Trying to subscribe before unsubscribing the previous subscription.'); + throw new MarbleDiagramException('Trying to subscribe before unsubscribing the previous subscription.'); } $latestSubscription = $now; continue; case '!': // unsubscribe if (!$latestSubscription) { - throw new MarbleDiagramError('Trying to unsubscribe before subscribing.'); + throw new MarbleDiagramException('Trying to unsubscribe before subscribing.'); } $events[] = new Subscription($latestSubscription, $now); $latestSubscription = null; break; default: - throw new MarbleDiagramError('Only "^" and "!" markers are allowed in this diagram.'); + throw new MarbleDiagramException('Only "^" and "!" markers are allowed in this diagram.'); continue; } } @@ -282,7 +282,7 @@ protected function convertMarblesToDisposeTime(string $marbles, $startTime = 0) $disposeAt = $now; break; default: - throw new MarbleDiagramError('Only " " and "!" markers are allowed in this diagram.'); + throw new MarbleDiagramException('Only " " and "!" markers are allowed in this diagram.'); continue; } } diff --git a/test/Rx/Functional/MarbleTest.php b/test/Rx/Functional/MarbleTest.php index 17bdba1f..9cd1f9ec 100644 --- a/test/Rx/Functional/MarbleTest.php +++ b/test/Rx/Functional/MarbleTest.php @@ -222,7 +222,7 @@ public function testSubscriptionsGroup() } /** - * @expectedException \Rx\MarbleDiagramError + * @expectedException \Rx\MarbleDiagramException */ public function testSubscriptionsInvalidMarkers() { @@ -231,7 +231,7 @@ public function testSubscriptionsInvalidMarkers() } /** - * @expectedException \Rx\MarbleDiagramError + * @expectedException \Rx\MarbleDiagramException */ public function testSubscriptionsMultipleSubscribeMarkers() { @@ -240,7 +240,7 @@ public function testSubscriptionsMultipleSubscribeMarkers() } /** - * @expectedException \Rx\MarbleDiagramError + * @expectedException \Rx\MarbleDiagramException */ public function testSubscriptionsMultipleUnsubscribeMarkers() { diff --git a/test/Rx/MarbleDiagramError.php b/test/Rx/MarbleDiagramError.php deleted file mode 100644 index f931adfe..00000000 --- a/test/Rx/MarbleDiagramError.php +++ /dev/null @@ -1,7 +0,0 @@ - Date: Mon, 20 Feb 2017 10:01:05 -0500 Subject: [PATCH 09/19] Grouped marbles now happen at the same time --- test/Rx/Functional/FunctionalTestCase.php | 2 +- test/Rx/Functional/MarbleTest.php | 32 ++++++++++++++++------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/test/Rx/Functional/FunctionalTestCase.php b/test/Rx/Functional/FunctionalTestCase.php index 3e8feabb..d4860466 100644 --- a/test/Rx/Functional/FunctionalTestCase.php +++ b/test/Rx/Functional/FunctionalTestCase.php @@ -153,7 +153,7 @@ protected function convertMarblesToMessages(string $marbles, array $eventMap = [ } for ($i = 0; $i < strlen($marbles); $i++) { - $now = $groupTime === -1 ? $timeOffset + $i * self::TIME_FACTOR : $groupTime++; + $now = $groupTime === -1 ? $timeOffset + $i * self::TIME_FACTOR : $groupTime; switch ($marbles[$i]) { case ' ': diff --git a/test/Rx/Functional/MarbleTest.php b/test/Rx/Functional/MarbleTest.php index 9cd1f9ec..28330aa5 100644 --- a/test/Rx/Functional/MarbleTest.php +++ b/test/Rx/Functional/MarbleTest.php @@ -134,8 +134,8 @@ public function testGroupedMarbleValues() $this->assertMessages([ onNext(30, 'a'), - onNext(31, 'b'), - onNext(32, 'c'), + onNext(30, 'b'), + onNext(30, 'c'), onCompleted(100) ], $messages); } @@ -151,11 +151,11 @@ public function testMultipleGroupedMarbleValues() $this->assertMessages([ onNext(20, 42), - onNext(21, 'b'), - onNext(22, 'c'), + onNext(20, 'b'), + onNext(20, 'c'), onNext(100, 'd'), - onNext(101, 'f'), - onNext(102, 42), + onNext(100, 'f'), + onNext(100, 42), onCompleted(170) ], $messages); } @@ -170,7 +170,7 @@ public function testGroupedMarkerAndComplete() onNext(20, 'a'), onNext(60, 'b'), onNext(90, 'c'), - onCompleted(91) + onCompleted(90) ], $messages); } @@ -183,7 +183,7 @@ public function testGroupedMarkerAndError() $this->assertMessages([ onNext(20, 'a'), onNext(60, 'b'), - onError(61, new \Exception()), + onError(60, new \Exception()), onNext(120, 'c'), onCompleted(150), ], $messages); @@ -217,7 +217,7 @@ public function testSubscriptionsGroup() $subscriptions = $this->convertMarblesToSubscriptions($marbles); $this->assertSubscriptions([ - new Subscription(20, 21), + new Subscription(20, 20), ], $subscriptions); } @@ -296,4 +296,16 @@ public function testMapDisposeMarble() $this->expectObservable($r, $unsub)->toBe($expected, ['x' => '1!', 'y' => '2!']); $this->expectSubscriptions($e1->getSubscriptions())->toBe($subs); } -} \ No newline at end of file + + public function testCountMarble() + { + $cold = '--a--b--c--|'; + $subs = '^ !'; + $expected = '-----------(x|)'; + + $e1 = $this->createCold($cold); + + $this->expectObservable($e1->count())->toBe($expected, ['x' => 3]); + $this->expectSubscriptions($e1->getSubscriptions())->toBe($subs); + } +} From a619b721b35f408c49b4725cd1190445820c3d37 Mon Sep 17 00:00:00 2001 From: Matt Bonneau Date: Mon, 20 Feb 2017 10:09:57 -0500 Subject: [PATCH 10/19] Grouped subscription marbles happen at the same time --- test/Rx/Functional/FunctionalTestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Rx/Functional/FunctionalTestCase.php b/test/Rx/Functional/FunctionalTestCase.php index d4860466..6aad7fc6 100644 --- a/test/Rx/Functional/FunctionalTestCase.php +++ b/test/Rx/Functional/FunctionalTestCase.php @@ -225,7 +225,7 @@ protected function convertMarblesToSubscriptions(string $marbles, $startTime = 0 $groupTime = -1; for ($i = 0; $i < strlen($marbles); $i++) { - $now = $groupTime === -1 ? $startTime + $i * self::TIME_FACTOR : $groupTime++; + $now = $groupTime === -1 ? $startTime + $i * self::TIME_FACTOR : $groupTime; switch ($marbles[$i]) { case ' ': From 6e3554139933b00f0ebb499515b991e700a068eb Mon Sep 17 00:00:00 2001 From: David Dan Date: Mon, 20 Feb 2017 10:21:04 -0500 Subject: [PATCH 11/19] Added interfaces so we get type hinting for `toBe` --- test/Rx/Functional/FunctionalTestCase.php | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/test/Rx/Functional/FunctionalTestCase.php b/test/Rx/Functional/FunctionalTestCase.php index 6aad7fc6..ce7cf984 100644 --- a/test/Rx/Functional/FunctionalTestCase.php +++ b/test/Rx/Functional/FunctionalTestCase.php @@ -290,9 +290,8 @@ protected function convertMarblesToDisposeTime(string $marbles, $startTime = 0) return $disposeAt; } - public function expectObservable(Observable $observable, string $disposeMarble = null) + public function expectObservable(Observable $observable, string $disposeMarble = null): ExpectObservableToBe { - if ($disposeMarble) { $disposeAt = $this->convertMarblesToDisposeTime($disposeMarble, 200); @@ -307,7 +306,7 @@ public function expectObservable(Observable $observable, string $disposeMarble = $messages = $results->getMessages(); - return new class($messages) extends FunctionalTestCase + return new class($messages) extends FunctionalTestCase implements ExpectObservableToBe { private $messages; @@ -329,9 +328,9 @@ public function toBe(string $expected, array $values = [], string $errorMessage }; } - public function expectSubscriptions(array $subscriptions) + public function expectSubscriptions(array $subscriptions): ExpectSubscriptionsToBe { - return new class($subscriptions) extends FunctionalTestCase + return new class($subscriptions) extends FunctionalTestCase implements ExpectSubscriptionsToBe { private $subscriptions; @@ -351,3 +350,13 @@ public function toBe(string $subscriptionsMarbles) }; } } + +interface ExpectSubscriptionsToBe +{ + public function toBe(string $subscriptionsMarbles); +} + +interface ExpectObservableToBe +{ + public function toBe(string $expected, array $values = [], string $errorMessage = null); +} From 416034c6dc74296da64c12e703977482d4798385 Mon Sep 17 00:00:00 2001 From: David Dan Date: Mon, 20 Feb 2017 10:29:41 -0500 Subject: [PATCH 12/19] Fixed continues to actually continue the loop --- test/Rx/Functional/FunctionalTestCase.php | 24 +++++++++++------------ 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/test/Rx/Functional/FunctionalTestCase.php b/test/Rx/Functional/FunctionalTestCase.php index ce7cf984..859335cb 100644 --- a/test/Rx/Functional/FunctionalTestCase.php +++ b/test/Rx/Functional/FunctionalTestCase.php @@ -159,29 +159,29 @@ protected function convertMarblesToMessages(string $marbles, array $eventMap = [ case ' ': case '^': case '-': // nothing - continue; + continue 2; case '#': // error $events[] = onError($now, $customError ?? new \Exception()); - continue; + continue 2; case '|': $events[] = onCompleted($now); - continue; + continue 2; case '(': if ($groupTime !== -1) { throw new MarbleDiagramException('We\'re already inside a group'); } $groupTime = $now; - continue; + continue 2; case ')': if ($groupTime === -1) { throw new MarbleDiagramException('We\'re already outside of a group'); } $groupTime = -1; - continue; + continue 2; default: $eventKey = $marbles[$i]; $events[] = onNext($now, isset($eventMap[$eventKey]) ? $eventMap[$eventKey] : $marbles[$i]); - continue; + continue 2; } } @@ -230,25 +230,25 @@ protected function convertMarblesToSubscriptions(string $marbles, $startTime = 0 switch ($marbles[$i]) { case ' ': case '-': - continue; + continue 2; case '(': if ($groupTime !== -1) { throw new MarbleDiagramException('We\'re already inside a group'); } $groupTime = $now; - continue; + continue 2; case ')': if ($groupTime === -1) { throw new MarbleDiagramException('We\'re already outside of a group'); } $groupTime = -1; - continue; + continue 2; case '^': // subscribe if ($latestSubscription) { throw new MarbleDiagramException('Trying to subscribe before unsubscribing the previous subscription.'); } $latestSubscription = $now; - continue; + continue 2; case '!': // unsubscribe if (!$latestSubscription) { throw new MarbleDiagramException('Trying to unsubscribe before subscribing.'); @@ -258,7 +258,6 @@ protected function convertMarblesToSubscriptions(string $marbles, $startTime = 0 break; default: throw new MarbleDiagramException('Only "^" and "!" markers are allowed in this diagram.'); - continue; } } if ($latestSubscription) { @@ -277,13 +276,12 @@ protected function convertMarblesToDisposeTime(string $marbles, $startTime = 0) switch ($marbles[$i]) { case ' ': - continue; + continue 2; case '!': // unsubscribe $disposeAt = $now; break; default: throw new MarbleDiagramException('Only " " and "!" markers are allowed in this diagram.'); - continue; } } From c1d2d4aedd98381b86588282736070480e28d0e5 Mon Sep 17 00:00:00 2001 From: David Dan Date: Fri, 10 Mar 2017 18:18:00 -0500 Subject: [PATCH 13/19] Cherry picked higher-order observable test from v1 --- .../OnNextObservableNotification.php | 56 +++++ src/Testing/ColdObservable.php | 15 +- src/Testing/HotObservable.php | 15 +- src/Testing/MockHigherOrderObserver.php | 7 + src/Testing/MockObserver.php | 20 +- test/Rx/Testing/RecordedTest.php | 218 +++++++++++++++++- test/helper-functions.php | 28 ++- 7 files changed, 327 insertions(+), 32 deletions(-) create mode 100644 src/Notification/OnNextObservableNotification.php create mode 100644 src/Testing/MockHigherOrderObserver.php diff --git a/src/Notification/OnNextObservableNotification.php b/src/Notification/OnNextObservableNotification.php new file mode 100644 index 00000000..717b3cbe --- /dev/null +++ b/src/Notification/OnNextObservableNotification.php @@ -0,0 +1,56 @@ +observer = new MockHigherOrderObserver($scheduler, $scheduler->getClock()); + + $value->subscribe($this->observer); + + $scheduler->start(); + } + + public function equals($other): bool + { + $messages1 = $this->getMessages(); + /** @var OnNextObservableNotification $other */ + $messages2 = $other->getMessages(); + + if (count($messages1) != count($messages2)) { + return false; + } + + for ($i = 0; $i < count($messages1); $i++) { + if (!$messages1[$i]->equals($messages2[$i])) { + return false; + } + } + + return true; + } + + public function getMessages() + { + return $this->observer->getMessages(); + } + + public function __toString(): string + { + return '[' . implode(', ', $this->getMessages()) . ']'; + } +} \ No newline at end of file diff --git a/src/Testing/ColdObservable.php b/src/Testing/ColdObservable.php index fce41240..1c04d619 100644 --- a/src/Testing/ColdObservable.php +++ b/src/Testing/ColdObservable.php @@ -26,13 +26,16 @@ public function __construct(TestScheduler $scheduler, array $messages = []) protected function _subscribe(ObserverInterface $observer): DisposableInterface { - $this->subscriptions[] = new Subscription($this->scheduler->getClock()); - $index = count($this->subscriptions) - 1; - $currentObservable = $this; $disposable = new CompositeDisposable(); $scheduler = $this->scheduler; $isDisposed = false; + $index = null; + + if (!($observer instanceof MockHigherOrderObserver)) { + $this->subscriptions[] = new Subscription($this->scheduler->getClock()); + $index = count($this->subscriptions) - 1; + } foreach ($this->messages as $message) { $notification = $message->getValue(); @@ -53,8 +56,10 @@ protected function _subscribe(ObserverInterface $observer): DisposableInterface $subscriptions = &$this->subscriptions; return new CallbackDisposable(function () use (&$currentObservable, $index, $observer, $scheduler, &$subscriptions, &$isDisposed) { - $isDisposed = true; - $subscriptions[$index] = new Subscription($subscriptions[$index]->getSubscribed(), $scheduler->getClock()); + $isDisposed = true; + if (!($observer instanceof MockHigherOrderObserver)) { + $subscriptions[$index] = new Subscription($subscriptions[$index]->getSubscribed(), $scheduler->getClock()); + } }); } diff --git a/src/Testing/HotObservable.php b/src/Testing/HotObservable.php index a86bdfed..61e91022 100644 --- a/src/Testing/HotObservable.php +++ b/src/Testing/HotObservable.php @@ -48,17 +48,22 @@ protected function _subscribe(ObserverInterface $observer): DisposableInterface { $currentObservable = $this; - $this->observers[] = $observer; - $this->subscriptions[] = new Subscription($this->scheduler->getClock()); + $this->observers[] = $observer; + $subscriptions = &$this->subscriptions; + $index = null; - $subscriptions = &$this->subscriptions; + if (!($observer instanceof MockHigherOrderObserver)) { + $this->subscriptions[] = new Subscription($this->scheduler->getClock()); + $index = count($this->subscriptions) - 1; + } - $index = count($this->subscriptions) - 1; $scheduler = $this->scheduler; return new CallbackDisposable(function () use (&$currentObservable, $index, $observer, $scheduler, &$subscriptions) { $currentObservable->removeObserver($observer); - $subscriptions[$index] = new Subscription($subscriptions[$index]->getSubscribed(), $scheduler->getClock()); + if (!($observer instanceof MockHigherOrderObserver)) { + $subscriptions[$index] = new Subscription($subscriptions[$index]->getSubscribed(), $scheduler->getClock()); + } }); } diff --git a/src/Testing/MockHigherOrderObserver.php b/src/Testing/MockHigherOrderObserver.php new file mode 100644 index 00000000..ec526144 --- /dev/null +++ b/src/Testing/MockHigherOrderObserver.php @@ -0,0 +1,7 @@ +scheduler = $scheduler; + $this->startTime = $startTime; } public function onNext($value) { + if ($value instanceof Observable) { + $notification = new OnNextObservableNotification($value, $this->scheduler); + } else { + $notification = new OnNextNotification($value); + } + $this->messages[] = new Recorded( - $this->scheduler->getClock(), - new OnNextNotification($value) + $this->scheduler->getClock() - $this->startTime, + $notification ); } public function onError(\Throwable $error) { $this->messages[] = new Recorded( - $this->scheduler->getClock(), + $this->scheduler->getClock() - $this->startTime, new OnErrorNotification($error) ); } @@ -41,7 +51,7 @@ public function onError(\Throwable $error) public function onCompleted() { $this->messages[] = new Recorded( - $this->scheduler->getClock(), + $this->scheduler->getClock() - $this->startTime, new OnCompletedNotification() ); } diff --git a/test/Rx/Testing/RecordedTest.php b/test/Rx/Testing/RecordedTest.php index 10c88443..923e7680 100644 --- a/test/Rx/Testing/RecordedTest.php +++ b/test/Rx/Testing/RecordedTest.php @@ -4,24 +4,220 @@ namespace Rx\Testing; -use Rx\TestCase; +use Rx\Functional\FunctionalTestCase; +use Rx\Observable; -class RecordedTest extends TestCase +class RecordedTest extends FunctionalTestCase { - public function testRecordedWillUseStrictCompareIfNoEqualsMethod() + + /** + * @test + */ + public function compare_basic_types() + { + $r1 = new Recorded(100, 42); + $r2 = new Recorded(100, 42); + $this->assertTrue($r1->equals($r2)); + + $r3 = new Recorded(100, 42); + $r4 = new Recorded(150, 42); + $this->assertFalse($r3->equals($r4)); + + $r5 = new Recorded(100, 42); + $r6 = new Recorded(100, 24); + $this->assertFalse($r5->equals($r6)); + } + + /** + * @test + */ + public function compare_cold_observables() + { + $records1 = onNext(100, $this->createColdObservable([ + onNext(150, 1), + onNext(200, 2), + onNext(250, 3), + onCompleted(300), + ])); + $records2 = onNext(100, $this->createColdObservable([ + onNext(150, 1), + onNext(200, 2), + onNext(250, 3), + onCompleted(300), + ])); + + $this->assertTrue($records1->equals($records2)); + $this->assertMessages([$records1], [$records2]); + } + + /** + * @test + */ + public function automatic_mock_observer_doesnt_create_loggable_subscription() + { + $inner = $this->createColdObservable([ + onNext(150, 1), + onNext(200, 2), + onNext(250, 3), + onCompleted(300), + ]); + // Thanks to OnNextObservableNotification this internally creates + // an instance of MockHigherOrderObserver which is not logged + // by the ColdObservable::subscribe() method. + $records = onNext(100, $inner); + + $expected = onNext(100, $this->createColdObservable([ + onNext(150, 1), + onNext(200, 2), + onNext(250, 3), + onCompleted(300), + ])); + + $this->assertTrue($records->equals($expected)); + $this->assertMessages([$records], [$expected]); + + $this->assertSubscriptions([], $inner->getSubscriptions()); + } + + /** + * @test + */ + public function compare_with_range_cold_observable() + { + $records1 = onNext(100, Observable::range(1, 3)); + $records2 = onNext(100, $this->createColdObservable([ + onNext(1, 1), + onNext(2, 2), + onNext(3, 3), + onCompleted(4), + ])); + + $this->assertMessages([$records1], [$records2]); + } + + /** + * @test + */ + public function compare_with_delayed_range_cold_observable() { - $recorded1 = new Recorded(1, 5); - $recorded2 = new Recorded(1, "5"); - $recorded3 = new Recorded(1, 5); + $records1 = onNext(100, $this->createColdObservable([ + onNext(50, 1), + onNext(100, 2), + onNext(150, 3), + onCompleted(200) + ])->delay(100)); - $this->assertFalse($recorded1->equals($recorded2)); - $this->assertTrue($recorded1->equals($recorded3)); + $records2 = onNext(100, $this->createColdObservable([ + onNext(150, 1), + onNext(200, 2), + onNext(250, 3), + onCompleted(300), + ])); + + $this->assertMessages([$records1], [$records2]); } - public function testRecordedToString() + /** + * @test + */ + public function observables_at_different_time_with_same_records_arent_equal() { - $recorded = new Recorded(1, 5); + $records1 = onNext(50, $this->createColdObservable([ + onNext(50, 1), + onNext(100, 2), + ])); + $records2 = onNext(100, $this->createColdObservable([ + onNext(50, 1), + onNext(100, 2), + ])); - $this->assertEquals("5@1", $recorded->__toString()); + $this->assertFalse($records1->equals($records2)); + $this->assertEquals('[OnNext(1)@50, OnNext(2)@100]@50', $records1->__toString()); } + + /** + * @test + */ + public function observables_with_inner_records_at_different_time_arent_equal() + { + $records1 = onNext(100, $this->createColdObservable([ + onNext(50, 1), + onNext(150, 2), + ])); + $records2 = onNext(100, $this->createColdObservable([ + onNext(50, 1), + onNext(100, 2), + ])); + + $this->assertFalse($records1->equals($records2)); + $this->assertEquals('[OnNext(1)@50, OnNext(2)@150]@100', $records1->__toString()); + } + + /** + * @test + */ + public function observables_with_more_nested_inner_observables() + { + $records1 = onNext(100, $this->createColdObservable([ + onNext(50, 1), + onNext(100, 2), + onNext(150, $this->createColdObservable([ + onNext(10, 3), + onNext(20, 4), + onNext(30, $this->createColdObservable([ + onNext(10, 5), + onNext(20, 6), + ])), + ])->delay(100)), + ])); + $records2 = onNext(100, $this->createColdObservable([ + onNext(50, 1), + onNext(100, 2), + onNext(150, $this->createColdObservable([ + onNext(110, 3), + onNext(120, 4), + onNext(130, $this->createColdObservable([ + onNext(10, 5), + onNext(20, 6), + ])), + ])), + ])); + + $this->assertTrue($records1->equals($records2)); + $this->assertMessages([$records1], [$records2]); + } + + /** + * @test + */ + public function observables_with_difference_in_nested_inner_observables() + { + $records1 = onNext(100, $this->createColdObservable([ + onNext(50, 1), + onNext(100, 2), + onNext(150, $this->createColdObservable([ + onNext(10, 3), + onNext(20, 4), + onNext(30, $this->createColdObservable([ + onNext(10, 5), + onNext(20, 6), + ])), + ])->delay(100)), + ])); + $records2 = onNext(100, $this->createColdObservable([ + onNext(50, 1), + onNext(100, 2), + onNext(150, $this->createColdObservable([ + onNext(110, 3), + onNext(120, 4), + onNext(130, $this->createColdObservable([ + onNext(10, 5), + onNext(20, 42), // this is wrong + ])), + ])), + ])); + + $this->assertFalse($records1->equals($records2)); + } + } diff --git a/test/helper-functions.php b/test/helper-functions.php index 81ae91b7..f4c2ce77 100644 --- a/test/helper-functions.php +++ b/test/helper-functions.php @@ -2,28 +2,44 @@ declare(strict_types = 1); +use Rx\Observable; use Rx\Testing\Recorded; use Rx\Testing\Subscription; use Rx\Notification\OnCompletedNotification; use Rx\Notification\OnErrorNotification; use Rx\Notification\OnNextNotification; +use Rx\Notification\OnNextObservableNotification; -function onError(int $dueTime, $error, callable $comparer = null) { +function onError(int $dueTime, $error, callable $comparer = null) +{ return new Recorded($dueTime, new OnErrorNotification($error), $comparer); } -function onNext(int $dueTime, $value, callable $comparer = null) { - return new Recorded($dueTime, new OnNextNotification($value), $comparer); +function onNext(int $dueTime, $value, callable $comparer = null) +{ + if ($value instanceof Observable) { + try { + $notification = new OnNextObservableNotification($value); + } catch (Throwable $e) { + $notification = new OnErrorNotification($e); + } + } else { + $notification = new OnNextNotification($value); + } + return new Recorded($dueTime, $notification, $comparer); } -function onCompleted(int $dueTime, callable $comparer = null) { +function onCompleted(int $dueTime, callable $comparer = null) +{ return new Recorded($dueTime, new OnCompletedNotification(), $comparer); } -function subscribe(int $start, int $end = PHP_INT_MAX) { +function subscribe(int $start, int $end = PHP_INT_MAX) +{ return new Subscription($start, $end); } -function RxIdentity($x) { +function RxIdentity($x) +{ return $x; } From 08b0e0af9fc35ee972cc78ce18da23e80fe4be73 Mon Sep 17 00:00:00 2001 From: Martin Sikora Date: Sat, 11 Mar 2017 17:00:17 +0100 Subject: [PATCH 14/19] use single instance of TestScheduler in all Notification objects --- .../OnNextObservableNotification.php | 10 ++--- test/Rx/Functional/FunctionalTestCase.php | 16 ++++++++ test/Rx/Testing/RecordedTest.php | 41 +++++++++++++++---- test/helper-functions.php | 3 +- 4 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/Notification/OnNextObservableNotification.php b/src/Notification/OnNextObservableNotification.php index 717b3cbe..7a79792d 100644 --- a/src/Notification/OnNextObservableNotification.php +++ b/src/Notification/OnNextObservableNotification.php @@ -3,6 +3,8 @@ namespace Rx\Notification; use Rx\Observable; +use Rx\Testing\ColdObservable; +use Rx\Testing\HotObservable; use Rx\Testing\MockHigherOrderObserver; use Rx\Testing\TestScheduler; @@ -11,18 +13,12 @@ class OnNextObservableNotification extends OnNextNotification /** @var MockHigherOrderObserver */ private $observer; - public function __construct(Observable $value, TestScheduler $scheduler = null) + public function __construct(Observable $value, TestScheduler $scheduler) { parent::__construct($value); - if (!$scheduler) { - $scheduler = new TestScheduler(); - } $this->observer = new MockHigherOrderObserver($scheduler, $scheduler->getClock()); - $value->subscribe($this->observer); - - $scheduler->start(); } public function equals($other): bool diff --git a/test/Rx/Functional/FunctionalTestCase.php b/test/Rx/Functional/FunctionalTestCase.php index db7771b2..464b5c93 100644 --- a/test/Rx/Functional/FunctionalTestCase.php +++ b/test/Rx/Functional/FunctionalTestCase.php @@ -19,11 +19,19 @@ abstract class FunctionalTestCase extends TestCase /** @var TestScheduler */ protected $scheduler; + /** @var TestScheduler */ + static protected $globalScheduler; + const TIME_FACTOR = 10; public function setup() { $this->scheduler = $this->createTestScheduler(); + self::$globalScheduler = $this->scheduler; + } + + static function getScheduler() { + return self::$globalScheduler; } /** @@ -32,6 +40,8 @@ public function setup() */ public function assertMessages(array $expected, array $recorded) { + $this->scheduler->start(); + if (count($expected) !== count($recorded)) { $this->fail(sprintf('Expected message count %d does not match actual count %d.', count($expected), count($recorded))); } @@ -51,6 +61,8 @@ public function assertMessages(array $expected, array $recorded) */ public function assertMessagesNotEqual(array $expected, array $recorded) { + $this->scheduler->start(); + if (count($expected) !== count($recorded)) { $this->assertTrue(true); return; @@ -68,6 +80,8 @@ public function assertMessagesNotEqual(array $expected, array $recorded) public function assertSubscription(HotObservable $observable, Subscription $expected) { + $this->scheduler->start(); + $subscriptionCount = count($observable->getSubscriptions()); if ($subscriptionCount === 0) { @@ -89,6 +103,8 @@ public function assertSubscription(HotObservable $observable, Subscription $expe public function assertSubscriptions(array $expected, array $recorded) { + $this->scheduler->start(); + if (count($expected) !== count($recorded)) { $this->fail(sprintf('Expected subscription count %d does not match actual count %d.', count($expected), count($recorded))); } diff --git a/test/Rx/Testing/RecordedTest.php b/test/Rx/Testing/RecordedTest.php index 923e7680..98ac4c0a 100644 --- a/test/Rx/Testing/RecordedTest.php +++ b/test/Rx/Testing/RecordedTest.php @@ -46,8 +46,30 @@ public function compare_cold_observables() onCompleted(300), ])); - $this->assertTrue($records1->equals($records2)); $this->assertMessages([$records1], [$records2]); + $this->assertTrue($records1->equals($records2)); + } + + /** + * @test + */ + public function compare_cold_observables_not_equal() + { + $records1 = onNext(100, $this->createColdObservable([ + onNext(150, 1), + onNext(200, 42), // this is wrong + onNext(250, 3), + onCompleted(300), + ])); + $records2 = onNext(100, $this->createColdObservable([ + onNext(150, 1), + onNext(200, 2), + onNext(250, 3), + onCompleted(300), + ])); + + $this->assertMessagesNotEqual([$records1], [$records2]); + $this->assertFalse($records1->equals($records2)); } /** @@ -73,8 +95,8 @@ public function automatic_mock_observer_doesnt_create_loggable_subscription() onCompleted(300), ])); - $this->assertTrue($records->equals($expected)); $this->assertMessages([$records], [$expected]); + $this->assertTrue($records->equals($expected)); $this->assertSubscriptions([], $inner->getSubscriptions()); } @@ -84,7 +106,7 @@ public function automatic_mock_observer_doesnt_create_loggable_subscription() */ public function compare_with_range_cold_observable() { - $records1 = onNext(100, Observable::range(1, 3)); + $records1 = onNext(100, Observable::range(1, 3, $this->scheduler)); $records2 = onNext(100, $this->createColdObservable([ onNext(1, 1), onNext(2, 2), @@ -105,7 +127,7 @@ public function compare_with_delayed_range_cold_observable() onNext(100, 2), onNext(150, 3), onCompleted(200) - ])->delay(100)); + ])->delay(100, $this->scheduler)); $records2 = onNext(100, $this->createColdObservable([ onNext(150, 1), @@ -131,6 +153,8 @@ public function observables_at_different_time_with_same_records_arent_equal() onNext(100, 2), ])); + $this->scheduler->start(); + $this->assertFalse($records1->equals($records2)); $this->assertEquals('[OnNext(1)@50, OnNext(2)@100]@50', $records1->__toString()); } @@ -149,6 +173,8 @@ public function observables_with_inner_records_at_different_time_arent_equal() onNext(100, 2), ])); + $this->scheduler->start(); + $this->assertFalse($records1->equals($records2)); $this->assertEquals('[OnNext(1)@50, OnNext(2)@150]@100', $records1->__toString()); } @@ -168,7 +194,7 @@ public function observables_with_more_nested_inner_observables() onNext(10, 5), onNext(20, 6), ])), - ])->delay(100)), + ])->delay(100, $this->scheduler)), ])); $records2 = onNext(100, $this->createColdObservable([ onNext(50, 1), @@ -183,8 +209,8 @@ public function observables_with_more_nested_inner_observables() ])), ])); - $this->assertTrue($records1->equals($records2)); $this->assertMessages([$records1], [$records2]); + $this->assertTrue($records1->equals($records2)); } /** @@ -202,7 +228,7 @@ public function observables_with_difference_in_nested_inner_observables() onNext(10, 5), onNext(20, 6), ])), - ])->delay(100)), + ])->delay(100, $this->scheduler)), ])); $records2 = onNext(100, $this->createColdObservable([ onNext(50, 1), @@ -217,6 +243,7 @@ public function observables_with_difference_in_nested_inner_observables() ])), ])); + $this->scheduler->start(); $this->assertFalse($records1->equals($records2)); } diff --git a/test/helper-functions.php b/test/helper-functions.php index f4c2ce77..bced97ab 100644 --- a/test/helper-functions.php +++ b/test/helper-functions.php @@ -5,6 +5,7 @@ use Rx\Observable; use Rx\Testing\Recorded; use Rx\Testing\Subscription; +use Rx\Functional\FunctionalTestCase; use Rx\Notification\OnCompletedNotification; use Rx\Notification\OnErrorNotification; use Rx\Notification\OnNextNotification; @@ -19,7 +20,7 @@ function onNext(int $dueTime, $value, callable $comparer = null) { if ($value instanceof Observable) { try { - $notification = new OnNextObservableNotification($value); + $notification = new OnNextObservableNotification($value, FunctionalTestCase::getScheduler()); } catch (Throwable $e) { $notification = new OnErrorNotification($e); } From eb2466707975b39fa68f3023b55771a6f0aac1cf Mon Sep 17 00:00:00 2001 From: David Dan Date: Sat, 11 Mar 2017 17:56:41 -0500 Subject: [PATCH 15/19] Materialize the inner observable when converting the OnNextNotification to a string, so we only need to check once if the value is an instance of an observable --- src/Notification/OnNextNotification.php | 6 +++ .../OnNextObservableNotification.php | 52 ------------------- src/Testing/ColdObservable.php | 21 +++----- src/Testing/HotObservable.php | 22 +++----- src/Testing/MockHigherOrderObserver.php | 7 --- src/Testing/MockObserver.php | 10 +--- src/Testing/TestScheduler.php | 1 - test/helper-functions.php | 33 ++++++++---- 8 files changed, 44 insertions(+), 108 deletions(-) delete mode 100644 src/Notification/OnNextObservableNotification.php delete mode 100644 src/Testing/MockHigherOrderObserver.php diff --git a/src/Notification/OnNextNotification.php b/src/Notification/OnNextNotification.php index 57f5f946..17a1d5df 100644 --- a/src/Notification/OnNextNotification.php +++ b/src/Notification/OnNextNotification.php @@ -4,6 +4,7 @@ namespace Rx\Notification; +use Rx\Observable; use Rx\ObserverInterface; use Rx\Notification; @@ -30,6 +31,11 @@ protected function doAccept($onNext, $onError, $onCompleted) public function __toString(): string { + if ($this->value instanceof Observable) { + $messages = materializeObservable($this->value); + return '[' . implode(', ', $messages) . ']'; + } + return 'OnNext(' . json_encode($this->value) . ')'; } diff --git a/src/Notification/OnNextObservableNotification.php b/src/Notification/OnNextObservableNotification.php deleted file mode 100644 index 7a79792d..00000000 --- a/src/Notification/OnNextObservableNotification.php +++ /dev/null @@ -1,52 +0,0 @@ -observer = new MockHigherOrderObserver($scheduler, $scheduler->getClock()); - $value->subscribe($this->observer); - } - - public function equals($other): bool - { - $messages1 = $this->getMessages(); - /** @var OnNextObservableNotification $other */ - $messages2 = $other->getMessages(); - - if (count($messages1) != count($messages2)) { - return false; - } - - for ($i = 0; $i < count($messages1); $i++) { - if (!$messages1[$i]->equals($messages2[$i])) { - return false; - } - } - - return true; - } - - public function getMessages() - { - return $this->observer->getMessages(); - } - - public function __toString(): string - { - return '[' . implode(', ', $this->getMessages()) . ']'; - } -} \ No newline at end of file diff --git a/src/Testing/ColdObservable.php b/src/Testing/ColdObservable.php index 1c04d619..df536525 100644 --- a/src/Testing/ColdObservable.php +++ b/src/Testing/ColdObservable.php @@ -26,16 +26,12 @@ public function __construct(TestScheduler $scheduler, array $messages = []) protected function _subscribe(ObserverInterface $observer): DisposableInterface { - $currentObservable = $this; - $disposable = new CompositeDisposable(); - $scheduler = $this->scheduler; - $isDisposed = false; - $index = null; - - if (!($observer instanceof MockHigherOrderObserver)) { - $this->subscriptions[] = new Subscription($this->scheduler->getClock()); - $index = count($this->subscriptions) - 1; - } + $currentObservable = $this; + $disposable = new CompositeDisposable(); + $scheduler = $this->scheduler; + $isDisposed = false; + $this->subscriptions[] = new Subscription($this->scheduler->getClock()); + $index = count($this->subscriptions) - 1; foreach ($this->messages as $message) { $notification = $message->getValue(); @@ -57,9 +53,8 @@ protected function _subscribe(ObserverInterface $observer): DisposableInterface return new CallbackDisposable(function () use (&$currentObservable, $index, $observer, $scheduler, &$subscriptions, &$isDisposed) { $isDisposed = true; - if (!($observer instanceof MockHigherOrderObserver)) { - $subscriptions[$index] = new Subscription($subscriptions[$index]->getSubscribed(), $scheduler->getClock()); - } + + $subscriptions[$index] = new Subscription($subscriptions[$index]->getSubscribed(), $scheduler->getClock()); }); } diff --git a/src/Testing/HotObservable.php b/src/Testing/HotObservable.php index 61e91022..bd0b2a26 100644 --- a/src/Testing/HotObservable.php +++ b/src/Testing/HotObservable.php @@ -46,24 +46,16 @@ public function __construct(TestScheduler $scheduler, array $messages) protected function _subscribe(ObserverInterface $observer): DisposableInterface { - $currentObservable = $this; - - $this->observers[] = $observer; - $subscriptions = &$this->subscriptions; - $index = null; - - if (!($observer instanceof MockHigherOrderObserver)) { - $this->subscriptions[] = new Subscription($this->scheduler->getClock()); - $index = count($this->subscriptions) - 1; - } - - $scheduler = $this->scheduler; + $currentObservable = $this; + $this->observers[] = $observer; + $subscriptions = &$this->subscriptions; + $this->subscriptions[] = new Subscription($this->scheduler->getClock()); + $index = count($this->subscriptions) - 1; + $scheduler = $this->scheduler; return new CallbackDisposable(function () use (&$currentObservable, $index, $observer, $scheduler, &$subscriptions) { $currentObservable->removeObserver($observer); - if (!($observer instanceof MockHigherOrderObserver)) { - $subscriptions[$index] = new Subscription($subscriptions[$index]->getSubscribed(), $scheduler->getClock()); - } + $subscriptions[$index] = new Subscription($subscriptions[$index]->getSubscribed(), $scheduler->getClock()); }); } diff --git a/src/Testing/MockHigherOrderObserver.php b/src/Testing/MockHigherOrderObserver.php deleted file mode 100644 index ec526144..00000000 --- a/src/Testing/MockHigherOrderObserver.php +++ /dev/null @@ -1,7 +0,0 @@ -scheduler); - } else { - $notification = new OnNextNotification($value); - } - $this->messages[] = new Recorded( $this->scheduler->getClock() - $this->startTime, - $notification + new OnNextNotification($value) ); } diff --git a/src/Testing/TestScheduler.php b/src/Testing/TestScheduler.php index 4d7312b6..d9838127 100644 --- a/src/Testing/TestScheduler.php +++ b/src/Testing/TestScheduler.php @@ -7,7 +7,6 @@ use Rx\Disposable\EmptyDisposable; use Rx\DisposableInterface; use Rx\ObserverInterface; -use Rx\Scheduler; use Rx\Scheduler\VirtualTimeScheduler; class TestScheduler extends VirtualTimeScheduler diff --git a/test/helper-functions.php b/test/helper-functions.php index bced97ab..28c9ee17 100644 --- a/test/helper-functions.php +++ b/test/helper-functions.php @@ -9,7 +9,7 @@ use Rx\Notification\OnCompletedNotification; use Rx\Notification\OnErrorNotification; use Rx\Notification\OnNextNotification; -use Rx\Notification\OnNextObservableNotification; +use Rx\Timestamped; function onError(int $dueTime, $error, callable $comparer = null) { @@ -18,16 +18,7 @@ function onError(int $dueTime, $error, callable $comparer = null) function onNext(int $dueTime, $value, callable $comparer = null) { - if ($value instanceof Observable) { - try { - $notification = new OnNextObservableNotification($value, FunctionalTestCase::getScheduler()); - } catch (Throwable $e) { - $notification = new OnErrorNotification($e); - } - } else { - $notification = new OnNextNotification($value); - } - return new Recorded($dueTime, $notification, $comparer); + return new Recorded($dueTime, new OnNextNotification($value), $comparer); } function onCompleted(int $dueTime, callable $comparer = null) @@ -44,3 +35,23 @@ function RxIdentity($x) { return $x; } + +function materializeObservable(Observable $observable): array +{ + $messages = []; + $startTime = FunctionalTestCase::getScheduler()->getClock(); + + $sub = $observable + ->materialize() + ->timestamp(FunctionalTestCase::getScheduler()) + ->subscribe( + function (Timestamped $ts) use (&$messages, $startTime) { + $messages[] = new Recorded($ts->getTimestampMillis() - $startTime, $ts->getValue()); + }); + + FunctionalTestCase::getScheduler()->start(); + + $sub->dispose(); + + return $messages; +} From 8d219fa99a7fcf9d600efd358b9ab0f5a5929c1e Mon Sep 17 00:00:00 2001 From: David Dan Date: Sat, 11 Mar 2017 18:09:33 -0500 Subject: [PATCH 16/19] Simplified `materializeObservable` --- test/helper-functions.php | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/test/helper-functions.php b/test/helper-functions.php index 28c9ee17..3e97acab 100644 --- a/test/helper-functions.php +++ b/test/helper-functions.php @@ -3,6 +3,7 @@ declare(strict_types = 1); use Rx\Observable; +use Rx\Testing\MockObserver; use Rx\Testing\Recorded; use Rx\Testing\Subscription; use Rx\Functional\FunctionalTestCase; @@ -38,20 +39,15 @@ function RxIdentity($x) function materializeObservable(Observable $observable): array { - $messages = []; $startTime = FunctionalTestCase::getScheduler()->getClock(); - $sub = $observable - ->materialize() - ->timestamp(FunctionalTestCase::getScheduler()) - ->subscribe( - function (Timestamped $ts) use (&$messages, $startTime) { - $messages[] = new Recorded($ts->getTimestampMillis() - $startTime, $ts->getValue()); - }); + $observer = new MockObserver(FunctionalTestCase::getScheduler(), $startTime); + + $sub = $observable->subscribe($observer); FunctionalTestCase::getScheduler()->start(); $sub->dispose(); - return $messages; + return $observer->getMessages(); } From c1539653b4e15230e288455b4ae343a5f2f79970 Mon Sep 17 00:00:00 2001 From: David Dan Date: Sat, 11 Mar 2017 18:12:33 -0500 Subject: [PATCH 17/19] Removed unused use statement --- test/helper-functions.php | 1 - 1 file changed, 1 deletion(-) diff --git a/test/helper-functions.php b/test/helper-functions.php index 3e97acab..1bdf6d4d 100644 --- a/test/helper-functions.php +++ b/test/helper-functions.php @@ -10,7 +10,6 @@ use Rx\Notification\OnCompletedNotification; use Rx\Notification\OnErrorNotification; use Rx\Notification\OnNextNotification; -use Rx\Timestamped; function onError(int $dueTime, $error, callable $comparer = null) { From ee4ec612fd087ed032361774db3ba3cffc650dcb Mon Sep 17 00:00:00 2001 From: Martin Sikora Date: Sun, 12 Mar 2017 22:37:07 +0100 Subject: [PATCH 18/19] remove redundant test --- test/Rx/Functional/FunctionalTestCase.php | 8 ------ test/Rx/Testing/RecordedTest.php | 33 ----------------------- 2 files changed, 41 deletions(-) diff --git a/test/Rx/Functional/FunctionalTestCase.php b/test/Rx/Functional/FunctionalTestCase.php index 464b5c93..6bb7a4e1 100644 --- a/test/Rx/Functional/FunctionalTestCase.php +++ b/test/Rx/Functional/FunctionalTestCase.php @@ -40,8 +40,6 @@ static function getScheduler() { */ public function assertMessages(array $expected, array $recorded) { - $this->scheduler->start(); - if (count($expected) !== count($recorded)) { $this->fail(sprintf('Expected message count %d does not match actual count %d.', count($expected), count($recorded))); } @@ -61,8 +59,6 @@ public function assertMessages(array $expected, array $recorded) */ public function assertMessagesNotEqual(array $expected, array $recorded) { - $this->scheduler->start(); - if (count($expected) !== count($recorded)) { $this->assertTrue(true); return; @@ -80,8 +76,6 @@ public function assertMessagesNotEqual(array $expected, array $recorded) public function assertSubscription(HotObservable $observable, Subscription $expected) { - $this->scheduler->start(); - $subscriptionCount = count($observable->getSubscriptions()); if ($subscriptionCount === 0) { @@ -103,8 +97,6 @@ public function assertSubscription(HotObservable $observable, Subscription $expe public function assertSubscriptions(array $expected, array $recorded) { - $this->scheduler->start(); - if (count($expected) !== count($recorded)) { $this->fail(sprintf('Expected subscription count %d does not match actual count %d.', count($expected), count($recorded))); } diff --git a/test/Rx/Testing/RecordedTest.php b/test/Rx/Testing/RecordedTest.php index 98ac4c0a..ff667049 100644 --- a/test/Rx/Testing/RecordedTest.php +++ b/test/Rx/Testing/RecordedTest.php @@ -72,35 +72,6 @@ public function compare_cold_observables_not_equal() $this->assertFalse($records1->equals($records2)); } - /** - * @test - */ - public function automatic_mock_observer_doesnt_create_loggable_subscription() - { - $inner = $this->createColdObservable([ - onNext(150, 1), - onNext(200, 2), - onNext(250, 3), - onCompleted(300), - ]); - // Thanks to OnNextObservableNotification this internally creates - // an instance of MockHigherOrderObserver which is not logged - // by the ColdObservable::subscribe() method. - $records = onNext(100, $inner); - - $expected = onNext(100, $this->createColdObservable([ - onNext(150, 1), - onNext(200, 2), - onNext(250, 3), - onCompleted(300), - ])); - - $this->assertMessages([$records], [$expected]); - $this->assertTrue($records->equals($expected)); - - $this->assertSubscriptions([], $inner->getSubscriptions()); - } - /** * @test */ @@ -153,8 +124,6 @@ public function observables_at_different_time_with_same_records_arent_equal() onNext(100, 2), ])); - $this->scheduler->start(); - $this->assertFalse($records1->equals($records2)); $this->assertEquals('[OnNext(1)@50, OnNext(2)@100]@50', $records1->__toString()); } @@ -173,8 +142,6 @@ public function observables_with_inner_records_at_different_time_arent_equal() onNext(100, 2), ])); - $this->scheduler->start(); - $this->assertFalse($records1->equals($records2)); $this->assertEquals('[OnNext(1)@50, OnNext(2)@150]@100', $records1->__toString()); } From 58d185d4aa28486b95448543c4335151491ae474 Mon Sep 17 00:00:00 2001 From: David Dan Date: Sun, 12 Mar 2017 18:13:38 -0400 Subject: [PATCH 19/19] `toBe` now does a string comparison Added a higher-order marble test --- test/Rx/Functional/FunctionalTestCase.php | 4 ++-- test/Rx/Functional/MarbleTest.php | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/test/Rx/Functional/FunctionalTestCase.php b/test/Rx/Functional/FunctionalTestCase.php index 6bb7a4e1..040f6fc3 100644 --- a/test/Rx/Functional/FunctionalTestCase.php +++ b/test/Rx/Functional/FunctionalTestCase.php @@ -328,7 +328,7 @@ public function toBe(string $expected, array $values = [], string $errorMessage { $error = $errorMessage ? new \Exception($errorMessage) : null; - $this->assertEquals( + $this->assertMessages( $this->convertMarblesToMessages($expected, $values, $error, 200), $this->messages ); @@ -350,7 +350,7 @@ public function __construct(array $subscriptions) public function toBe(string $subscriptionsMarbles) { - $this->assertEquals( + $this->assertMessages( $this->convertMarblesToSubscriptions($subscriptionsMarbles, 200), $this->subscriptions ); diff --git a/test/Rx/Functional/MarbleTest.php b/test/Rx/Functional/MarbleTest.php index 28330aa5..59c395c1 100644 --- a/test/Rx/Functional/MarbleTest.php +++ b/test/Rx/Functional/MarbleTest.php @@ -308,4 +308,24 @@ public function testCountMarble() $this->expectObservable($e1->count())->toBe($expected, ['x' => 3]); $this->expectSubscriptions($e1->getSubscriptions())->toBe($subs); } + + public function testGroupMarble() + { + $hot = '--1---2---3---4---5---|'; + $expected = '--x---y---------------|'; + $coldx = '1-------3-------5---|'; + $coldy = '2-------4-------|'; + + $e1 = $this->createHot($hot); + $x = $this->createCold($coldx); + $y = $this->createCold($coldy); + + $expectedValues = ['x' => $x, 'y' => $y]; + + $source = $e1->groupBy(function ($val) { + return (int)$val % 2; + }); + + $this->expectObservable($source)->toBe($expected, $expectedValues); + } }