tricu

A lazy functional language based on Tree Calculus
Log | Files | Refs | README | LICENSE

conformance.php (5632B)


      1 <?php
      2 declare(strict_types=1);
      3 ini_set('display_errors', 'stderr');
      4 require __DIR__.'/../src/ffi.php';
      5 use Arboricx\{Library, TricuError, Context};
      6 function check(bool $ok, string $message='conformance failed'): void { if (!$ok) throw new RuntimeException($message); }
      7 function expectStatus(int $status, callable $operation): void {
      8     try { $operation(); } catch (TricuError $error) { check($error->status === $status, $error->getMessage()); return; }
      9     throw new RuntimeException('invalid operation accepted');
     10 }
     11 [$script,$libraryPath,$fixtureDir,$specification] = $argv;
     12 $f = json_decode(file_get_contents($specification),true,flags:JSON_THROW_ON_ERROR);
     13 $library = new Library($libraryPath); $ctx = $library->context(); $other = $library->context();
     14 $load = fn(string $file,string $name) => $ctx->loadBundle(file_get_contents("$fixtureDir/$file"),$name);
     15 $report = [];
     16 try {
     17     $report['numbers'] = array_map(fn($n) => $ctx->toNumber($ctx->number($n)),$f['numbers']);
     18     $report['strings'] = array_map(fn($s) => $ctx->toString($ctx->string($s)),$f['strings']);
     19     $report['bytesHex'] = array_map(fn($h) => bin2hex($ctx->toBytes($ctx->bytes(hex2bin($h)))),$f['bytesHex']);
     20     $worker=$load('id.arboricx','id'); $argument=$ctx->string($f['identity']);
     21     $application=$ctx->apply($worker,$argument); $ctx->release($worker); $ctx->release($argument);
     22     $report['identity']=$ctx->toString($ctx->reduce($application));
     23     $append=$load('append.arboricx','append');
     24     foreach ($f['append'] as $s) $append=$ctx->apply($append,$ctx->string($s));
     25     $report['append']=$ctx->toString($append);
     26     $report['booleans']=array_map(fn($name) => $ctx->toBool($load($name.'.arboricx',$name)),['true','false']);
     27     $report['notQKind']=$ctx->kind($ctx->reduce($load('notQ.arboricx','not?')));
     28     $report['outcomes']=[];
     29     foreach ([0,2,9] as $code) {
     30         $program=$ctx->fork($ctx->string('tricuIO'),$ctx->fork($ctx->number(2),$ctx->fork($ctx->number($code),$ctx->string('opaque payload'))));
     31         $r=$ctx->runIO($program);
     32         $report['outcomes'][]=['outcome'=>$r['outcome'],'payload'=>$ctx->toString($r['value']),'cleanupKind'=>$ctx->kind($r['cleanup'])];
     33         $ctx->release($program); $ctx->release($r['value']); $ctx->release($r['cleanup']);
     34     }
     35     $root=$ctx->string('owned'); $alias=$ctx->retain($root);
     36     check($root->cdata !== $alias->cdata); $ctx->release($root); check($ctx->toString($alias)==='owned');
     37     $wrong=$ctx->stem($ctx->stem($ctx->leaf()));
     38     $report['negativeStatuses']=[];
     39     foreach ([fn()=>$ctx->release($root),fn()=>$other->retain($alias),fn()=>$ctx->toNumber($wrong),fn()=>$ctx->toBool($wrong),
     40         fn()=>$ctx->loadBundle('bad bundle','id'),fn()=>$load('id.arboricx','missing'),fn()=>$ctx->runIO($ctx->leaf())] as $operation) {
     41         try { $operation(); throw new LogicException('invalid operation accepted'); }
     42         catch (TricuError $error) { check($error->getMessage()!==''); $report['negativeStatuses'][]=$error->status; }
     43     }
     44     foreach ($f['invalidUtf8Hex'] as $h) expectStatus(3,fn()=>$ctx->string(hex2bin($h)));
     45     foreach ([-1,'18446744073709551616','1.5'] as $n) {
     46         try { $ctx->number($n); throw new LogicException('invalid number accepted'); }
     47         catch (RangeException $error) {}
     48     }
     49     if (isset($argv[4])) checkCopier($library,$argv[4],$f);
     50     // PHP array equality ignores object-key order while retaining value types.
     51     check($report == $f['expected'],json_encode($report));
     52 } finally { $other->close(); $ctx->close(); }
     53 expectStatus(1,fn()=>$ctx->leaf()); $ctx->close();
     54 for ($i=0;$i<100;$i++) { $fresh=$library->context(); $fresh->close(); }
     55 echo json_encode($report,JSON_THROW_ON_ERROR|JSON_UNESCAPED_UNICODE)."\n";
     56 
     57 function checkCopier(Library $library,string $bundle,array $f): void
     58 {
     59     $dir=sys_get_temp_dir().'/tricu-php-'.bin2hex(random_bytes(8));
     60     mkdir($dir); $source="$dir/in"; $target="$dir/out";
     61     $body=str_repeat(hex2bin($f['copyHex']),$f['copyRepeats']);
     62     $ctx=$library->context(); $bytes=file_get_contents($bundle); $worker=$ctx->loadBundle($bytes,'copy');
     63     $run=fn($fn,$destination=null)=>$ctx->runIO($ctx->apply($ctx->apply($fn,$ctx->string($source)),$ctx->string($destination??$target)));
     64     $count=fn()=>is_dir('/proc/self/fd') ? count(scandir('/proc/self/fd')) : null;
     65     try {
     66         file_put_contents($source,$body); $baseline=null;
     67         for ($i=0;$i<3;$i++) {
     68             $r=$run($worker);
     69             check($r['outcome']===0 && $ctx->kind($r['cleanup'])===0);
     70             check($ctx->toBool($ctx->child($r['value'],0))===true);
     71             check(file_get_contents($target)===$body);
     72             if ($i===0) $baseline=$count(); else check($count()===$baseline,'descriptor leak');
     73             $ctx->release($r['value']); $ctx->release($r['cleanup']);
     74         }
     75         $r=$run($ctx->loadBundle($bytes,'cancelledCopy'));
     76         check($r['outcome']===0 && $ctx->toString($ctx->child($r['value'],0))==='cancelled');
     77         check($ctx->toString($ctx->child($ctx->child($r['value'],1),0))==='requested');
     78         check(file_get_contents($target)===substr($body,0,4096));
     79         $r=$run($worker,"$dir/missing-parent/out");
     80         check($r['outcome']===0 && $ctx->toBool($ctx->child($r['value'],0))===false);
     81         check($count()===$baseline,'descriptor leak on failure');
     82         unlink($source); unlink($target);
     83         $r=$run($worker);
     84         check($r['outcome']===0 && $ctx->toBool($ctx->child($r['value'],0))===false);
     85         check(!file_exists($target));
     86     } finally {
     87         $ctx->close();
     88         foreach (glob("$dir/*") as $file) unlink($file);
     89         rmdir($dir);
     90     }
     91 }