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