195 lines
7.0 KiB
Rust
195 lines
7.0 KiB
Rust
use dever::compiler::check;
|
|
use dever::syntax::source::Source;
|
|
|
|
#[path = "compiler/application/mod.rs"]
|
|
mod application;
|
|
#[path = "compiler/auth/mod.rs"]
|
|
mod auth;
|
|
#[path = "compiler/database/mod.rs"]
|
|
mod database;
|
|
#[path = "compiler/job/mod.rs"]
|
|
mod job;
|
|
|
|
#[path = "compiler/api.rs"]
|
|
mod api;
|
|
|
|
#[path = "compiler/bytes.rs"]
|
|
mod bytes;
|
|
#[path = "compiler/calendar.rs"]
|
|
mod calendar;
|
|
#[path = "compiler/choice.rs"]
|
|
mod choice;
|
|
#[path = "compiler/collection.rs"]
|
|
mod collection;
|
|
#[path = "compiler/crypto.rs"]
|
|
mod crypto;
|
|
#[path = "compiler/failure.rs"]
|
|
mod failure;
|
|
#[path = "compiler/format.rs"]
|
|
mod format;
|
|
#[path = "compiler/handler.rs"]
|
|
mod handler;
|
|
#[path = "compiler/http.rs"]
|
|
mod http;
|
|
#[path = "compiler/id.rs"]
|
|
mod id;
|
|
#[path = "compiler/io/mod.rs"]
|
|
mod io;
|
|
#[path = "compiler/json.rs"]
|
|
mod json;
|
|
#[path = "compiler/net.rs"]
|
|
mod net;
|
|
#[path = "compiler/numeric/mod.rs"]
|
|
mod numeric;
|
|
#[path = "compiler/package.rs"]
|
|
mod package;
|
|
#[path = "compiler/port/mod.rs"]
|
|
mod port;
|
|
#[path = "compiler/standard.rs"]
|
|
mod standard;
|
|
#[path = "compiler/storage.rs"]
|
|
mod storage;
|
|
#[path = "compiler/task/mod.rs"]
|
|
mod task;
|
|
#[path = "compiler/tenant.rs"]
|
|
mod tenant;
|
|
#[path = "compiler/upload.rs"]
|
|
mod upload;
|
|
#[path = "compiler/usage.rs"]
|
|
mod usage;
|
|
#[path = "compiler/uuid.rs"]
|
|
mod uuid;
|
|
|
|
fn checked(
|
|
text: &str,
|
|
) -> Result<dever::compiler::model::Program, dever::syntax::diagnostic::Diagnostic> {
|
|
let source = Source::new(0, "case.dever", text.into());
|
|
check::check(vec![dever::syntax::parse(&source)?], &["main".into()])
|
|
}
|
|
|
|
#[test]
|
|
fn rejects_overlap_gaps_and_recursion() {
|
|
assert_eq!(
|
|
checked("pick(value: Int < 0) (answer: Int) { answer = 1 }")
|
|
.unwrap_err()
|
|
.code,
|
|
"C009"
|
|
);
|
|
assert_eq!(checked("pick(value: Int <= 0) (answer: Int) { answer = 1 }\npick(value: Int >= 0) (answer: Int) { answer = 2 }").unwrap_err().code, "C009");
|
|
assert_eq!(
|
|
checked("cycle() (answer: Int) { answer = cycle() }")
|
|
.unwrap_err()
|
|
.code,
|
|
"C007"
|
|
);
|
|
assert!(checked("pick(value: Int < 0) (answer: Int) { answer = 1 }\npick(value: other) (answer: Int) { answer = 2 }").is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn rejects_uninitialized_outputs_and_overflow() {
|
|
assert!(checked("main() (answer: Int) { value = answer }").is_err());
|
|
assert!(checked("main() (answer: Int) {}").is_err());
|
|
assert!(checked("main() (answer: Int) { answer = 9223372036854775807 + 1 }").is_err());
|
|
assert!(checked("main() (answer: Int) { answer = -9223372036854775808 }").is_ok());
|
|
assert!(checked("main() (answer: Int) { answer = 9223372036854775806 + (1 + 1) }").is_err());
|
|
assert!(checked("main() (answer: Decimal) { answer = 1.0 / (2.0 - 2.0) }").is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn preserves_record_and_list_types() {
|
|
assert!(checked("type Row { name: Text\n count: Int }\nmain() (answer: Row) { answer = Row { name = \"雪\"\n count = sum([1, 2, 3]) } }").is_ok());
|
|
assert!(
|
|
checked("type Row { count: Int }\nmain() (answer: Row) { answer = Row { wrong = 1 } }")
|
|
.is_err()
|
|
);
|
|
assert!(checked("main() (answer: List<Int>) { answer = [1, true] }").is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn retains_original_naming_constraints() {
|
|
assert_eq!(checked("read_value() () {}").unwrap_err().code, "C010");
|
|
assert_eq!(checked("Wrong() () {}").unwrap_err().code, "C010");
|
|
assert_eq!(checked("type bad { count: Int }").unwrap_err().code, "C010");
|
|
assert_eq!(checked("main() () { Bad = 1 }").unwrap_err().code, "C010");
|
|
assert!(checked("type HttpClient { user_id: Int }\nreadValue(input_value: Int) (output_value: Int) { output_value = input_value }").is_ok());
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "explicit compiler cost probe: fixed 15 rounds, run without concurrent CPU work"]
|
|
fn compiler_reduction_costs() {
|
|
use dever::compiler::project::Project;
|
|
use std::{hint::black_box, path::Path, time::Instant};
|
|
|
|
let fixtures = Path::new(env!("CARGO_MANIFEST_DIR")).join("../test/rust/fixtures");
|
|
for (label, count) in [("medium", 32), ("large", 512)] {
|
|
let mut text = "copyText(value: Text) (answer: Text) { answer = value }\n".to_owned();
|
|
for index in 0..count {
|
|
let mut body =
|
|
format!("case{index}(value: Text) (answer: Text) {{\nanswer = copyText(value)\n");
|
|
for _ in 0..15 {
|
|
body.push_str("answer = copyText(answer)\n");
|
|
}
|
|
body.push_str("}\n");
|
|
text.push_str(&body);
|
|
}
|
|
let source = Source::new(0, "cost.dever", text);
|
|
let mut project = Project::load(&fixtures.join("core.dever")).unwrap();
|
|
project.sources[0] = Source::new(0, project.sources[0].path.clone(), source.text.clone());
|
|
let packages = vec!["main".to_owned()];
|
|
let expected_project_functions = project.check().unwrap().functions.len();
|
|
assert!(expected_project_functions > count);
|
|
println!(
|
|
"audit_compiler input={label} functions={} calls={} bytes={} project_bytes={} rounds=15 profile=test",
|
|
count + 1,
|
|
count * 16,
|
|
source.text.len(),
|
|
project.sources[0].text.len()
|
|
);
|
|
for round in 1..=15 {
|
|
let started = Instant::now();
|
|
let module = dever::syntax::parse(black_box(&source)).unwrap();
|
|
let elapsed = started.elapsed().as_nanos();
|
|
assert_eq!(module.declarations.len(), count + 1);
|
|
println!("audit_compiler case={label} phase=parse round={round} ns={elapsed}");
|
|
|
|
let started = Instant::now();
|
|
let program = check::check(vec![module], black_box(&packages)).unwrap();
|
|
let elapsed = started.elapsed().as_nanos();
|
|
assert_eq!(program.functions.len(), count + 1);
|
|
black_box(&program);
|
|
println!("audit_compiler case={label} phase=check round={round} ns={elapsed}");
|
|
drop(program);
|
|
|
|
let started = Instant::now();
|
|
let program = black_box(&project).check().unwrap();
|
|
let elapsed = started.elapsed().as_nanos();
|
|
assert_eq!(program.functions.len(), expected_project_functions);
|
|
black_box(&program);
|
|
println!("audit_compiler case={label} phase=project round={round} ns={elapsed}");
|
|
}
|
|
}
|
|
let project = Project::load(&fixtures.join("tenant")).unwrap();
|
|
let program = project.check().unwrap();
|
|
let roots = program.entries().collect::<Vec<_>>();
|
|
assert!(!roots.is_empty());
|
|
println!(
|
|
"audit_compiler input=tenant roots={} repeats=64 rounds=15",
|
|
roots.len()
|
|
);
|
|
for round in 1..=15 {
|
|
let started = Instant::now();
|
|
let mut required = 0usize;
|
|
for _ in 0..64 {
|
|
for root in &roots {
|
|
let access = black_box(&program).tenant_access(black_box(*root));
|
|
required += usize::from(access.required);
|
|
black_box(access);
|
|
}
|
|
}
|
|
let elapsed = started.elapsed().as_nanos();
|
|
println!(
|
|
"audit_compiler case=tenant phase=access round={round} ns={elapsed} required={required}"
|
|
);
|
|
}
|
|
}
|