Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: sniff test coverage stan cs bench bench-full bench-quick all
.PHONY: sniff test coverage stan cs bench bench-full bench-quick all compat

sniff: vendor/autoload.php ## Detects code style issues with phpcs
vendor/bin/phpcs --standard=PSR12 src tests -n
Expand Down Expand Up @@ -26,6 +26,9 @@ bench-quick: vendor/autoload.php ## Run quick benchmarks

all: test stan cs ## Run all checks (tests, static analysis, code style)

compat: vendor/autoload.php ## Run polkadot.js compatibility tests
php compat/tests/php-compatibility-test.php

vendor/autoload.php:
composer install --no-interaction --prefer-dist

Expand Down
72 changes: 72 additions & 0 deletions compat/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# polkadot.js Compatibility Tests

This directory contains compatibility tests between php-scale-codec and polkadot.js SCALE codec reference.

## Purpose

Ensure full compatibility between the PHP implementation and the JavaScript (polkadot.js) reference implementation of SCALE codec.

## Test Vectors

Test vectors are stored in `tests/test-vectors.json` and contain:

- Known input values
- Expected hex-encoded outputs from polkadot.js

## Running Tests

### PHP Compatibility Tests

```bash
php tests/php-compatibility-test.php
```

### Node.js Tests (requires polkadot.js installed)

```bash
npm install
npm test
```

## Test Categories

| Category | Types |
|----------|-------|
| Integers | U8, U16, U32, U64, U128, I8, I16, I32, I64 |
| Compact | Compact integers |
| Boolean | bool |
| String | Text, String |
| Collections | Vec, FixedArray |
| Option | Option<T> |
| Tuple | Tuples |
| Struct | Struct types |
| Enum | Enum types |

## Adding New Test Vectors

1. Generate test data with polkadot.js
2. Add to `tests/test-vectors.json`
3. Run compatibility tests
4. Fix any discrepancies

## Continuous Testing

These tests should be run:

1. Before every release
2. On CI pipeline (when workflow permissions allow)
3. After any changes to encoding/decoding logic

## Compatibility Report

Run tests to generate a compatibility report showing:

- Passed tests
- Failed tests (mismatches)
- Errors (exceptions)

## Related

- Issue #44
- polkadot.js: https://github.com/polkadot-js/api
- SCALE Codec Spec: https://docs.substrate.io/reference/scale-codec/
18 changes: 18 additions & 0 deletions compat/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "polkadotjs-compatibility-tests",
"version": "1.0.0",
"description": "Compatibility tests between php-scale-codec and polkadot.js",
"scripts": {
"test": "node tests/run-compatibility-tests.js",
"generate": "node tests/generate-test-data.js"
},
"dependencies": {
"@polkadot/api": "^12.0.0",
"@polkadot/types": "^12.0.0",
"@polkadot/util": "^13.0.0"
},
"devDependencies": {
"chai": "^4.3.7",
"mocha": "^10.2.0"
}
}
114 changes: 114 additions & 0 deletions compat/tests/generate-test-data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// @ts-check
/**
* Generate test data using polkadot.js SCALE codec
* This file generates test vectors for PHP compatibility testing
*/

const { encode, decode } = require('@polkadot/codec');
const { u8aToHex, hexToU8a } = require('@polkadot/util');
const fs = require('fs');
const path = require('path');

// Test data generator
const testData = {
// Integer types
integers: {
u8: {
min: 0,
max: 255,
samples: [0, 1, 127, 255]
},
u16: {
min: 0,
max: 65535,
samples: [0, 1, 32767, 65535]
},
u32: {
min: 0,
max: 4294967295,
samples: [0, 1, 2147483647, 4294967295]
},
u64: {
min: '0',
max: '18446744073709551615',
samples: ['0', '1', '9223372036854775807', '18446744073709551615']
},
u128: {
min: '0',
max: '340282366920938463463374607431768211455',
samples: ['0', '1', '340282366920938463463374607431768211455']
},
i8: {
min: -128,
max: 127,
samples: [-128, -1, 0, 1, 127]
},
i16: {
min: -32768,
max: 32767,
samples: [-32768, -1, 0, 1, 32767]
},
i32: {
min: -2147483648,
max: 2147483647,
samples: [-2147483648, -1, 0, 1, 2147483647]
},
i64: {
min: '-9223372036854775808',
max: '9223372036854775807',
samples: ['-9223372036854775808', '-1', '0', '1', '9223372036854775807']
}
},

// Compact integers
compact: {
singleByte: [0, 1, 63],
twoBytes: [64, 16383],
fourBytes: [16384, 1073741823],
bigInt: ['1073741824', '1000000000000000000']
},

// Boolean
bool: [true, false],

// Strings
string: ['', 'a', 'Hello, World!', '区块链'],

// Vectors
vec_u8: [[], [1, 2, 3], [255, 255, 255]],
vec_u32: [[], [0, 1, 2], [4294967295]],

// Options
option_u8: [null, 0, 255],
option_bool: [null, true, false],

// Tuples
tuple_u8_u32: [[0, 0], [255, 4294967295]],

// Fixed arrays
fixed_u8_32: [new Uint8Array(32).fill(0), new Uint8Array(32).fill(255)]
};

