mirror of
https://github.com/Noratrieb/riverdelta.git
synced 2026-01-16 09:25:03 +01:00
Add basic testsuite using ui_test
This commit is contained in:
parent
1551847d8c
commit
b68d775671
13 changed files with 1524 additions and 79 deletions
|
|
@ -20,12 +20,20 @@
|
||||||
{
|
{
|
||||||
devShells = forAllSystems ({ pkgs }: {
|
devShells = forAllSystems ({ pkgs }: {
|
||||||
default = pkgs.mkShell {
|
default = pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
rustup
|
||||||
|
];
|
||||||
packages = with pkgs; [
|
packages = with pkgs; [
|
||||||
nodejs-18_x # Node.js 18, plus npm, npx, and corepack
|
nodejs-18_x # Node.js 18, plus npm, npx, and corepack
|
||||||
wasmtime
|
wasmtime
|
||||||
wasm-tools
|
wasm-tools
|
||||||
binaryen
|
binaryen
|
||||||
];
|
];
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
export PATH=$PATH:''${CARGO_HOME:-~/.cargo}/bin
|
||||||
|
export PATH=$PATH:''${RUSTUP_HOME:-~/.rustup}/toolchains/$RUSTC_VERSION-x86_64-unknown-linux-gnu/bin/
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"fmt": "prettier -w .",
|
"fmt": "prettier -w .",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
|
"ui-test": "npm run build && cargo run --manifest-path tests/Cargo.toml --bin tests",
|
||||||
"lint": "eslint ."
|
"lint": "eslint ."
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|
|
||||||
15
src/error.ts
15
src/error.ts
|
|
@ -11,7 +11,10 @@ export function spanMerge(a: Span, b: Span): Span {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DUMMY_SPAN = { start: 0, end: 0 };
|
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 {
|
export class CompilerError extends Error {
|
||||||
msg: string;
|
msg: string;
|
||||||
|
|
@ -24,16 +27,18 @@ export class CompilerError extends Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function withErrorPrinter(
|
export function withErrorPrinter<R>(
|
||||||
input: string,
|
input: string,
|
||||||
filename: string,
|
filename: string,
|
||||||
f: () => void
|
f: () => R,
|
||||||
): void {
|
afterError: (e: CompilerError) => R
|
||||||
|
): R {
|
||||||
try {
|
try {
|
||||||
f();
|
return f();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof CompilerError) {
|
if (e instanceof CompilerError) {
|
||||||
renderError(input, filename, e);
|
renderError(input, filename, e);
|
||||||
|
return afterError(e);
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
212
src/index.ts
212
src/index.ts
|
|
@ -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 filename: string;
|
||||||
let input: string;
|
let input: string;
|
||||||
let packageName: string;
|
let packageName: string;
|
||||||
|
let debug = new Set<string>();
|
||||||
|
let noOutput = false;
|
||||||
|
|
||||||
if (process.argv.length > 2) {
|
if (process.argv.length > 2) {
|
||||||
filename = process.argv[2];
|
filename = process.argv[2];
|
||||||
if (path.extname(filename) !== ".nil") {
|
if (path.extname(filename) !== ".nil") {
|
||||||
|
console.error(process.argv);
|
||||||
|
|
||||||
console.error(
|
console.error(
|
||||||
`error: filename must have \`.nil\` extension: \`${filename}\``
|
`error: filename must have \`.nil\` extension: \`${filename}\``
|
||||||
);
|
);
|
||||||
|
|
@ -48,12 +61,43 @@ function main() {
|
||||||
|
|
||||||
input = fs.readFileSync(filename, { encoding: "utf-8" });
|
input = fs.readFileSync(filename, { encoding: "utf-8" });
|
||||||
packageName = path.basename(filename, ".nil");
|
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 {
|
} else {
|
||||||
filename = "<hardcoded>";
|
filename = "<hardcoded>";
|
||||||
input = INPUT;
|
input = INPUT;
|
||||||
packageName = "test";
|
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)) {
|
if (!isValidIdent(packageName)) {
|
||||||
console.error(
|
console.error(
|
||||||
`error: package name \`${packageName}\` is not a valid identifer`
|
`error: package name \`${packageName}\` is not a valid identifer`
|
||||||
|
|
@ -61,62 +105,85 @@ function main() {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`package name: '${packageName}'`);
|
withErrorPrinter(
|
||||||
|
input,
|
||||||
|
filename,
|
||||||
|
() => {
|
||||||
|
const start = Date.now();
|
||||||
|
|
||||||
withErrorPrinter(input, filename, () => {
|
const tokens = tokenize(input);
|
||||||
const start = Date.now();
|
if (debug.has("tokens")) {
|
||||||
|
console.log("-----TOKENS------------");
|
||||||
const tokens = tokenize(input);
|
console.log(tokens);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
function loadCrate(
|
||||||
|
|
@ -134,7 +201,7 @@ function loadCrate(
|
||||||
}
|
}
|
||||||
|
|
||||||
const filename = `${name}.nil`;
|
const filename = `${name}.nil`;
|
||||||
let input;
|
let input: string;
|
||||||
try {
|
try {
|
||||||
input = fs.readFileSync(filename, { encoding: "utf-8" });
|
input = fs.readFileSync(filename, { encoding: "utf-8" });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -144,24 +211,25 @@ function loadCrate(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
return withErrorPrinter(
|
||||||
const tokens = tokenize(input);
|
input,
|
||||||
const ast = parse(name, tokens, crateId.next());
|
filename,
|
||||||
const [resolved, crates] = resolve(ast, loadCrate);
|
() => {
|
||||||
console.log(resolved);
|
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]);
|
const typecked = typeck(resolved, [...existingCrates, ...crates]);
|
||||||
return [typecked, crates];
|
return [typecked, crates];
|
||||||
} catch (e) {
|
},
|
||||||
withErrorPrinter(input, filename, () => {
|
() => {
|
||||||
throw e;
|
throw new CompilerError(
|
||||||
});
|
`failed to load crate ${name}: crate contains errors`,
|
||||||
throw new CompilerError(
|
span
|
||||||
`failed to load crate ${name}: crate contains errors`,
|
);
|
||||||
span
|
}
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
main();
|
main();
|
||||||
|
|
|
||||||
|
|
@ -101,8 +101,6 @@ function appendData(cx: Context, newData: Uint8Array): number {
|
||||||
});
|
});
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
console.log("appending", newData);
|
|
||||||
|
|
||||||
const data = datas[0];
|
const data = datas[0];
|
||||||
const idx = data.init.length;
|
const idx = data.init.length;
|
||||||
const init = new Uint8Array(data.init.length + newData.length);
|
const init = new Uint8Array(data.init.length + newData.length);
|
||||||
|
|
|
||||||
1
tests/.gitignore
vendored
Normal file
1
tests/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/target
|
||||||
1224
tests/Cargo.lock
generated
Normal file
1224
tests/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
9
tests/Cargo.toml
Normal file
9
tests/Cargo.toml
Normal 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"
|
||||||
35
tests/src/bin/nilc-wrapper.rs
Normal file
35
tests/src/bin/nilc-wrapper.rs
Normal 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
54
tests/src/main.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
tests/ui/function_calls_ok.nil
Normal file
19
tests/ui/function_calls_ok.nil
Normal 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
2
tests/ui/hello_world.nil
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
//@check-pass
|
||||||
|
function main() = print("Hello, world!\n");
|
||||||
21
tests/ui/type_assignments.nil
Normal file
21
tests/ui/type_assignments.nil
Normal 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;
|
||||||
|
);
|
||||||
Loading…
Add table
Add a link
Reference in a new issue