/test/Lazy/Test/SequenceTest.php

https://bitbucket.org/mkjpryor/lazy-sequence · PHP · 369 lines · 206 code · 78 blank · 85 comment · 5 complexity · 88dd79ec263ebfbb570a38acef6fd0ef MD5 · raw file

  1. <?php
  2. namespace Lazy\Test;
  3. use Lazy\Sequence as S;
  4. /**
  5. * Stub callback
  6. */
  7. class CallableStub {
  8. public function __invoke() {}
  9. }
  10. class SequenceTest extends \PHPUnit_Framework_TestCase {
  11. /**
  12. * Returns a callable that expects to be called $amount times
  13. * If given, $params is an array of parameter values expected for each
  14. * invocation
  15. *
  16. * @param integer $amount
  17. * @param array $args
  18. */
  19. protected function expectCallable($amount, array $args = null) {
  20. $mock = $this->getMock('\Lazy\Test\CallableStub');
  21. // Set the expectation for the number of calls
  22. $mock->expects($this->exactly($amount))->method('__invoke');
  23. // If $params is not given, that is all we need to do
  24. if( is_null($args) ) return $mock;
  25. // Otherwise $params is an array
  26. // Check that $params has the correct size (i.e. one set of parameters per
  27. // expected call)
  28. if( count($args) != $amount )
  29. throw new \BadMethodCallException(
  30. 'A set of parameters for each expected call must be given'
  31. );
  32. // Set the expectations for each call to the callable
  33. $count = 0;
  34. foreach($args as $with) {
  35. // Make sure that $with is an array, even if it has a single element
  36. $with = is_array($with) ? $with : array($with);
  37. // Capture the expectation so we can use call_user_func_array with it
  38. $expectation = $mock->expects($this->at($count))->method('__invoke');
  39. // Call with with $with as positional arguments
  40. call_user_func_array(array($expectation, 'with'), $with);
  41. $count++;
  42. }
  43. return $mock;
  44. }
  45. public function testHead() {
  46. // Test that head returns correctly for a non-empty sequence
  47. $this->assertEquals(5, S::head(S::create(5, 6, 7, 8)));
  48. // Test that head returns correctly for an empty sequence
  49. $this->assertNull(S::head(S::create()));
  50. }
  51. public function testTail() {
  52. // Test that tail returns correctly for a sequence with >= 2 elements
  53. $this->assertEquals([6, 7, 8], S::toArray(S::tail(S::create(5, 6, 7, 8))));
  54. // Test that tail returns correctly for a sequence with 1 item
  55. $this->assertEmpty(S::toArray(S::tail(S::create(5))));
  56. // Test that tail returns correctly for an empty sequence
  57. $this->assertEmpty(S::toArray(S::tail(S::create())));
  58. }
  59. public function testLast() {
  60. // Test that last returns correctly for a sequence with >= 2 items
  61. $this->assertEquals(8, S::last(S::create(5, 6, 7, 8)));
  62. // Test that last returns correctly for a sequence with 1 item
  63. $this->assertEquals(5, S::last(S::create(5)));
  64. // Test that last returns correctly for an empty sequence
  65. $this->assertNull(S::last(S::create()));
  66. }
  67. public function testIsEmpty() {
  68. // Test that isEmpty returns false for a non-empty sequence
  69. $this->assertFalse(S::isEmpty(S::create(5, 6, 7)));
  70. // Test that isEmpty returns true for an empty sequence
  71. $this->assertTrue(S::isEmpty(S::create()));
  72. }
  73. public function testCons() {
  74. // Just test cons prepends the element to the given sequence
  75. $this->assertEquals([4, 5, 6, 7], S::toArray(S::cons(S::create(5, 6, 7), 4)));
  76. }
  77. public function testAppend() {
  78. // Test that append joins 2 non-empty sequences correctly
  79. $this->assertEquals(
  80. [5, 6, 7, 8],
  81. S::toArray(S::append(S::create(5, 6), S::create(7, 8)))
  82. );
  83. // Test that append joins an empty and a non-empty sequence correctly
  84. $this->assertEquals(
  85. [5, 6],
  86. S::toArray(S::append(S::create(5, 6), S::create()))
  87. );
  88. $this->assertEquals(
  89. [7, 8],
  90. S::toArray(S::append(S::create(), S::create(7, 8)))
  91. );
  92. // Test that append joins two empty sequences successfully
  93. $this->assertEmpty(S::toArray(S::append(S::create(), S::create())));
  94. }
  95. public function testMap() {
  96. // Test that map works as expected on a non-empty sequence
  97. $this->assertEquals(
  98. [2, 4, 6],
  99. S::toArray(S::map(S::create(1, 2, 3), function($x) { return $x * 2; }))
  100. );
  101. // Test that map works as expected on an empty sequence
  102. $this->assertEmpty(S::toArray(S::map(S::create(), function($x) { return $x * 2; })));
  103. }
  104. public function testFilter() {
  105. // Test that filter works as expected on a non-empty sequence
  106. $this->assertEquals(
  107. [2, 4],
  108. S::toArray(S::filter(S::create(1, 2, 3, 4), function($x) { return $x % 2 == 0; }))
  109. );
  110. // Test that filter works as expected on an empty sequence
  111. $this->assertEmpty(
  112. S::toArray(S::filter(S::create(), function($x) { return $x % 2 == 0; }))
  113. );
  114. }
  115. public function testTake() {
  116. // Test that take works with an empty sequence
  117. $this->assertEmpty(S::toArray(S::take(S::create(), 3)));
  118. // Test that take works with a sequence with <= 3 elements
  119. $this->assertEquals([1, 2, 3], S::toArray(S::take(S::create(1, 2, 3), 3)));
  120. // Test that take works with a sequence with > 3 elements
  121. $this->assertEquals([1, 2, 3], S::toArray(S::take(S::create(1, 2, 3, 4, 5), 3)));
  122. // Test that take can be used with an infinite sequence and returns
  123. $this->assertEquals([1, 2, 3], S::toArray(S::take(S::range(1), 3)));
  124. }
  125. public function testTakeWhile() {
  126. $lessThan3 = function($x) { return $x < 3; };
  127. // Test that takeWhile works with an empty sequence
  128. $this->assertEmpty(S::toArray(S::takeWhile(S::create(), $lessThan3)));
  129. // Test that takeWhile works with no true elements
  130. $this->assertEmpty(
  131. S::toArray(S::takeWhile(S::create(3, 4, 5, 6), $lessThan3))
  132. );
  133. // Test that takeWhile works with no false elements
  134. $this->assertEquals(
  135. [1, 2, 0, 1, 2],
  136. S::toArray(S::takeWhile(S::create(1, 2, 0, 1, 2), $lessThan3))
  137. );
  138. // Test that takeWhile works when predicate becomes true again after
  139. // becoming false
  140. $this->assertEquals(
  141. [1, 2],
  142. S::toArray(S::takeWhile(S::create(1, 2, 3, 1, 2), $lessThan3))
  143. );
  144. // Test that takeWhile can be used with an infinite sequence and returns
  145. $this->assertEquals([1, 2], S::toArray(S::takeWhile(S::range(1), $lessThan3)));
  146. }
  147. public function testSkip() {
  148. // Test that skip works with an empty sequence
  149. $this->assertEmpty(S::toArray(S::skip(S::create(), 3)));
  150. // Test that skip works with a sequence with <= 3 elements
  151. $this->assertEmpty(S::toArray(S::skip(S::create(1, 2, 3), 3)));
  152. // Test that skip works with a sequence with > 3 elements
  153. $this->assertEquals([4, 5], S::toArray(S::skip(S::create(1, 2, 3, 4, 5), 3)));
  154. }
  155. public function testSkipWhile() {
  156. $lessThan3 = function($x) { return $x < 3; };
  157. // Test that skipWhile works with an empty sequence
  158. $this->assertEmpty(S::toArray(S::skipWhile(S::create(), $lessThan3)));
  159. // Test that skipWhile works with no false elements
  160. $this->assertEmpty(
  161. S::toArray(S::skipWhile(S::create(1, 2, 0, 1, 2), $lessThan3))
  162. );
  163. // Test that skipWhile works with no true elements
  164. $this->assertEquals(
  165. [3, 4, 5, 6],
  166. S::toArray(S::skipWhile(S::create(3, 4, 5, 6), $lessThan3))
  167. );
  168. // Test that skipWhile works when predicate becomes true again after
  169. // becoming false
  170. $this->assertEquals(
  171. [3, 1, 2],
  172. S::toArray(S::skipWhile(S::create(1, 2, 3, 1, 2), $lessThan3))
  173. );
  174. }
  175. public function testReduce() {
  176. $sum = function($x, $y) { return $x + $y; };
  177. // Test that reduce works with an empty sequence
  178. $this->assertEquals(0, S::reduce(S::create(), $sum, 0));
  179. // Test that reduce works with a non-empty sequence
  180. $this->assertEquals(6, S::reduce(S::create(1, 2, 3), $sum, 0));
  181. }
  182. public function testReverse() {
  183. // Test that the reverse of an empty sequence is the empty sequence
  184. $this->assertEmpty(S::toArray(S::reverse(S::create())));
  185. // Test that a non-empty sequence reverses correctly
  186. $this->assertEquals([7, 6, 5], S::toArray(S::reverse(S::create(5, 6, 7))));
  187. }
  188. public function testZip() {
  189. $zipper = function($x, $y) { return array($x, $y); };
  190. // Test that zipping two empty sequences works
  191. $this->assertEmpty(
  192. S::toArray(S::zip(S::create(), S::create(), $zipper))
  193. );
  194. // Test that zipping any other sequence with the empty sequence works
  195. $this->assertEmpty(
  196. S::toArray(S::zip(S::create(1, 2, 3), S::create(), $zipper))
  197. );
  198. $this->assertEmpty(
  199. S::toArray(S::zip(S::create(), S::create(4, 5, 6), $zipper))
  200. );
  201. // Test that zipping two equal size sequences works
  202. $this->assertEquals(
  203. [[1, 4], [2, 5], [3, 6]],
  204. S::toArray(S::zip(S::create(1, 2, 3), S::create(4, 5, 6), $zipper))
  205. );
  206. // Test that zipping sequences with different sizes works
  207. $this->assertEquals(
  208. [[1, 4], [2, 5]],
  209. S::toArray(S::zip(S::create(1, 2), S::create(4, 5, 6), $zipper))
  210. );
  211. $this->assertEquals(
  212. [[1, 4], [2, 5]],
  213. S::toArray(S::zip(S::create(1, 2, 3), S::create(4, 5), $zipper))
  214. );
  215. // Test that zipping with plain arrays works
  216. $this->assertEquals(
  217. [[1, 4], [2, 5]],
  218. S::toArray(S::zip([1, 2], [4, 5, 6], $zipper))
  219. );
  220. }
  221. public function testEach() {
  222. // Test that each works correctly with an empty sequence
  223. // I.e. the given callback is never called
  224. S::each(S::create(), $this->expectCallable(0));
  225. // Test that each works correctly with a non-empty sequence
  226. // I.e. the given callback is called with successive elements
  227. S::each(S::create(1, 2, 3, 4), $this->expectCallable(4, [[1], [2], [3], [4]]));
  228. }
  229. public function testToArray() {
  230. // Test that toArray works with an empty sequence
  231. $this->assertEmpty(S::toArray(S::create()));
  232. // Test that toArray works with a non-empty sequence
  233. $this->assertEquals([1, 2, 3], S::toArray(S::create(1, 2, 3)));
  234. }
  235. public function testCount() {
  236. // Test that count works with an empty array
  237. $this->assertEquals(0, S::count(S::create()));
  238. // Test that count works with a non-empty array
  239. $this->assertEquals(3, S::count(S::create(1, 2, 3)));
  240. }
  241. public function testCreate() {
  242. // Test that create can create an empty sequence
  243. foreach( S::create() as $item )
  244. $this->fail('Should not be reachable');
  245. // Test that create can create non-empty sequences
  246. S::each(S::create(1, 2, 3), $this->expectCallable(3, [[1], [2], [3]]));
  247. }
  248. public function testRange() {
  249. // Test that range can create empty sequences with +ve and -ve steps
  250. $this->assertEmpty(S::toArray(S::range(2, 1, 1)));
  251. $this->assertEmpty(S::toArray(S::range(1, 2, -1)));
  252. // Test that range can create single element sequences with +ve and -ve steps
  253. $this->assertEquals([1], S::toArray(S::range(1, 1, 1)));
  254. $this->assertEquals([1], S::toArray(S::range(1, 1, -1)));
  255. // Test that range can create multi-element array with different +ve and -ve steps
  256. $this->assertEquals([1, 2, 3], S::toArray(S::range(1, 3)));
  257. $this->assertEquals([1, 3, 5, 7, 9], S::toArray(S::range(1, 10, 2)));
  258. $this->assertEquals([2, 4, 6, 8, 10], S::toArray(S::range(2, 10, 2)));
  259. $this->assertEquals([3, 2, 1], S::toArray(S::range(3, 1, -1)));
  260. $this->assertEquals([10, 8, 6, 4, 2], S::toArray(S::range(10, 1, -2)));
  261. $this->assertEquals([10, 8, 6, 4, 2, 0], S::toArray(S::range(10, 0, -2)));
  262. // Check that range can create infinite sequences (i.e. the same range command
  263. // can be used to create arrays of different sizes by taking more or less items)
  264. $this->assertEquals([1, 3, 5], S::toArray(S::take(S::range(1, null, 2), 3)));
  265. $this->assertEquals([1, 3, 5, 7, 9], S::toArray(S::take(S::range(1, null, 2), 5)));
  266. }
  267. public function testRepeat() {
  268. // Test that repeat creates an infinite sequence containing the same value
  269. // repeated
  270. $this->assertEquals([3, 3, 3], S::toArray(S::take(S::repeat(3), 3)));
  271. $this->assertEquals([3, 3, 3, 3, 3], S::toArray(S::take(S::repeat(3), 5)));
  272. }
  273. public function testReplicate() {
  274. // Test that replicate can create an empty sequence
  275. $this->assertEmpty(S::toArray(S::replicate(0, 3)));
  276. // Test that replicate can create non-empty sequences
  277. $this->assertEquals([3, 3, 3], S::toArray(S::replicate(3, 3)));
  278. $this->assertEquals([3, 3, 3, 3, 3], S::toArray(S::replicate(5, 3)));
  279. }
  280. public function testIterate() {
  281. // Test that iterate can create infinite sequences
  282. $this->assertEquals(
  283. [1, 2, 3],
  284. S::toArray(S::take(S::iterate(function($i) { return $i + 1; }, 1), 3))
  285. );
  286. $this->assertEquals(
  287. [1, 2, 3, 4, 5],
  288. S::toArray(S::take(S::iterate(function($i) { return $i + 1; }, 1), 5))
  289. );
  290. }
  291. }