-
Notifications
You must be signed in to change notification settings - Fork 66
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(loader): fix to support new metering formats + test cases
- Loading branch information
1 parent
4d846f7
commit 0d4a977
Showing
19 changed files
with
303 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
/* eslint-disable no-prototype-builtins */ | ||
|
||
import { describe, it } from 'node:test' | ||
import * as assert from 'node:assert' | ||
import fs from 'fs' | ||
import { Readable } from 'node:stream' | ||
import { createReadStream } from 'node:fs' | ||
|
||
/** | ||
* dynamic import, so we can run unit tests against the source | ||
* and integration tests against the bundled distribution | ||
*/ | ||
const MODULE_PATH = process.env.MODULE_PATH || '../src/index.cjs' | ||
|
||
console.log(`${MODULE_PATH}`) | ||
|
||
const { default: AoLoader } = await import(MODULE_PATH) | ||
|
||
/** | ||
* Test cases for both 32-bit and 64-bit versions. | ||
* Each test case includes the binary path and options specific to that architecture. | ||
*/ | ||
const testCases = [ | ||
{ | ||
name: 'Emscripten4 (32-bit)', // Test for the 32-bit WASM | ||
binaryPath: './test/emscripten4/process.wasm', // Path to the 32-bit WASM binary | ||
options: { format: 'wasm32-unknown-emscripten4' } // Format for 32-bit | ||
}, | ||
{ | ||
name: 'Wasm64-Emscripten (64-bit)', // Test for the 64-bit WASM | ||
binaryPath: './test/wasm64-emscripten/process.wasm', // Path to the 64-bit WASM binary | ||
options: { format: 'wasm64-unknown-emscripten-draft_2024_02_15' } // Format for 64-bit | ||
}, | ||
{ | ||
name: 'Emscripten4 Metering (32-bit)', // Test for the 32-bit WASM | ||
binaryPath: './test/emscripten4/process.wasm', // Path to the 32-bit WASM binary | ||
options: { format: 'wasm32-unknown-emscripten-metering' } // Format for 32-bit metering | ||
}, | ||
{ | ||
name: 'Wasm64-Emscripten Metering (64-bit)', // Test for the 64-bit WASM | ||
binaryPath: './test/wasm64-emscripten/process.wasm', // Path to the 64-bit WASM binary | ||
options: { format: 'wasm64-unknown-emscripten-draft_2024_10_16-metering' } // Format for 64-bit metering | ||
} | ||
] | ||
|
||
/* Helper function to generate test messages */ | ||
function getMsg (Data, Action = 'Eval') { | ||
return { | ||
Target: '1', | ||
From: 'FOOBAR', | ||
Owner: 'FOOBAR', | ||
Module: 'FOO', | ||
Id: '1', | ||
'Block-Height': '1000', | ||
Timestamp: Date.now(), | ||
Tags: [{ name: 'Action', value: Action }], | ||
Data | ||
} | ||
} | ||
|
||
/* Helper function to generate test environment variables */ | ||
function getEnv () { | ||
return { | ||
Process: { | ||
Id: '1', | ||
Owner: 'FOOBAR', | ||
Tags: [{ name: 'Name', value: 'TEST_PROCESS_OWNER' }] | ||
} | ||
} | ||
} | ||
|
||
describe('AoLoader Functionality Tests', () => { | ||
// Iterate over each test case (32-bit and 64-bit) | ||
for (const testCase of testCases) { | ||
const { name, binaryPath, options } = testCase | ||
const wasmBinary = fs.readFileSync(binaryPath) // Load the WASM binary | ||
|
||
describe(`${name}`, () => { | ||
it('load wasm and evaluate message', async () => { | ||
const handle = await AoLoader(wasmBinary, options) | ||
const mainResult = await handle(null, getMsg('return \'Hello World\''), getEnv()) | ||
|
||
// Check basic properties of the result | ||
assert.ok(mainResult.Memory) | ||
assert.ok(mainResult.hasOwnProperty('Messages')) | ||
assert.ok(mainResult.hasOwnProperty('Spawns')) | ||
assert.ok(mainResult.hasOwnProperty('Error')) | ||
assert.equal(mainResult.Output.data, 'Hello World') // Expect 'Hello World' in output | ||
}) | ||
|
||
it('should use separately instantiated WebAssembly.Instance', async () => { | ||
// Compile and instantiate a separate WebAssembly instance | ||
const wasmModuleP = WebAssembly.compileStreaming( | ||
new Response( | ||
Readable.toWeb(createReadStream(binaryPath)), | ||
{ headers: { 'Content-Type': 'application/wasm' } } | ||
), | ||
options | ||
) | ||
|
||
// Set up a loader and check the separate instance | ||
const handle = await AoLoader((info, receiveInstance) => { | ||
assert.ok(info) | ||
assert.ok(receiveInstance) | ||
wasmModuleP | ||
.then((mod) => WebAssembly.instantiate(mod, info)) | ||
.then((instance) => receiveInstance(instance)) | ||
}, options) | ||
|
||
// Evaluate the message and verify the result | ||
const result = await handle(null, getMsg('count = 1\nreturn count'), getEnv()) | ||
assert.equal(result.Output.data, 1) | ||
|
||
const result2 = await handle(result.Memory, getMsg('count = count + 1\nreturn count'), getEnv()) | ||
assert.equal(result2.Output.data, 2) | ||
}) | ||
|
||
/* TODO: Add tests to make sure the loader: | ||
1. ) Creates the correct instance based on the format | ||
2. ) Creates a /data directory using FS_createPath | ||
3. ) Correctly determines the doHandle function based on the format | ||
4. ) If a buffer is passed, it is used as the memory and resizes the HEAP | ||
5. ) Checks to make sure Memory, Messages, Spawns, GasUsed, Assignments, Output, and Error are returned | ||
*/ | ||
|
||
// TODO: This test should not be part of loader tests | ||
it('should get deterministic date', async () => { | ||
const handle = await AoLoader(wasmBinary, options) | ||
|
||
// Verify that the date returned is deterministic | ||
const result = await handle(null, getMsg('return os.date("%Y-%m-%d")'), getEnv()) | ||
assert.ok(result.Output.data === '1970-01-01' || result.Output.data === '1969-12-31') | ||
}) | ||
|
||
// TODO: This test should not be part of loader tests | ||
it('should get deterministic random numbers', async () => { | ||
let handle = await AoLoader(wasmBinary, options) | ||
|
||
// Verify deterministic random number generation | ||
const result = await handle(null, getMsg('return math.random(1, 10)'), getEnv()) | ||
assert.equal(result.Output.data, 4) | ||
|
||
handle = await AoLoader(wasmBinary, options) | ||
const result2 = await handle(null, getMsg('return math.random(1, 10)'), getEnv()) | ||
assert.equal(result2.Output.data, 4) | ||
}) | ||
}) | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
/* eslint-disable no-prototype-builtins */ | ||
|
||
import { describe, it } from 'node:test' | ||
import * as assert from 'node:assert' | ||
import fs from 'fs' | ||
|
||
/** | ||
* dynamic import, so we can run unit tests against the source | ||
* and integration tests against the bundled distribution | ||
*/ | ||
const MODULE_PATH = process.env.MODULE_PATH || '../src/index.cjs' | ||
|
||
console.log(`${MODULE_PATH}`) | ||
|
||
const { default: AoLoader } = await import(MODULE_PATH) | ||
|
||
/** | ||
* Test cases for both 32-bit and 64-bit versions. | ||
* Each test case includes the binary path and options specific to that architecture. | ||
*/ | ||
const testCases = [ | ||
{ | ||
name: 'Emscripten4 Metering (32-bit)', // Test for the 32-bit WASM | ||
binaryPath: './test/emscripten4/process.wasm', // Path to the 32-bit WASM binary | ||
options: { format: 'wasm32-unknown-emscripten-metering' }, // Format for 32-bit metering | ||
standardOptions: { format: 'wasm32-unknown-emscripten4' } // Format for 32-bit standard | ||
}, | ||
{ | ||
name: 'Wasm64-Emscripten Metering (64-bit)', // Test for the 64-bit WASM | ||
binaryPath: './test/wasm64-emscripten/process.wasm', // Path to the 64-bit WASM binary | ||
options: { format: 'wasm64-unknown-emscripten-draft_2024_10_16-metering' }, // Format for 64-bit metering | ||
standardOptions: { format: 'wasm64-unknown-emscripten-draft_2024_02_15' } // Format for 64-bit standard | ||
} | ||
] | ||
|
||
/* Helper function to generate test messages */ | ||
function getMsg (Data, Action = 'Eval') { | ||
return { | ||
Target: '1', | ||
From: 'FOOBAR', | ||
Owner: 'FOOBAR', | ||
Module: 'FOO', | ||
Id: '1', | ||
'Block-Height': '1000', | ||
Timestamp: Date.now(), | ||
Tags: [{ name: 'Action', value: Action }], | ||
Data | ||
} | ||
} | ||
|
||
/* Helper function to generate test environment variables */ | ||
function getEnv () { | ||
return { | ||
Process: { | ||
Id: '1', | ||
Owner: 'FOOBAR', | ||
Tags: [{ name: 'Name', value: 'TEST_PROCESS_OWNER' }] | ||
} | ||
} | ||
} | ||
|
||
describe('AoLoader Metering Tests', () => { | ||
// Iterate over each test case (32-bit and 64-bit) | ||
for (const testCase of testCases) { | ||
const { name, binaryPath, options, standardOptions } = testCase | ||
const wasmBinary = fs.readFileSync(binaryPath) // Load the WASM binary | ||
|
||
describe(`${name}`, () => { | ||
it('should use gas for computation', async () => { | ||
const handle = await AoLoader(wasmBinary, options) | ||
|
||
const initial = await handle(null, getMsg('return "OK"'), getEnv()) | ||
|
||
// Test that gas is used for computation | ||
const result = await handle(initial.Memory, getMsg('return 1+1'), getEnv()) | ||
assert.ok(result.GasUsed > 0) | ||
|
||
const result2 = await handle(initial.Memory, getMsg('return 1+1+1'), getEnv()) | ||
assert.ok(result2.GasUsed > result.GasUsed) | ||
}) | ||
|
||
it('should load previous memory and refill gas', async () => { | ||
const handle = await AoLoader(wasmBinary, options) | ||
|
||
// Test loading previous memory and ensuring gas is refilled on each invocation | ||
// The first invocation always uses alot more gas than subsequent invocations due to setup | ||
const initial = await handle(null, getMsg('count = 1\nreturn count'), getEnv()) | ||
assert.equal(initial.Output.data, 1) | ||
assert.ok(initial.GasUsed > 0) | ||
|
||
// Thats why we need to run the function twice to get a more accurate gas usage comparison | ||
const result = await handle(initial.Memory, getMsg('count = count + 1\nreturn count'), getEnv()) | ||
assert.equal(result.Output.data, 2) | ||
assert.ok(result.GasUsed > 0) | ||
|
||
const result2 = await handle(result.Memory, getMsg('count = count + 1\nreturn count'), getEnv()) | ||
assert.equal(result2.Output.data, 3) | ||
assert.ok(result2.GasUsed > 0) | ||
|
||
// Check that gas usage difference is within an acceptable range | ||
const gasDiff = Math.abs(result.GasUsed - result2.GasUsed) | ||
assert.ok(gasDiff < 900000) | ||
}) | ||
|
||
it('should run out of gas', async () => { | ||
// Test that the function will run out of gas when exceeding the compute limit | ||
const handle = await AoLoader(wasmBinary, { format: options.format, computeLimit: 10_750_000_000 }) | ||
try { | ||
await handle(null, getMsg(` | ||
count = 0 | ||
for i = 1, 1000 do | ||
count = count + 1 | ||
end | ||
return count | ||
`), getEnv()) | ||
assert.ok(false) // Should not reach here | ||
} catch (e) { | ||
assert.equal(e.message, 'out of gas!') // Expect an out-of-gas error | ||
} | ||
}) | ||
|
||
it('metering should only apply to metering formats', async () => { | ||
// Verify that metering only applies to specific formats | ||
const handle = await AoLoader(wasmBinary, standardOptions) | ||
const result = await handle(null, getMsg('return \'Hello World\''), getEnv()) | ||
assert.equal(result.GasUsed, 0) // No gas usage in non-metered format | ||
}) | ||
|
||
/* TODO: Some other possible tests for metering: | ||
1. ) Confirm that WebAssembly.compile is being overridden | ||
2. ) Confirm that WebAssembly.compileStreaming is being overridden | ||
*/ | ||
}) | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.