aboutsummaryrefslogtreecommitdiffhomepage
path: root/tests/src/SpecTestsuites/SpecTestsuiteBase.php
diff options
context:
space:
mode:
Diffstat (limited to 'tests/src/SpecTestsuites/SpecTestsuiteBase.php')
-rw-r--r--tests/src/SpecTestsuites/SpecTestsuiteBase.php225
1 files changed, 225 insertions, 0 deletions
diff --git a/tests/src/SpecTestsuites/SpecTestsuiteBase.php b/tests/src/SpecTestsuites/SpecTestsuiteBase.php
new file mode 100644
index 0000000..72d1a86
--- /dev/null
+++ b/tests/src/SpecTestsuites/SpecTestsuiteBase.php
@@ -0,0 +1,225 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Nsfisis\Waddiwasi\Tests\SpecTestsuites;
+
+use Nsfisis\Waddiwasi\BinaryFormat\Decoder;
+use Nsfisis\Waddiwasi\Execution\Runtime;
+use Nsfisis\Waddiwasi\Execution\Store;
+use Nsfisis\Waddiwasi\Execution\TrapException;
+use PHPUnit\Framework\TestCase;
+use function count;
+
+abstract class SpecTestsuiteBase extends TestCase
+{
+ private static $modules = [];
+ private static $runtimes = [];
+
+ protected function runModuleCommand(
+ string $filename,
+ ?string $name,
+ int $line,
+ ): void {
+ $moduleName = $name ?? '_';
+ $filePath = __DIR__ . "/../../fixtures/spec_testsuites/core/$filename";
+ $wasmBinary = file_get_contents($filePath);
+ $module = (new Decoder($wasmBinary))->decode();
+ self::$modules[$moduleName] = $module;
+ $runtime = Runtime::instantiate(Store::empty(), $module, []);
+ self::$runtimes[$moduleName] = $runtime;
+ $this->assertTrue(true);
+ }
+
+ protected function runAssertReturnCommand(
+ ?string $module,
+ array $action,
+ array $expected,
+ int $line,
+ ): void {
+ $targetModuleName = $module ?? '_';
+ $targetModule = self::$modules[$targetModuleName];
+ $actionType = $action['type'];
+ $actionField = $action['field'];
+ if ($actionType === 'invoke') {
+ $actionArgs = $action['args'];
+ $runtime = self::$runtimes[$targetModuleName];
+ try {
+ $this->assertWasmInvokeResults(
+ $expected,
+ $runtime->invoke(
+ $actionField,
+ array_map($this->toWasmArg(...), $actionArgs),
+ ),
+ "at $line",
+ );
+ } catch (TrapException $e) {
+ $this->assertTrue(false, "assert_return: trap, $e at $line");
+ }
+ } else {
+ $this->assertTrue(false, "assert_return: unknown action, $actionType");
+ }
+ }
+
+ protected function runAssertTrapCommand(
+ ?string $module,
+ array $action,
+ string $text,
+ int $line,
+ ): void {
+ $targetModuleName = $module ?? '_';
+ $targetModule = self::$modules[$targetModuleName];
+ $actionType = $action['type'];
+ $actionField = $action['field'];
+ if ($actionType === 'invoke') {
+ $actionArgs = $action['args'];
+ $runtime = self::$runtimes[$targetModuleName];
+ $exception = null;
+ try {
+ $runtime->invoke(
+ $actionField,
+ array_map($this->toWasmArg(...), $actionArgs),
+ );
+ } catch (TrapException $e) {
+ $exception = $e;
+ }
+ $this->assertNotNull($exception, "at $line");
+ // @todo check trap message
+ } else {
+ $this->assertTrue(false, "assert_trap: unknown action, $actionType");
+ }
+ }
+
+ protected function runAssertMalformedCommand(
+ string $filename,
+ string $text,
+ int $line,
+ ): void {
+ $this->assertTrue(false, "assert_malformed");
+ }
+
+ protected function runAssertInvalidCommand(
+ string $filename,
+ string $text,
+ int $line,
+ ): void {
+ $this->assertTrue(false, "assert_invalid");
+ }
+
+ protected function runAssertExhaustionCommand(
+ array $action,
+ string $text,
+ int $line,
+ ): void {
+ $this->assertTrue(false, "assert_exhaustion");
+ }
+
+ protected function runAssertUninstantiableCommand(
+ string $filename,
+ string $text,
+ int $line,
+ ): void {
+ $this->assertTrue(false, "assert_uninstantiable");
+ }
+
+ protected function runAssertUnlinkableCommand(
+ string $filename,
+ string $text,
+ int $line,
+ ): void {
+ $this->assertTrue(false, "assert_unlinkable");
+ }
+
+ protected function runActionCommand(
+ array $action,
+ int $line,
+ ): void {
+ $this->assertTrue(false, "action");
+ }
+
+ protected function runRegisterCommand(
+ ?string $name,
+ string $as,
+ int $line,
+ ): void {
+ $this->assertTrue(false, "register");
+ }
+
+ /**
+ * @param array{type: string, value: string} $arg
+ */
+ private function toWasmArg(array $arg): int|float
+ {
+ $type = $arg['type'];
+ $value = $arg['value'];
+ return match ($type) {
+ 'i32' => (int)$value,
+ 'i64' => (int)$value,
+ 'f32' => unpack('g', pack('l', (int)$value))[1],
+ 'f64' => unpack('e', pack('q', (int)$value))[1],
+ default => $this->assertTrue(false, "unknown arg type: $type"),
+ };
+ }
+
+ /**
+ * @param list<mixed> $actualResults
+ * @param list<array{type: string, value: string}> $expectedResults
+ */
+ private function assertWasmInvokeResults(
+ array $expectedResults,
+ array $actualResults,
+ string $message = '',
+ ): void {
+ if ($message !== '') {
+ $message = " ($message)";
+ }
+ $this->assertCount(
+ count($expectedResults),
+ $actualResults,
+ "results count mismatch" . $message,
+ );
+
+ for ($i = 0; $i < count($expectedResults); $i++) {
+ $expectedResult = $expectedResults[$i];
+ $actualResult = $actualResults[$i];
+ if ($expectedResult['type'] === 'f32') {
+ $expectedValue = unpack('g', pack('l', (int)$expectedResult['value']))[1];
+ if (is_nan($expectedValue)) {
+ // @todo check NaN bit pattern.
+ $this->assertTrue(
+ is_nan($actualResult),
+ "result $i is not NaN" . $message,
+ );
+ } else {
+ $this->assertSame(
+ $expectedValue,
+ $actualResult,
+ "result $i mismatch" . $message,
+ );
+ }
+ } elseif ($expectedResult['type'] === 'f64') {
+ $expectedValue = unpack('e', pack('q', (int)$expectedResult['value']))[1];
+ if (is_nan($expectedValue)) {
+ // @todo check NaN bit pattern.
+ $this->assertTrue(
+ is_nan($actualResult),
+ "result $i is not NaN" . $message,
+ );
+ } else {
+ $this->assertSame(
+ $expectedValue,
+ $actualResult,
+ "result $i mismatch" . $message,
+ );
+ }
+ } else {
+ $expectedValue = (int)$expectedResult['value'];
+ $this->assertSame(
+ $expectedValue,
+ $actualResult,
+ "result $i mismatch" . $message,
+ );
+ }
+ }
+ }
+}