mirror of
https://github.com/copy/v86.git
synced 2025-12-31 04:23:15 +00:00
moved text matching code from expect() to V86.wait_until_vga_screen_contains()
All changes to V86.wait_until_vga_screen_contains() are backward compatible.
This commit is contained in:
parent
b91afaa821
commit
00203987f4
2 changed files with 115 additions and 121 deletions
|
|
@ -1358,52 +1358,126 @@ V86.prototype.automatically = function(steps)
|
|||
run(steps);
|
||||
};
|
||||
|
||||
V86.prototype.wait_until_vga_screen_contains = function(text)
|
||||
/**
|
||||
* Wait until expected text is present on the VGA text screen.
|
||||
*
|
||||
* Returns immediately if the expected text is already present on screen
|
||||
* at the time this funtion is called.
|
||||
*
|
||||
* An optional timeout may be specified in `options.timeout_msec`, an Error
|
||||
* is thrown if the timeout expires before the expected text could be
|
||||
* detected. If no timeout is specified then this function only unblocks
|
||||
* once text detection succeeds.
|
||||
*
|
||||
* Expected text (or texts, see below) must be of type string or RegExp.
|
||||
*
|
||||
* Two methods of text detection are supported depending on the type of the
|
||||
* argument `expected`:
|
||||
*
|
||||
* 1. If `expected` is a string or RegExp then the given text string or
|
||||
* regular expression must partially or fully match any line on screen.
|
||||
*
|
||||
* 2. If `expected` is an array of strings and/or RegExp objects then the
|
||||
* list of expected lines must match exactly at "the bottom" of the
|
||||
* screen. The "bottom" line is the first non-empty line starting from
|
||||
* the screen's end.
|
||||
* Expected lines specified as strings must match the entire line on
|
||||
* screen, and regular expressions are matched against full screen
|
||||
* lines but may match partially and use wildcards.
|
||||
* Expected lines should not contain any trailing whitespace and/or
|
||||
* newline characters. Expecting an empty line is valid.
|
||||
*
|
||||
* Returns the array of lines that matched on screen for both methods.
|
||||
*
|
||||
* @param {string|RegExp|Array<string|RegExp>} expected
|
||||
* @param {{timeout_msec:(number|undefined)}=} options
|
||||
*/
|
||||
V86.prototype.wait_until_vga_screen_contains = async function(expected, options)
|
||||
{
|
||||
return new Promise(resolve =>
|
||||
{
|
||||
function test_line(line)
|
||||
{
|
||||
return typeof text === "string" ? line.includes(text) : text.test(line);
|
||||
}
|
||||
const match_multi = Array.isArray(expected);
|
||||
const timeout_msec = options?.timeout_msec ? options.timeout_msec : 0;
|
||||
const changed_rows = new Set();
|
||||
const screen_put_char = args => changed_rows.add(args[0]);
|
||||
const contains_expected = expected.test ? expected.test : line => line.includes(expected);
|
||||
|
||||
for(const line of this.screen_adapter.get_text_screen())
|
||||
this.add_listener("screen-put-char", screen_put_char);
|
||||
try
|
||||
{
|
||||
const screen_lines = [];
|
||||
for(const screen_line of this.screen_adapter.get_text_screen())
|
||||
{
|
||||
if(test_line(line))
|
||||
if(match_multi)
|
||||
{
|
||||
resolve(true);
|
||||
return;
|
||||
screen_lines.push(screen_line.trimRight());
|
||||
}
|
||||
else if(contains_expected(screen_line))
|
||||
{
|
||||
return [screen_line];
|
||||
}
|
||||
}
|
||||
|
||||
const changed_rows = new Set();
|
||||
|
||||
function put_char(args)
|
||||
const tm_end = timeout_msec ? performance.now() + timeout_msec : 0;
|
||||
while(true)
|
||||
{
|
||||
const [row, col, char] = args;
|
||||
changed_rows.add(row);
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
if(tm_end && performance.now() >= tm_end)
|
||||
{
|
||||
throw new Error("Timeout of " + timeout_msec + " msec expired");
|
||||
}
|
||||
|
||||
const check = () =>
|
||||
{
|
||||
for(const row of changed_rows)
|
||||
{
|
||||
const line = this.screen_adapter.get_text_row(row);
|
||||
if(test_line(line))
|
||||
const screen_line = this.screen_adapter.get_text_row(row);
|
||||
if(match_multi)
|
||||
{
|
||||
this.remove_listener("screen-put-char", put_char);
|
||||
resolve();
|
||||
return;
|
||||
screen_lines[row] = screen_line.trimRight();
|
||||
}
|
||||
else if(contains_expected(screen_line))
|
||||
{
|
||||
return [screen_line];
|
||||
}
|
||||
}
|
||||
|
||||
changed_rows.clear();
|
||||
setTimeout(check, 100);
|
||||
};
|
||||
check();
|
||||
|
||||
this.add_listener("screen-put-char", put_char);
|
||||
});
|
||||
if(!match_multi)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let screen_height = screen_lines.length;
|
||||
while(screen_height > 0 && screen_lines[screen_height - 1] === "")
|
||||
{
|
||||
screen_height--;
|
||||
}
|
||||
const screen_offset = screen_height - expected.length;
|
||||
if(screen_offset < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let matches = true;
|
||||
for(let i = 0; i < expected.length && matches; i++)
|
||||
{
|
||||
if(expected[i].test)
|
||||
{
|
||||
matches = expected[i].test(screen_lines[screen_offset + i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
matches = screen_lines[screen_offset + i] === expected[i];
|
||||
}
|
||||
}
|
||||
if(matches)
|
||||
{
|
||||
return screen_lines.slice(screen_offset, screen_height);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.remove_listener("screen-put-char", screen_put_char);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ const { V86 } = await import(TEST_RELEASE_BUILD ? "../../build/libv86.mjs" : "..
|
|||
|
||||
process.on("unhandledRejection", exn => { throw exn; });
|
||||
|
||||
async function exec_test(test_name, v86_config, timeout_sec, test_function)
|
||||
export async function exec_test(test_name, v86_config, timeout_sec, test_function)
|
||||
{
|
||||
console.log("Starting: " + test_name);
|
||||
const tm_start = performance.now();
|
||||
|
|
@ -26,7 +26,7 @@ async function exec_test(test_name, v86_config, timeout_sec, test_function)
|
|||
}, timeout_sec * 1000);
|
||||
try
|
||||
{
|
||||
await new Promise(resolve => { emulator.bus.register("emulator-started", () => { resolve(); }); });
|
||||
await new Promise(resolve => emulator.bus.register("emulator-started", () => resolve()));
|
||||
await test_function(emulator);
|
||||
console.log("Done: " + test_name + " (" + ((performance.now() - tm_start) / 1000).toFixed(2) + " sec)");
|
||||
}
|
||||
|
|
@ -50,123 +50,43 @@ async function exec_test(test_name, v86_config, timeout_sec, test_function)
|
|||
* Execute given CLI command and wait for its completion.
|
||||
*
|
||||
* Injects command into the guest's keyboard buffer, then waits for both the
|
||||
* command and the expected response lines to show at "the bottom" of the
|
||||
* VGA screen. The "bottom" line is the first non-empty line from the VGA
|
||||
* text screen's bottom.
|
||||
* command and the expected response lines to show at the bottom of the VGA
|
||||
* screen.
|
||||
*
|
||||
* If command is empty then no command is executed and only the expected
|
||||
* response lines are waited for. If command contains only whitespace and/or
|
||||
* newline characters then it is send to the guest, but it does not become
|
||||
* part of the expected response.
|
||||
*
|
||||
* Items in response_lines must be of type string or RegExp. A regular
|
||||
* expression is matched against the complete screen line, whereas a string
|
||||
* is matched against the entire screen line. Expected response lines should
|
||||
* not contain any trailing whitespace and/or newline characters. Expecting
|
||||
* an empty line is valid.
|
||||
*
|
||||
* Allowed character set for command and response_lines is the printable
|
||||
* subset of 7-bit ASCII, use newline character "\n" to encode ENTER key.
|
||||
* Allowed character set for command and expected is the printable subset
|
||||
* of 7-bit ASCII, use newline character "\n" to encode ENTER key.
|
||||
*
|
||||
* Returns the matched response lines. Throws an Error if the given timeout
|
||||
* elapsed before the expected response could be detected.
|
||||
*
|
||||
* @param {V86} emulator
|
||||
* @param {string} command
|
||||
* @param {Array[string|RegExp]} response_lines
|
||||
* @param {number} response_timeout_msec
|
||||
* @return {Array[string]}
|
||||
* @param {Array<string|RegExp>} expected
|
||||
* @param {number} timeout_msec
|
||||
*/
|
||||
async function expect(emulator, command, response_lines, response_timeout_msec)
|
||||
export async function expect(emulator, command, expected, timeout_msec)
|
||||
{
|
||||
if(command)
|
||||
{
|
||||
// inject command characters into guest's keyboard buffer
|
||||
for(const ch of command)
|
||||
{
|
||||
emulator.keyboard_send_text(ch);
|
||||
await pause(10);
|
||||
}
|
||||
|
||||
// trim trailing newline (and/or whitespace) from command
|
||||
command = command.trimRight();
|
||||
if(command)
|
||||
{
|
||||
// prepend command to expected response lines
|
||||
response_lines = [command, ...response_lines];
|
||||
expected = [new RegExp(RegExp.escape(command) + "$"), ...expected];
|
||||
}
|
||||
}
|
||||
|
||||
const changed_rows = new Set();
|
||||
const screen_put_char = args => { changed_rows.add(args[0]); };
|
||||
emulator.add_listener("screen-put-char", screen_put_char);
|
||||
try
|
||||
{
|
||||
// initialize VGA screen buffer
|
||||
const screen_lines = [];
|
||||
for(const line of emulator.screen_adapter.get_text_screen())
|
||||
{
|
||||
screen_lines.push(line.trimRight());
|
||||
}
|
||||
|
||||
const tm_end = performance.now() + response_timeout_msec;
|
||||
while(performance.now() < tm_end)
|
||||
{
|
||||
await pause(100);
|
||||
|
||||
// update VGA screen buffer
|
||||
for(const row of changed_rows)
|
||||
{
|
||||
screen_lines[row] = emulator.screen_adapter.get_text_row(row).trimRight();
|
||||
}
|
||||
changed_rows.clear();
|
||||
|
||||
let screen_bottom = screen_lines.length;
|
||||
while(screen_bottom > 0 && screen_lines[screen_bottom - 1] === "")
|
||||
{
|
||||
screen_bottom--;
|
||||
}
|
||||
const screen_offset = screen_bottom - response_lines.length;
|
||||
if(screen_offset < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let matches = true;
|
||||
for(let i = 0; i < response_lines.length && matches; i++)
|
||||
{
|
||||
if(i === 0 && command)
|
||||
{
|
||||
// match raw command against end of screen line
|
||||
matches = screen_lines[screen_offset + i].endsWith(response_lines[i]);
|
||||
}
|
||||
else if(response_lines[i].test)
|
||||
{
|
||||
// match screen line against anything that implements test(), for example a RegExp
|
||||
matches = response_lines[i].test(screen_lines[screen_offset + i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// match exact
|
||||
matches = screen_lines[screen_offset + i] === response_lines[i];
|
||||
}
|
||||
}
|
||||
if(matches)
|
||||
{
|
||||
return screen_lines.slice(screen_offset, screen_bottom);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("Timeout in command \"" + command + "\"");
|
||||
}
|
||||
finally
|
||||
{
|
||||
emulator.remove_listener("screen-put-char", screen_put_char);
|
||||
}
|
||||
return await emulator.wait_until_vga_screen_contains(expected, {timeout_msec: timeout_msec});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const CONFIG_MSDOS622_HD = {
|
||||
bios: { url: __dirname + "/../../bios/seabios.bin" },
|
||||
vga_bios: { url: __dirname + "/../../bios/vgabios.bin" },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue