Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,243 @@
|
||||
use super::{Directory, fixture};
|
||||
use dever::compiler::{build, emit, project::Project};
|
||||
use std::path::Path;
|
||||
|
||||
fn document() -> String {
|
||||
"verify() (answer: Bool) { answer = true }
|
||||
"
|
||||
.into()
|
||||
}
|
||||
|
||||
fn write(root: &Path, name: &str, contents: impl AsRef<[u8]>) {
|
||||
let path = root.join(name);
|
||||
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||
std::fs::write(path, contents).unwrap();
|
||||
}
|
||||
|
||||
fn load_error(path: &Path, needle: &str) {
|
||||
let error = Project::load(path).err().expect("invalid source accepted");
|
||||
assert!(error.contains(needle), "{error}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entry_reachability_excludes_unused_task_code_but_keeps_checks() {
|
||||
for (sample, entry, features) in [
|
||||
("core.dever", "verify", vec![]),
|
||||
("handler.dever", "verify", vec![]),
|
||||
("task.dever", "verify", vec!["task"]),
|
||||
("task.dever", "yes", vec![]),
|
||||
] {
|
||||
let project = Project::load(&fixture().with_file_name(sample)).unwrap();
|
||||
let program = project.check().unwrap();
|
||||
let root = program
|
||||
.functions
|
||||
.iter()
|
||||
.position(|function| function.signature.source == 0 && function.signature.name == entry)
|
||||
.unwrap();
|
||||
let functions = program.reachable([root]);
|
||||
assert!(functions.contains(&root));
|
||||
assert!(functions.len() < program.functions.len());
|
||||
assert_eq!(
|
||||
build::runtime_features(&program, &functions),
|
||||
features,
|
||||
"{sample}/{entry}"
|
||||
);
|
||||
let source = emit::generate(&program, &project.sources, &functions);
|
||||
if features.is_empty() {
|
||||
assert!(
|
||||
!source.contains("dever_runtime::task::"),
|
||||
"{sample}/{entry}"
|
||||
);
|
||||
}
|
||||
for index in 0..program.functions.len() {
|
||||
if !functions.contains(&index) {
|
||||
assert!(
|
||||
!source.contains(&format!("fn function{index}(")),
|
||||
"{sample}/{entry}: {index}"
|
||||
);
|
||||
assert!(
|
||||
!source.contains(&format!("as function{index};")),
|
||||
"{sample}/{entry}: {index}"
|
||||
);
|
||||
assert!(
|
||||
!source.contains(&format!("enum Failure{index} {{")),
|
||||
"{sample}/{entry}: {index}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut project = Project::load(&fixture()).unwrap();
|
||||
let source = &project.sources[0];
|
||||
let text = format!(
|
||||
"{}\nunused() (answer: Int) {{ answer = true }}\n",
|
||||
source.text
|
||||
);
|
||||
project.sources[0] = dever::syntax::source::Source::new(source.id, source.path.clone(), text);
|
||||
assert!(project.check().unwrap_err().contains("C005"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn packages_execute_with_original_cross_file_failure_locations() {
|
||||
let directory = Directory::new();
|
||||
let path = fixture().with_file_name("package");
|
||||
let project = Project::load(&path).unwrap();
|
||||
let program = project.check().unwrap();
|
||||
assert_eq!(
|
||||
&program.packages[..3],
|
||||
&["calc.batch", "calc.value", "main"]
|
||||
);
|
||||
let second = Project::load(&path).unwrap();
|
||||
assert_eq!(
|
||||
emit::generate(
|
||||
&program,
|
||||
&project.sources,
|
||||
&(0..program.functions.len()).collect()
|
||||
),
|
||||
emit::generate(
|
||||
&second.check().unwrap(),
|
||||
&second.sources,
|
||||
&(0..program.functions.len()).collect()
|
||||
)
|
||||
);
|
||||
for (entry, expected) in [
|
||||
("verify", None),
|
||||
("calc.batch.verify", None),
|
||||
(
|
||||
"failureEntry",
|
||||
Some(("Problem.Rejected", "Problem.Rejected(\"negative\")")),
|
||||
),
|
||||
(
|
||||
"faultEntry",
|
||||
Some(("integer arithmetic fault", "value + 1")),
|
||||
),
|
||||
] {
|
||||
let binary =
|
||||
build::build(&program, &project.sources, entry, &directory.0.join(entry)).unwrap();
|
||||
let result = std::process::Command::new(binary).output().unwrap();
|
||||
if let Some((message, needle)) = expected {
|
||||
assert!(!result.status.success());
|
||||
assert!(result.stdout.is_empty());
|
||||
let stderr = String::from_utf8(result.stderr).unwrap();
|
||||
let source = project
|
||||
.sources
|
||||
.iter()
|
||||
.find(|source| source.path.ends_with("calc/value.dever"))
|
||||
.unwrap();
|
||||
let (line, column) = source.position(source.text.find(needle).unwrap());
|
||||
assert!(stderr.contains(message), "{stderr}");
|
||||
assert!(
|
||||
stderr.contains(&format!("{}:{line}:{column}:", source.path.display())),
|
||||
"{stderr}"
|
||||
);
|
||||
} else {
|
||||
assert!(
|
||||
result.status.success(),
|
||||
"{}",
|
||||
String::from_utf8_lossy(&result.stderr)
|
||||
);
|
||||
assert_eq!(result.stdout, b"true\n");
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
build::build(
|
||||
&program,
|
||||
&project.sources,
|
||||
"calc.missing",
|
||||
&directory.0.join("missing")
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
assert!(!directory.0.join("missing").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn private_records_preserve_cross_package_value_semantics() {
|
||||
let directory = Directory::new();
|
||||
let project = Project::load(&fixture().with_file_name("private")).unwrap();
|
||||
let program = project.check().unwrap();
|
||||
let binary = build::build(
|
||||
&program,
|
||||
&project.sources,
|
||||
"verify",
|
||||
&directory.0.join("private"),
|
||||
)
|
||||
.unwrap();
|
||||
let result = std::process::Command::new(binary).output().unwrap();
|
||||
assert!(
|
||||
result.status.success(),
|
||||
"{}",
|
||||
String::from_utf8_lossy(&result.stderr)
|
||||
);
|
||||
assert_eq!(result.stdout, b"true\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn package_paths_are_authoritative_and_discovery_is_stable() {
|
||||
let directory = Directory::new();
|
||||
write(&directory.0, "zeta/value.dever", document());
|
||||
write(&directory.0, "alpha.dever", document());
|
||||
write(&directory.0, "README.md", "not code");
|
||||
write(&directory.0, ".hidden/invalid.dever", "not code");
|
||||
let project = Project::load(&directory.0).unwrap();
|
||||
let program = project.check().unwrap();
|
||||
assert_eq!(&program.packages[..2], &["alpha", "zeta.value"]);
|
||||
assert_eq!(
|
||||
program.packages,
|
||||
Project::load(&directory.0)
|
||||
.unwrap()
|
||||
.check()
|
||||
.unwrap()
|
||||
.packages
|
||||
);
|
||||
write(
|
||||
&directory.0,
|
||||
"zeta/value.dever",
|
||||
document().replace("answer = true", "answer = 1"),
|
||||
);
|
||||
let error = Project::load(&directory.0).unwrap().check().unwrap_err();
|
||||
assert!(
|
||||
error.contains("zeta/value.dever:") && error.contains("C005"),
|
||||
"{error}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn package_loader_rejects_collisions_names_and_invalid_sources() {
|
||||
for (name, needle) in [
|
||||
("Wrong.dever", "lower_snake_case"),
|
||||
("user..bad.dever", "lower_snake_case"),
|
||||
("dever/custom.dever", "reserved standard"),
|
||||
("shop/account/app.dever", "application role"),
|
||||
] {
|
||||
let directory = Directory::new();
|
||||
write(&directory.0, name, document());
|
||||
load_error(&directory.0, needle);
|
||||
}
|
||||
let directory = Directory::new();
|
||||
load_error(&directory.0, "no Dever sources");
|
||||
write(&directory.0, "sample.dever", document());
|
||||
write(&directory.0, "sample.dever.md", "verify() () {}");
|
||||
load_error(&directory.0, "use .dever source");
|
||||
let directory = Directory::new();
|
||||
write(&directory.0, "bad.dever", [0xff]);
|
||||
load_error(&directory.0, "bad.dever");
|
||||
let directory = Directory::new();
|
||||
write(&directory.0, "module/shop/account/app.dever", document());
|
||||
let error = Project::load(&directory.0).unwrap().check().unwrap_err();
|
||||
assert!(
|
||||
error.contains("application has no supported API/CMD entry"),
|
||||
"{error}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn package_loader_does_not_follow_source_links() {
|
||||
let directory = Directory::new();
|
||||
write(&directory.0, "sources/value.dever", document());
|
||||
let source = directory.0.join("sources");
|
||||
std::os::unix::fs::symlink(source.join("value.dever"), source.join("alias.dever")).unwrap();
|
||||
load_error(&source, "source links");
|
||||
load_error(&source.join("alias.dever"), "source links");
|
||||
std::os::unix::fs::symlink(&source, directory.0.join("linked")).unwrap();
|
||||
load_error(&directory.0.join("linked/value.dever"), "source links");
|
||||
}
|
||||
Reference in new issue
Block a user