blob: 3ff41afe41a09f239574cd9f9957806959de9f55 [file] [log] [blame]
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Tests for read & write functions.
*/
import * as Process from '../js/process.js';
import * as SyscallEntry from '../js/syscall_entry.js';
import * as SyscallHandler from '../js/syscall_handler.js';
import * as util from '../js/util.js';
import * as WASI from '../js/wasi.js';
describe('read-write.js', () => {
class FileHandle {
constructor() {
this.buf = new Uint8Array(0);
// NB: This is not how a standard file handle behaves -- it only has one
// file offset pointer, and it moves based on reads & writes. We maintain
// separate ones here purely for ease of testing: it allows us to write to
// a fd then quickly read the data back out. To support a single offset,
// we'd have to implement & include lseek usage in our testing.
this.write_pos = 0;
this.read_pos = 0;
}
write(buf) {
const ret = this.pwrite(buf, this.write_pos);
this.write_pos += ret;
return ret;
}
pwrite(buf, offset) {
offset = Number(offset);
if (this.buf.length < offset + buf.length) {
const newbuf = new Uint8Array(offset + buf.length);
newbuf.set(this.buf);
this.buf = newbuf;
}
this.buf.set(buf, offset);
return buf.length;
}
read(length) {
const buf = this.pread(length, this.read_pos);
this.read_pos += buf.length;
return buf;
}
pread(length, offset) {
const off = Number(offset);
const buf = this.buf.slice(off, off + length);
return buf;
}
}
/**
* A handler just to capture output.
*/
class TestSyscallHandler extends SyscallHandler.DirectWasiPreview1 {
constructor(...args) {
super(...args);
this.stdout = '';
this.stderr = '';
this.td = new TextDecoder();
this.fd = {};
}
/** @override */
handle_fd_write(fd, buf) {
if (fd < 0) {
return WASI.errno.EBADF;
}
switch (fd) {
case 0:
return WASI.errno.EINVAL;
case 1:
this.stdout += this.td.decode(buf, {stream: true});
return WASI.errno.ESUCCESS;
case 2:
this.stderr += this.td.decode(buf, {stream: true});
return WASI.errno.ESUCCESS;
default: {
let fh = this.fd[fd];
if (fh === undefined) {
fh = this.fd[fd] = new FileHandle();
}
fh.write(buf);
return WASI.errno.ESUCCESS;
}
}
}
/** @override */
handle_fd_pwrite(fd, buf, offset) {
if (fd < 0) {
return WASI.errno.EBADF;
}
switch (fd) {
case 0:
case 1:
case 2:
return WASI.errno.EINVAL;
default: {
let fh = this.fd[fd];
if (fh === undefined) {
fh = this.fd[fd] = new FileHandle();
}
fh.pwrite(buf, offset);
return WASI.errno.ESUCCESS;
}
}
}
/** @override */
handle_fd_read(fd, length) {
if (fd < 0) {
return WASI.errno.EBADF;
}
switch (fd) {
case 0:
return WASI.errno.EAGAIN;
case 1:
case 2:
return WASI.errno.EINVAL;
default: {
const fh = this.fd[fd];
if (fh === undefined) {
return WASI.errno.EBADF;
}
const buf = fh.read(length);
return {
buf: buf,
nread: buf.length,
};
}
}
}
/** @override */
handle_fd_pread(fd, length, offset) {
if (fd < 0) {
return WASI.errno.EBADF;
}
switch (fd) {
case 0:
return WASI.errno.EAGAIN;
case 1:
case 2:
return WASI.errno.EINVAL;
default: {
const fh = this.fd[fd];
if (fh === undefined) {
return WASI.errno.EBADF;
}
const buf = fh.pread(length, offset);
return {
buf: buf,
nread: buf.length,
};
}
}
}
}
/**
* Helper function to run the wasm module & return output.
*
* @param {!ArrayBuffer} prog The program to run.
* @param {!Array<string>} argv The program arguments.
* @return {!Object} The program results.
*/
async function run(prog, argv) {
const handler = new TestSyscallHandler();
const sys_handlers = [handler];
const proc = new Process.Foreground({
executable: prog,
argv: ['read-write.wasm', ...argv],
sys_handlers: sys_handlers,
sys_entries: [
new SyscallEntry.WasiPreview1({sys_handlers}),
],
});
let ret;
try {
ret = await proc.run();
} catch (e) {
assert.fail(`${handler.stdout}\n${handler.stderr}\n${e}`);
}
return {
returncode: ret,
stdout: handler.stdout,
stderr: handler.stderr,
};
}
/**
* Load some common state that all tests in here want.
*/
before(async function() {
/**
* Fetch & read the body once to speed up the tests.
*
* @type {!ArrayBuffer}
*/
this.prog = await fetch('read-write.wasm')
.then((response) => response.arrayBuffer());
});
/**
* Check internal assert handling.
*/
it('asserts', async function() {
await run(this.prog, [
'clear-errno',
'ret', '0',
'errno', '0',
'string', '',
'lstring', '1', '',
]);
});
/**
* Verify read() works.
*/
it('read', async function() {
await run(this.prog, [
// Read an invalid fd.
'clear-errno',
'read', '123', '1',
'ret', '-1',
'errno', `${WASI.errno.EBADF}`,
// Write to an fd and then read the data back out.
'clear-errno',
'write', '3', 'abcde',
'read', '3', '5',
'ret', '5',
'errno', '0',
'string', 'abcde',
]);
});
/**
* Verify readv() works.
*/
it('readv', async function() {
await run(this.prog, [
// Read an invalid fd.
'clear-errno',
'readv', '123', '1', '1',
'ret', '-1',
'errno', `${WASI.errno.EBADF}`,
// Write to an fd and then read the data back out.
'clear-errno',
'write', '3', 'abcde',
'readv', '3', '4', '2', '0', '2', '1',
'ret', '5',
'errno', '0',
'string', 'abcde',
]);
});
/**
* Verify pread() works.
*/
it('pread', async function() {
await run(this.prog, [
// Read an invalid fd.
'clear-errno',
'pread', '123', '1', '1',
'ret', '-1',
'errno', `${WASI.errno.EBADF}`,
// Write to an fd and then read the data back out.
'clear-errno',
'write', '3', 'abcde',
'pread', '3', '2', '2',
'ret', '2',
'errno', '0',
'string', 'cd',
'pread', '3', '3', '1',
'ret', '3',
'errno', '0',
'string', 'bcd',
]);
});
/**
* Verify preadv() works.
*/
it('preadv', async function() {
await run(this.prog, [
// Read an invalid fd.
'clear-errno',
'preadv', '123', '1', '1', '1',
'ret', '-1',
'errno', `${WASI.errno.EBADF}`,
// Write to an fd and then read the data back out.
'clear-errno',
'write', '3', 'abcde',
'preadv', '3', '2', '2', '1', '2',
'ret', '3',
'errno', '0',
'string', 'cde',
]);
});
/**
* Verify write() works.
*/
it('write', async function() {
await run(this.prog, [
// Write an invalid fd.
'clear-errno',
'write', '-123', 'str',
'ret', '-1',
'errno', `${WASI.errno.EBADF}`,
// Write to an fd and then read the data back out.
'clear-errno',
'write', '3', 'abcde',
'ret', '5',
'errno', '0',
'read', '3', '5',
'string', 'abcde',
]);
});
/**
* Verify writev() works.
*/
it('writev', async function() {
await run(this.prog, [
// Write an invalid fd.
'clear-errno',
'writev', '-123', '1', 'str',
'ret', '-1',
'errno', `${WASI.errno.EBADF}`,
// Write to an fd and then read the data back out.
'clear-errno',
'writev', '3', '3', 'a', 'bcd', 'e',
'ret', '5',
'errno', '0',
'read', '3', '5',
'string', 'abcde',
]);
});
/**
* Verify pwrite() works.
*/
it('pwrite', async function() {
await run(this.prog, [
// Write an invalid fd.
'clear-errno',
'pwrite', '-123', 'str', '1',
'ret', '-1',
'errno', `${WASI.errno.EBADF}`,
// Write to an fd and then read the data back out.
'clear-errno',
'pwrite', '3', 'Xb', '0',
'ret', '2',
'errno', '0',
'pread', '3', '5', '0',
'string', 'Xb',
'write', '3', 'a',
'ret', '1',
'errno', '0',
'pread', '3', '5', '0',
'string', 'ab',
'pwrite', '3', 'cde', '2',
'ret', '3',
'errno', '0',
'pread', '3', '5', '0',
'string', 'abcde',
]);
});
/**
* Verify pwritev() works.
*/
it('pwritev', async function() {
await run(this.prog, [
// Write an invalid fd.
'clear-errno',
'pwritev', '-123', '1', '1', 'str',
'ret', '-1',
'errno', `${WASI.errno.EBADF}`,
// Write to an fd and then read the data back out.
'clear-errno',
'pwritev', '3', '0', '2', 'Xe', 'abcde',
'ret', '7',
'errno', '0',
'pread', '3', '5', '0',
'string', 'Xeabc',
]);
});
});