Files
sausage/host/cred-script/src/tests.rs
T

197 lines
5.3 KiB
Rust

use super::*;
fn run(s: &str) -> Result<String, Error> {
evaluate(s, "null", None, Limits::default())
}
#[test]
fn module_default_and_named() {
assert_eq!(
evaluate(
"export default x => ({value:x}); export const other=x=>x+1",
"42",
None,
Limits::default()
)
.unwrap(),
"{\"value\":42}"
);
assert_eq!(
evaluate(
"export const other=x=>x+1",
"42",
Some("other"),
Limits::default()
)
.unwrap(),
"43"
);
assert_eq!(run("export default x=>null").unwrap(), "null");
}
#[test]
fn promise_settlement() {
assert_eq!(
run("export default async x => { await Promise.resolve(); return 42; }").unwrap(),
"42"
);
assert_eq!(
run("await Promise.resolve(); export default x=>true").unwrap(),
"true"
);
for s in [
"export default x=>Promise.reject(new Error('rejected'))",
"export default async x=>{ throw 'rejected'; }",
"await Promise.reject('rejected'); export default x=>true",
] {
let e = run(s).unwrap_err();
assert_eq!(e.kind, ErrorKind::Throw);
assert_eq!(e.message, "rejected");
}
assert_eq!(
run("export default x=>new Promise(()=>{})")
.unwrap_err()
.kind,
ErrorKind::Engine
);
}
#[test]
fn typed_errors() {
assert_eq!(run("export default (").unwrap_err().kind, ErrorKind::Syntax);
assert_eq!(
run("export default x=>{throw new Error('oops')}")
.unwrap_err()
.kind,
ErrorKind::Throw
);
for s in [
"export default x=>undefined",
"export default x=>(()=>1)",
"export default x=>{let a={};a.a=a;return a}",
"export default x=>Symbol()",
] {
assert_eq!(run(s).unwrap_err().kind, ErrorKind::NonJson);
}
assert_eq!(
evaluate("export default x=>x", "not json", None, Limits::default())
.unwrap_err()
.kind,
ErrorKind::Syntax
);
assert_eq!(run("export const x=1").unwrap_err().kind, ErrorKind::Engine);
assert_eq!(run("export default 1").unwrap_err().kind, ErrorKind::Engine);
}
#[test]
fn recursion_is_bounded() {
for stack in [4096, 16384, usize::MAX] {
let e = evaluate(
"export default x=>{function f(){return f()+1}return f()}",
"null",
None,
Limits {
max_stack_bytes: Some(stack),
..Limits::default()
},
)
.unwrap_err();
eprintln!("STACK {stack}: {e:?}");
assert_eq!(e.kind, ErrorKind::Limit);
assert!(e.message.contains("stack"));
}
}
#[test]
fn runaway_loop_and_jobs() {
for s in [
"export default x=>{while(true){}}",
"while(true){};export default x=>x",
"export default x=>({toJSON(){while(true){}}})",
"export default x=>{throw {get message(){while(true){}}}}",
"export default x=>new Promise(()=>{function f(){Promise.resolve().then(f)};f()})",
] {
let start = Instant::now();
assert_eq!(run(s).unwrap_err().kind, ErrorKind::Limit);
assert!(start.elapsed() < Duration::from_secs(2));
}
}
#[test]
fn runaway_allocation() {
let e = evaluate(
"export default x=>{let a=[];while(1)a.push('abcdefghijklmnopqrstuvwxyz'+a.length)}",
"null",
None,
Limits {
max_time_ms: Some(1000),
max_memory_bytes: Some(1024 * 1024),
..Limits::default()
},
)
.unwrap_err();
eprintln!("HEAP {e:?}");
assert_eq!(e.kind, ErrorKind::Limit);
assert!(e.message.contains("memory"));
}
#[test]
fn forbidden_globals_and_imports() {
for name in [
"Date",
"performance",
"Math.random",
"fetch",
"XMLHttpRequest",
"require",
"process",
"std",
"os",
"fs",
"crypto",
"console",
"setTimeout",
] {
assert_eq!(
run(&format!("export default x=>typeof {name}")).unwrap(),
"\"undefined\"",
"{name}"
);
}
assert!(run("import x from 'fs';export default x=>x").is_err());
assert_eq!(
run("export default x=>import('fs')").unwrap_err().kind,
ErrorKind::Throw
);
}
#[test]
fn isolation_and_limits() {
run("export default x=>{globalThis.leak=42;return true}").unwrap();
assert_eq!(
run("export default x=>typeof leak").unwrap(),
"\"undefined\""
);
assert_eq!(
Limits {
max_time_ms: Some(u64::MAX),
max_memory_bytes: Some(usize::MAX),
max_stack_bytes: Some(usize::MAX)
}
.clamped()
.max_time_ms,
Some(1000)
);
assert_eq!(
Limits {
max_time_ms: Some(0),
..Limits::default()
}
.clamped()
.max_time_ms,
Some(1)
);
}
#[test]
fn timings() {
let start = Instant::now();
assert_eq!(run("export default x=>true").unwrap(), "true");
eprintln!("TIMING first eval {}us", start.elapsed().as_micros());
let start = Instant::now();
for _ in 0..100 {
assert_eq!(run("export default x=>true").unwrap(), "true");
}
eprintln!("TIMING 100 evals {}us", start.elapsed().as_micros());
}