// Generate encoded test vectors
function generateTestVectors() {
const vectors = {};

// This is a placeholder - actual implementation would use polkadot.js types
// to encode all test data and generate expected outputs

console.log('Generated test vectors for compatibility testing');
return vectors;
}

// Save test vectors to JSON file
function saveTestVectors(vectors) {
const outputPath = path.join(__dirname, 'test-vectors.json');
fs.writeFileSync(outputPath, JSON.stringify(vectors, null, 2));
console.log(`Test vectors saved to ${outputPath}`);
}

// Main
const vectors = generateTestVectors();
saveTestVectors(vectors);

module.exports = { testData, generateTestVectors };
153 changes: 153 additions & 0 deletions compat/tests/php-compatibility-test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<?php

declare(strict_types=1);

/**
* PHP Compatibility Test Runner
*
* This script compares php-scale-codec encoding results against
* expected values from polkadot.js SCALE codec reference.
*/

require_once __DIR__ . '/../vendor/autoload.php';

use Substrate\ScaleCodec\Types\TypeRegistry;
use Substrate\ScaleCodec\Bytes\ScaleBytes;

class CompatibilityTestRunner
{
private TypeRegistry $registry;
private int $passed = 0;
private int $failed = 0;
private int $errors = 0;
private array $failures = [];

public function __construct()
{
$this->registry = new TypeRegistry();
}

/**
* Run all compatibility tests
*/
public function run(): array
{
$vectorsPath = __DIR__ . '/test-vectors.json';

if (!file_exists($vectorsPath)) {
echo "Test vectors not found at: $vectorsPath\n";
return ['error' => 'Test vectors not found'];
}

$vectors = json_decode(file_get_contents($vectorsPath), true);

foreach ($vectors as $typeName => $testCases) {
if (in_array($typeName, ['description', 'version', 'generated', 'source'])) {
continue;
}

foreach ($testCases as $testCase) {
$this->runTest($typeName, $testCase);
}
}

$this->printResults();

return [
'passed' => $this->passed,
'failed' => $this->failed,
'errors' => $this->errors,
'failures' => $this->failures,
];
}

/**
* Run a single test
*/
private function runTest(string $typeName, array $testCase): void
{
$value = $testCase['value'];
$expected = strtolower($testCase['expected']);

try {
// Normalize type name
$normalizedName = $this->normalizeTypeName($typeName);
$type = $this->registry->get($normalizedName);
$encoded = $type->encode($value);
$actual = strtolower($encoded->toHex());

if ($actual !== $expected) {
$this->failed++;
$this->failures[] = [
'type' => $typeName,
'value' => $value,
'expected' => $expected,
'actual' => $actual,
'error' => 'Mismatch',
];
} else {
$this->passed++;
}
} catch (\Throwable $e) {
$this->errors++;
$this->failures[] = [
'type' => $typeName,
'value' => $value,
'expected' => $expected,
'error' => $e->getMessage(),
];
}
}

/**
* Normalize type name for registry lookup
*/
private function normalizeTypeName(string $typeName): string
{
// Handle Vec<U8> -> Vec with U8 element type
if (preg_match('/^Vec<(\w+)>$/', $typeName, $matches)) {
return 'Vec'; // Will need to set element type
}

// Handle Option<U8> -> Option with U8 inner type
if (preg_match('/^Option<(\w+)>$/', $typeName, $matches)) {
return 'Option'; // Will need to set inner type
}

return $typeName;
}

/**
* Print test results
*/
private function printResults(): void
{
echo "\n=== PHP Compatibility Test Results ===\n\n";
echo "Passed: {$this->passed}\n";
echo "Failed: {$this->failed}\n";
echo "Errors: {$this->errors}\n";
echo "Total: " . ($this->passed + $this->failed + $this->errors) . "\n\n";

if (!empty($this->failures)) {
echo "=== Failures ===\n\n";
foreach ($this->failures as $failure) {
echo "Type: {$failure['type']}\n";
echo "Value: " . json_encode($failure['value']) . "\n";
if (isset($failure['expected'])) {
echo "Expected: {$failure['expected']}\n";
echo "Actual: {$failure['actual']}\n";
}
if (isset($failure['error']) && $failure['error'] !== 'Mismatch') {
echo "Error: {$failure['error']}\n";
}
echo "\n";
}
}
}
}

// Run tests
$runner = new CompatibilityTestRunner();
$results = $runner->run();

exit($results['failed'] > 0 || $results['errors'] > 0 ? 1 : 0);
Loading
Loading