Add basic testsuite using ui_test

This commit is contained in:
nora 2023-08-01 11:57:44 +02:00
parent 1551847d8c
commit b68d775671
13 changed files with 1524 additions and 79 deletions

View file

@ -20,12 +20,20 @@
{
devShells = forAllSystems ({ pkgs }: {
default = pkgs.mkShell {
buildInputs = with pkgs; [
rustup
];
packages = with pkgs; [
nodejs-18_x # Node.js 18, plus npm, npx, and corepack
wasmtime
wasm-tools
binaryen
];
shellHook = ''
export PATH=$PATH:''${CARGO_HOME:-~/.cargo}/bin
export PATH=$PATH:''${RUSTUP_HOME:-~/.rustup}/toolchains/$RUSTC_VERSION-x86_64-unknown-linux-gnu/bin/
'';
};
});
};

View file

@ -8,6 +8,7 @@
"build": "tsc",
"fmt": "prettier -w .",
"test": "jest",
"ui-test": "npm run build && cargo run --manifest-path tests/Cargo.toml --bin tests",
"lint": "eslint ."
},
"author": "",

View file

@ -11,7 +11,10 @@ export function spanMerge(a: Span, b: Span): Span {
}
export const DUMMY_SPAN = { start: 0, end: 0 };
export const EOF_SPAN = {start: Number.MAX_SAFE_INTEGER, end: Number.MAX_SAFE_INTEGER};
export const EOF_SPAN = {
start: Number.MAX_SAFE_INTEGER,
end: Number.MAX_SAFE_INTEGER,
};
export class CompilerError extends Error {
msg: string;
@ -24,16 +27,18 @@ export class CompilerError extends Error {
}
}
export function withErrorPrinter(
export function withErrorPrinter<R>(
input: string,
filename: string,
f: () => void
): void {
f: () => R,
afterError: (e: CompilerError) => R
): R {
try {
f();
return f();
} catch (e) {
if (e instanceof CompilerError) {
renderError(input, filename, e);
return afterError(e);
} else {
throw e;
}

View file

@ -33,13 +33,26 @@ function linkStd() = (
);
`;
function main() {
type Config = {
input: string;
filename: string;
packageName: string;
debug: Set<string>;
noOutput: boolean;
};
function parseArgs(): Config {
let filename: string;
let input: string;
let packageName: string;
let debug = new Set<string>();
let noOutput = false;
if (process.argv.length > 2) {
filename = process.argv[2];
if (path.extname(filename) !== ".nil") {
console.error(process.argv);
console.error(
`error: filename must have \`.nil\` extension: \`${filename}\``
);
@ -48,12 +61,43 @@ function main() {
input = fs.readFileSync(filename, { encoding: "utf-8" });
packageName = path.basename(filename, ".nil");
const debugArg = process.argv.find((arg) => arg.startsWith("--debug="));
if (debugArg !== undefined) {
const debugs = debugArg.slice("--debug=".length);
debug = new Set(debugs.split(","));
}
if (process.argv.some((arg) => arg === "--no-output")) {
noOutput = true;
}
} else {
filename = "<hardcoded>";
input = INPUT;
packageName = "test";
debug = new Set([
"tokens",
"parsed",
"resolved",
"typecked",
"wat",
"wasm-validate",
]);
}
return {
filename,
input,
packageName,
debug,
noOutput,
};
}
function main() {
const config = parseArgs();
const { filename, packageName, input, debug } = config;
if (!isValidIdent(packageName)) {
console.error(
`error: package name \`${packageName}\` is not a valid identifer`
@ -61,62 +105,85 @@ function main() {
process.exit(1);
}
console.log(`package name: '${packageName}'`);
withErrorPrinter(
input,
filename,
() => {
const start = Date.now();
withErrorPrinter(input, filename, () => {
const start = Date.now();
const tokens = tokenize(input);
console.log("-----TOKENS------------");
console.log(tokens);
const ast: Crate<Built> = parse(packageName, tokens, 0);
console.log("-----AST---------------");
console.dir(ast.rootItems, { depth: 50 });
console.log("-----AST pretty--------");
const printed = printAst(ast);
console.log(printed);
console.log("-----AST resolved------");
const [resolved, crates] = resolve(ast, loadCrate);
const resolvedPrinted = printAst(resolved);
console.log(resolvedPrinted);
console.log("-----AST typecked------");
const typecked: Crate<Typecked> = typeck(resolved, crates);
const typeckPrinted = printAst(typecked);
console.log(typeckPrinted);
console.log("-----wasm--------------");
const wasmModule = lowerToWasm([typecked, ...crates]);
const moduleStringColor = writeModuleWatToString(wasmModule, true);
const moduleString = writeModuleWatToString(wasmModule);
console.log(moduleStringColor);
fs.writeFileSync("out.wat", moduleString);
console.log("--validate wasm-tools--");
exec("wasm-tools validate out.wat", (error, stdout, stderr) => {
if (error && error.code === 1) {
console.log(stderr);
} else if (error) {
console.error(`failed to spawn wasm-tools: ${error.message}`);
} else {
if (stderr) {
console.log(stderr);
}
if (stdout) {
console.log(stdout);
}
const tokens = tokenize(input);
if (debug.has("tokens")) {
console.log("-----TOKENS------------");
console.log(tokens);
}
console.log(`finished in ${Date.now() - start}ms`);
});
});
const ast: Crate<Built> = parse(packageName, tokens, 0);
if (debug.has("ast")) {
console.log("-----AST---------------");
console.dir(ast.rootItems, { depth: 50 });
console.log("-----AST pretty--------");
const printed = printAst(ast);
console.log(printed);
}
if (debug.has("resolved")) {
console.log("-----AST resolved------");
}
const [resolved, crates] = resolve(ast, loadCrate);
if (debug.has("resolved")) {
const resolvedPrinted = printAst(resolved);
console.log(resolvedPrinted);
}
if (debug.has("typecked")) {
console.log("-----AST typecked------");
}
const typecked: Crate<Typecked> = typeck(resolved, crates);
if (debug.has("typecked")) {
const typeckPrinted = printAst(typecked);
console.log(typeckPrinted);
}
if (debug.has("wat")) {
console.log("-----wasm--------------");
}
const wasmModule = lowerToWasm([typecked, ...crates]);
const moduleStringColor = writeModuleWatToString(wasmModule, true);
const moduleString = writeModuleWatToString(wasmModule);
if (debug.has("wat")) {
console.log(moduleStringColor);
}
if (!config.noOutput) {
fs.writeFileSync("out.wat", moduleString);
}
if (debug.has("wasm-validate")) {
console.log("--validate wasm-tools--");
exec("wasm-tools validate out.wat", (error, stdout, stderr) => {
if (error && error.code === 1) {
console.log(stderr);
} else if (error) {
console.error(`failed to spawn wasm-tools: ${error.message}`);
} else {
if (stderr) {
console.log(stderr);
}
if (stdout) {
console.log(stdout);
}
}
console.log(`finished in ${Date.now() - start}ms`);
});
}
},
() => process.exit(1)
);
}
function loadCrate(
@ -134,7 +201,7 @@ function loadCrate(
}
const filename = `${name}.nil`;
let input;
let input: string;
try {
input = fs.readFileSync(filename, { encoding: "utf-8" });
} catch (e) {
@ -144,24 +211,25 @@ function loadCrate(
);
}
try {
const tokens = tokenize(input);
const ast = parse(name, tokens, crateId.next());
const [resolved, crates] = resolve(ast, loadCrate);
console.log(resolved);
return withErrorPrinter(
input,
filename,
() => {
const tokens = tokenize(input);
const ast = parse(name, tokens, crateId.next());
const [resolved, crates] = resolve(ast, loadCrate);
console.log(resolved);
const typecked = typeck(resolved, [...existingCrates, ...crates]);
return [typecked, crates];
} catch (e) {
withErrorPrinter(input, filename, () => {
throw e;
});
throw new CompilerError(
`failed to load crate ${name}: crate contains errors`,
span
);
}
const typecked = typeck(resolved, [...existingCrates, ...crates]);
return [typecked, crates];
},
() => {
throw new CompilerError(
`failed to load crate ${name}: crate contains errors`,
span
);
}
);
}
main();

View file

@ -101,8 +101,6 @@ function appendData(cx: Context, newData: Uint8Array): number {
});
return 0;
} else {
console.log("appending", newData);
const data = datas[0];
const idx = data.init.length;
const init = new Uint8Array(data.init.length + newData.length);

1
tests/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

1224
tests/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

9
tests/Cargo.toml Normal file
View file

@ -0,0 +1,9 @@
[package]
name = "tests"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
ui_test = "0.12.1"

View file

@ -0,0 +1,35 @@
fn main() {
let mut args = std::env::args().skip(1).collect::<Vec<_>>();
let mut after_edition = false;
args.retain(|arg| {
// drop some stuff we dont care about
if arg.starts_with("--crate-type") {
return false;
}
if arg == "--edition" {
after_edition = true;
return false;
}
if after_edition {
after_edition = false;
return false;
}
true
});
// We don't want out.wat polluting things.
args.push("--no-output".into());
let result = std::process::Command::new("node")
.arg(".")
.args(args)
.spawn()
.unwrap()
.wait()
.unwrap();
let code = result.code().unwrap_or(1);
std::process::exit(code);
}

54
tests/src/main.rs Normal file
View file

@ -0,0 +1,54 @@
use ui_test::{
clap::Parser, default_filter_by_arg, default_per_file_config, status_emitter, Args,
CommandBuilder, Config, Mode, OutputConflictHandling,
};
fn main() {
std::process::Command::new("cargo")
.args(&[
"build",
"--manifest-path",
"tests/Cargo.toml",
"--bin",
"nilc-wrapper",
])
.spawn()
.unwrap()
.wait()
.unwrap()
.success()
.then_some(())
.unwrap_or_else(|| std::process::exit(1));
let mut config = Config::rustc("tests/ui");
config.host = Some("wasm :3".into());
config.program = CommandBuilder::cmd("tests/target/debug/nilc-wrapper");
config.mode = Mode::Fail {
require_patterns: false,
};
let args = Args::parse();
let text = if args.quiet {
status_emitter::Text::quiet()
} else {
status_emitter::Text::verbose()
};
if !args.check && std::env::var_os("GITHUB_ACTIONS").is_none() {
config.output_conflict_handling = OutputConflictHandling::Bless;
}
let result = ui_test::run_tests_generic(
config,
args,
|path, args| {
path.extension().is_some_and(|ext| ext == "nil") && default_filter_by_arg(path, args)
},
default_per_file_config,
(text, status_emitter::Gha::<true> { name: "ui".into() }),
);
if let Err(result) = result {
println!("{:?}", result);
}
}

View file

@ -0,0 +1,19 @@
//@check-pass
function main() = (
noArgs();
singleArg("hi!");
manyArgs(1,2,3,4,5,6);
let a: () = returnNothing();
let b: () = returnExplicitUnit();
let c: String = returnString();
);
function noArgs() =;
function singleArg(a: String) =;
function manyArgs(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int) =;
function returnNothing() =;
function returnExplicitUnit(): () =;
function returnString(): String = "uwu";

2
tests/ui/hello_world.nil Normal file
View file

@ -0,0 +1,2 @@
//@check-pass
function main() = print("Hello, world!\n");

View file

@ -0,0 +1,21 @@
//@check-pass
type CustomType = {};
function main() = ();
function assigns(
a: Int,
b: I32,
c: String,
d: Bool,
e: CustomType,
f: (Int, I32),
) = (
let a1: Int = a;
let b1: I32 = b;
let c1: String = c;
let d1: Bool = d;
let e1: CustomType = e;
let f1: (Int, I32) = f;
);