ILIAS  trunk Revision v11.0_alpha-1713-gd8962da2f67
All Data Structures Namespaces Files Functions Variables Enumerations Enumerator Modules Pages
BrickTest.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 
23 use Closure;
31 
32 class BrickTest extends TestCase
33 {
34  public function testConstruct(): void
35  {
36  $this->assertInstanceOf(Brick::class, new Brick());
37  }
38 
39  public function testApply(): void
40  {
41  $expected = 'aloha';
42  $ok = $this->getMockBuilder(Result::class)->getMock();
43  $ok->method('isOk')->willReturn(true);
44  $ok->method('value')->willReturn($expected);
45  $intermediate = $this->getMockBuilder(Intermediate::class)->disableOriginalConstructor()->getMock();
46  $intermediate->method('done')->willReturn(true);
47  $intermediate->method('transform')->willReturnCallback(fn() => $ok);
48  $brick = new Brick();
49  $result = $brick->apply(static fn(Intermediate $x, Closure $cc): Result => (
50  $cc(new Ok($intermediate))
51  ), 'abcde');
52 
53  $this->assertTrue($result->isOk());
54  $this->assertEquals($expected, $result->value());
55  }
56 
57  public function testThatAllInputMustBeConsumed(): void
58  {
59  $intermediate = $this->getMockBuilder(Intermediate::class)->disableOriginalConstructor()->getMock();
60  $intermediate->expects(self::once())->method('done')->willReturn(false);
61  $intermediate->expects(self::never())->method('transform');
62 
63  $brick = new Brick();
64  $result = $brick->apply(static fn(Intermediate $x, Closure $cc): Result => (
65  $cc(new Ok($intermediate))
66  ), 'abcde');
67 
68  $this->assertFalse($result->isOk());
69  }
70 
71  public function testAFailingParser(): void
72  {
73  $brick = new Brick();
74  $result = $brick->apply(static fn(Intermediate $x, Closure $cc): Result => (
75  $cc(new Error('something happened'))
76  ), 'abcde');
77 
78  $this->assertFalse($result->isOk());
79  }
80 
81  public function testToTransformation(): void
82  {
83  $brick = new Brick();
84  $parser = $brick->sequence(['foo']);
85  $transformation = $brick->toTransformation($parser);
86  $this->assertInstanceOf(Transformation::class, $transformation);
87  $result = $transformation->applyTo(new Ok('foo'));
88  $this->assertTrue($result->isOk());
89  $this->assertEquals('foo', $result->value());
90  }
91 
92  public function testToTransformationFailed(): void
93  {
94  $brick = new Brick();
95  $parser = $brick->sequence(['foo']);
96  $transformation = $brick->toTransformation($parser);
97  $this->assertInstanceOf(Transformation::class, $transformation);
98  $result = $transformation->applyTo(new Ok('fox'));
99  $this->assertFalse($result->isOk());
100  }
101 
102  public function testRange(): void
103  {
104  $brick = new Brick();
105 
106  $parse = $brick->range(0, 0x14);
107 
108  foreach (array_fill(0, 0x14 + 1, null) as $i => $_) {
109  $result = $brick->apply($parse, chr($i));
110  $this->assertTrue($result->isOk());
111  $this->assertEquals(chr($i), $result->value());
112  }
113  }
114 
115  public function testOutOfRange(): void
116  {
117  $brick = new Brick();
118 
119  $parse = $brick->range(1, 0x14);
120 
121  $this->assertFalse($brick->apply($parse, "\x0")->isOk());
122  $this->assertFalse($brick->apply($parse, "\x15")->isOk());
123  }
124 
125  public function testEither(): void
126  {
127  $brick = new Brick();
128 
129  $parse = $brick->either([
130  'a' => $brick->range(0x10, 0x20), // False.
131  $brick->range(0x21, 0x30), // False without key.
132  'b' => $brick->range(0x0, 0x5), // True.
133  ]);
134 
135  $result = $brick->apply($parse, "\x0");
136 
137  $this->assertTrue($result->isOk());
138  $this->assertEquals("\x0", $result->value()['b']);
139  }
140 
141  public function testSequence(): void
142  {
143  $brick = new Brick();
144 
145  $parse = $brick->sequence([
146  'first' => $brick->range(ord('a'), ord('b')),
147  'second' => $brick->range(ord('c'), ord('d')),
148  ]);
149 
150  $result = $brick->apply($parse, 'ad');
151 
152  $this->assertTrue($result->isOk());
153  $this->assertEquals('a', $result->value()['first']);
154  $this->assertEquals('d', $result->value()['second']);
155  }
156 
160  public function testRepeat(int $min, ?int $max, array $succeed, array $fail): void
161  {
162  $brick = new Brick();
163  $parse = $brick->repeat($min, $max, $brick->range(ord('a'), ord('z')));
164 
165  foreach ($succeed as $input) {
166  $result = $brick->apply($parse, $input);
167 
168  $this->assertTrue($result->isOk());
169  $this->assertEquals($input, $result->value());
170  }
171 
172  foreach ($fail as $input) {
173  $result = $brick->apply($parse, $input);
174  $this->assertFalse($result->isOk());
175  }
176  }
177 
178  public static function repeatProvider(): array
179  {
180  return [
181  'Ranges are inclusive' => [3, 3, ['abc'], ['ab', 'abcd']],
182  'Null is used for infinity' => [0, null, ['', 'abcdefghijklmop'], []],
183  'Minimum is 0' => [-1, 3, ['', 'a', 'ab', 'abc'], ['abcd']],
184  'Minimum of the end range is the start range' => [3, 2, ['abc'], ['ab', 'abcd']],
185  ];
186  }
187 
191  public function testCharacters(string $method, string $input, bool $isOk): void
192  {
193  $brick = new Brick();
194 
195  $parse = $brick->either(str_split($input)); // No character is allowed to pass.
196  if ($isOk) {
197  $parse = $brick->repeat(0, null, $brick->$method()); // All characters must pass.
198  }
199 
200  $result = $brick->apply($parse, $input);
201 
202  $this->assertEquals($isOk, $result->isOk());
203  if ($isOk) {
204  $this->assertEquals($input, $result->value());
205  }
206  }
207 
208  private static function breakIntoPieces(int $x, string $break_me): array
209  {
210  $len = (int) floor(strlen($break_me) / $x);
211 
212  return array_map(
213  fn($i) => substr($break_me, $i * $len, $len),
214  range(0, $x - !(strlen($break_me) % $x))
215  );
216  }
217 
218  public static function characterProvider(): array
219  {
220  $alpha = array_fill(ord('a'), ord('z') - ord('a') + 1, '');
221  array_walk($alpha, function (&$value, int $i) {
222  $value = chr($i);
223  });
224  $alpha = implode('', $alpha);
225  $alpha .= strtoupper($alpha);
226 
227  // Circumvent error when running this test with Xdebug. The default value of xdebug.max_nesting_level will kill the test.
228  $alpha_parts = self::breakIntoPieces(3, $alpha);
229 
230  $digits = '1234567890';
231 
232  return [
233  'Accepts all digits.' => ['digit', $digits, true],
234  'Accepts no characters from a-z or A-Z.' => ['digit', $alpha, false],
235  'Accepts characters from a-z and A-Z (Part 1).' => ['alpha', $alpha_parts[0], true],
236  'Accepts characters from a-z and A-Z (Part 2).' => ['alpha', $alpha_parts[1], true],
237  'Accepts characters from a-z and A-Z (Part 3).' => ['alpha', $alpha_parts[2], true],
238  'Accepts no digits.' => ['alpha', $digits, false],
239  ];
240  }
241 
245  public function testEmptyString(string $input, bool $isOk): void
246  {
247  $brick = new Brick();
248 
249  $parse = $brick->sequence(['']);
250  $result = $brick->apply($parse, $input);
251 
252  $this->assertEquals($isOk, $result->isOk());
253  }
254 
255  public static function emptyStringProvider(): array
256  {
257  return [
258  'Test empty input' => ['', true],
259  'Test non empty input' => ['x', false],
260  ];
261  }
262 
263  public function testTransformation(): void
264  {
265  $brick = new Brick();
266 
267  $intermediate = $this->getMockBuilder(Intermediate::class)->disableOriginalConstructor()->getMock();
268  $intermediate->method('done')->willReturn(true);
269 
270  $ok = $this->getMockBuilder(Result::class)->getMock();
271  $ok->method('isOk')->willReturn(true);
272  $ok->method('value')->willReturn($intermediate);
273  $ok->method('map')->willReturn($ok);
274  $ok->method('then')->willReturn($ok);
275  $ok->method('except')->willReturn($ok);
276 
277  $transformation = $this->getMockBuilder(Transformation::class)->getMock();
278  $transformation->expects(self::once())->method('applyTo')->willReturn($ok);
279 
280  $parser = $brick->transformation($transformation, fn($x, $cc) => $cc(new Ok($x)));
281 
282  $result = $brick->apply($parser, 'a');
283 
284  $this->assertEquals($ok, $result);
285  }
286 
287  private function string(): Closure
288  {
289  return static function (string $string): Ok {
290  return new Ok($string);
291  };
292  }
293 }
testRepeat(int $min, ?int $max, array $succeed, array $fail)
repeatProvider
Definition: BrickTest.php:160
static breakIntoPieces(int $x, string $break_me)
Definition: BrickTest.php:208
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
testEmptyString(string $input, bool $isOk)
emptyStringProvider
Definition: BrickTest.php:245
A result encapsulates a value or an error and simplifies the handling of those.
Definition: Ok.php:30
testCharacters(string $method, string $input, bool $isOk)
characterProvider
Definition: BrickTest.php:191
-type Continuation Closure(Result<Intermediate>): Result<Intermediate> -type Parser Closure(Intermedi...
Definition: Brick.php:34