packages/deriver/tests/Unit/Deriver/Evaluator/Recurrence/BoundsTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Deriver\Evaluator\Recurrence;
6
7use Deriver\Deriver\BranchEvaluators;
8use Deriver\Deriver\Evaluator\Call\Evaluators;
9use Deriver\Deriver\Evaluator\Enumeration\Choices;
10use Deriver\Deriver\Evaluator\Evaluator;
11use Deriver\Deriver\Evaluator\EvaluatorConfig;
12use Deriver\Deriver\Evaluator\Index\DefinitionIndex;
13use Deriver\Deriver\Evaluator\Request\Budget;
14use Deriver\Deriver\Evaluator\Scope\Context;
15use Deriver\Deriver\Evaluator\Scope\Frame;
16use Deriver\Deriver\Evaluator\Term;
17use Deriver\Deriver\Lowerer\Declaration\Index;
18use Deriver\Deriver\MicroEvaluatorConversion;
19use Deriver\Deriver\Parser\Parser;
20use Deriver\Deriver\Parser\ParserConfig;
21use Deriver\Deriver\Resolver\Resolver;
22use Deriver\Deriver\Sources\Source;
23use PHPUnit\Framework\Attributes\CoversNothing;
24use PHPUnit\Framework\TestCase;
25
26#[CoversNothing]
27final class BoundsTest extends TestCase
28{
29 public function testOfReusesLoopBoundsForEveryStorageLocation(): void
30 {
31 $source = 'function subject(){for($i=0;$i<1;$i++){}return 1;}';
32 $tree = (new Resolver())->resolve((new Parser(new ParserConfig('8.3')))->parse(new Source('a.php', '<?php ' . $source)));
33 $index = new DefinitionIndex(new Index('s', [$tree]));
34 $config = new EvaluatorConfig(evaluators: array_map((new MicroEvaluatorConversion())->convert(...), BranchEvaluators::truth()));
35 $engine = new Evaluator(new Context($index, $config, new Evaluators($config->target), new Budget()));
36 $graph = $index->graph('subject');
37 self::assertNotNull($graph);
38 $frame = new Frame($graph, 'source:subject');
39 $header = array_values(array_filter($graph->body->blocks, static fn ($block): bool => $block->loopHeader))[0]->id;
40 $bounds = new \Deriver\Deriver\Evaluator\Recurrence\Bounds($engine);
41 $first = $bounds->of($frame, $header, 32);
42 $work = $engine->context->workUnits;
43 self::assertSame($first, $bounds->of($frame, $header, 32));
44 self::assertSame($work, $engine->context->workUnits);
45 self::assertSame(['minimum' => 1, 'maximum' => 1], $first->attributes);
46 }
47
48 public function testDeriveDoesNotCountAllIterations(): void
49 {
50 $source = 'function subject(){for($i=0;$i<10000;$i++){}return 1;}';
51 $tree = (new Resolver())->resolve((new Parser(new ParserConfig('8.3')))->parse(new Source('a.php', '<?php ' . $source)));
52 $index = new DefinitionIndex(new Index('s', [$tree]));
53 $config = new EvaluatorConfig(evaluators: array_map((new MicroEvaluatorConversion())->convert(...), BranchEvaluators::truth()));
54 $engine = new Evaluator(new Context($index, $config, new Evaluators($config->target), new Budget()));
55 $graph = $index->graph('subject');
56 self::assertNotNull($graph);
57 $frame = new Frame($graph, 'source:subject');
58 $header = array_values(array_filter($graph->body->blocks, static fn ($block): bool => $block->loopHeader))[0]->id;
59 $bounds = (new \Deriver\Deriver\Evaluator\Recurrence\Bounds($engine))->derive($frame, $header, 32);
60 self::assertSame(['minimum' => 1, 'maximum' => -1], $bounds->attributes);
61 self::assertLessThan(30, $engine->context->constructedNodes);
62 }
63
64 public function testKnownDoesNotExcludeUnknownAlternatives(): void
65 {
66 $source = 'function subject(){return 1;}';
67 $tree = (new Resolver())->resolve((new Parser(new ParserConfig('8.3')))->parse(new Source('a.php', '<?php ' . $source)));
68 $index = new DefinitionIndex(new Index('s', [$tree]));
69 $config = new EvaluatorConfig();
70 $engine = new Evaluator(new Context($index, $config, new Evaluators($config->target), new Budget()));
71 $bounds = new \Deriver\Deriver\Evaluator\Recurrence\Bounds($engine);
72 $partial = (new Choices())->make([[Term::constant(false), []], [Term::parameter('x'), []]]);
73 self::assertNull($bounds->known($partial));
74 self::assertFalse($bounds->known(Term::constant(false)));
75 }
76
77 public function testResultRetainsTheEvidenceForPruning(): void
78 {
79 $source = 'function subject(){return 1;}';
80 $tree = (new Resolver())->resolve((new Parser(new ParserConfig('8.3')))->parse(new Source('a.php', '<?php ' . $source)));
81 $index = new DefinitionIndex(new Index('s', [$tree]));
82 $config = new EvaluatorConfig();
83 $engine = new Evaluator(new Context($index, $config, new Evaluators($config->target), new Budget()));
84 $value = (new \Deriver\Deriver\Evaluator\Recurrence\Bounds($engine))->result(0, 0, [Term::constant(false)]);
85 self::assertSame(['minimum' => 0, 'maximum' => 0], $value->attributes);
86 self::assertNotNull($engine->context->provenance->get($value));
87 }
88}
89