Initial Dever 0.0.1

This commit is contained in:
Codex committed 2026-11-03 03:32:07 +00:00
commit 81233d098c
1479 files changed
+163782

No files matched your search

+204
View File
@@ -0,0 +1,204 @@
use super::{Command, Directory, Project};
use std::fs;
use std::path::Path;
fn inspect(root: &Path, check: bool) -> std::process::Output {
let mut command = Command::new(env!("CARGO_BIN_EXE_dever"));
command.arg("inspect").arg(root).arg("--links");
if check {
command.arg("--check");
}
command.output().unwrap()
}
fn success(output: std::process::Output) -> String {
assert!(
output.status.success(),
"{}",
String::from_utf8_lossy(&output.stderr)
);
String::from_utf8(output.stdout).unwrap()
}
#[test]
fn domain_links_check_is_read_only_and_generation_is_stable() {
{
let directory = Directory::new();
let root = directory.0.join("project with spaces");
dever::compiler::project::create(&root).unwrap();
let link = root.join("module/hello/greeting/link.md");
fs::remove_file(&link).unwrap();
let missing = inspect(&root, true);
assert_eq!(missing.status.code(), Some(1));
assert!(!link.exists());
assert!(success(inspect(&root, false)).contains("1 changed"));
let first = fs::read_to_string(&link).unwrap();
assert!(first.contains("CMD [hello.greeting.api.greet]"));
assert!(!first.contains(root.to_str().unwrap()));
assert!(first.contains("app.dever#L"));
let modified = fs::metadata(&link).unwrap().modified().unwrap();
assert!(success(inspect(&root, false)).contains("0 changed"));
assert_eq!(fs::metadata(&link).unwrap().modified().unwrap(), modified);
success(inspect(&root, true));
let source = root.join("module/hello/greeting/app.dever");
let text = fs::read_to_string(&source).unwrap();
fs::write(&source, text.replace("Hello, ", "Welcome, ")).unwrap();
assert_eq!(inspect(&root, true).status.code(), Some(1));
assert_eq!(fs::read_to_string(&link).unwrap(), first);
success(inspect(&root, false));
assert_ne!(fs::read_to_string(&link).unwrap(), first);
success(inspect(&root, true));
}
}
#[test]
fn domain_links_keep_every_cross_domain_call_site_and_refresh_both_ends() {
let directory = Directory::new();
let root = directory.0.join("project");
dever::compiler::project::create(&root).unwrap();
for domain in ["checkout", "account"] {
let owner = root.join("module/shop").join(domain);
fs::create_dir_all(&owner).unwrap();
fs::write(owner.join("app.dever"), "welcome(name: Text) (message: Text) {\n message = hello.greeting.greet(name) + hello.greeting.greet(\"other\")\n}\n").unwrap();
fs::write(owner.join("api.dever"), "cmd welcome = app.welcome\n").unwrap();
}
let unrelated = root.join("module/empty/types");
fs::create_dir_all(&unrelated).unwrap();
fs::write(
unrelated.join("domain.dever"),
"type Label { value: Text }\n",
)
.unwrap();
success(inspect(&root, false));
let incoming = root.join("module/hello/greeting/link.md");
let first = fs::read_to_string(&incoming).unwrap();
let incoming_calls = |text: &str| {
text.split("## 外部调用方\n")
.nth(1)
.unwrap()
.split("## 能力与内部调用\n")
.next()
.unwrap()
.matches("| [hello.greeting.greet]")
.count()
};
assert_eq!(incoming_calls(&first), 4);
assert!(first.contains("shop.checkout.welcome"));
assert!(first.contains("shop.account.welcome"));
assert!(first.contains("../../shop/checkout/app.dever#L2"));
let outgoing = fs::read_to_string(root.join("module/shop/checkout/link.md")).unwrap();
assert!(outgoing.contains("跨领域 [hello.greeting](../../hello/greeting/link.md)"));
let untouched = unrelated.join("link.md");
let modified = fs::metadata(&untouched).unwrap().modified().unwrap();
fs::write(
root.join("module/shop/checkout/app.dever"),
"welcome(name: Text) (message: Text) {\n message = hello.greeting.greet(name) + \"!\"\n}\n",
)
.unwrap();
let stale = inspect(&root, true);
assert_eq!(stale.status.code(), Some(1));
let report = String::from_utf8(stale.stdout).unwrap();
assert!(report.contains("stale module/hello/greeting/link.md"));
assert!(report.contains("stale module/shop/checkout/link.md"));
assert!(!report.contains("stale module/empty/types/link.md"));
success(inspect(&root, false));
assert_eq!(incoming_calls(&fs::read_to_string(&incoming).unwrap()), 3);
assert_eq!(
fs::metadata(&untouched).unwrap().modified().unwrap(),
modified
);
fs::remove_file(root.join("module/shop/account/app.dever")).unwrap();
fs::remove_file(root.join("module/shop/account/api.dever")).unwrap();
let removed = root.join("module/shop/account/link.md");
assert_eq!(inspect(&root, true).status.code(), Some(1));
assert!(removed.exists());
assert!(success(inspect(&root, false)).contains("removed module/shop/account/link.md"));
assert!(!removed.exists());
assert!(
!fs::read_to_string(&incoming)
.unwrap()
.contains("shop.account.welcome")
);
}
#[test]
fn domain_links_preserve_handwritten_files_and_reject_invalid_source_before_writing() {
let directory = Directory::new();
let root = directory.0.join("project");
dever::compiler::project::create(&root).unwrap();
let link = root.join("module/hello/greeting/link.md");
fs::write(&link, "# 手工说明\n").unwrap();
let refused = inspect(&root, false);
assert!(!refused.status.success());
assert!(String::from_utf8_lossy(&refused.stderr).contains("handwritten"));
assert_eq!(fs::read_to_string(&link).unwrap(), "# 手工说明\n");
fs::remove_file(&link).unwrap();
success(inspect(&root, false));
let first = fs::read_to_string(&link).unwrap();
let source = root.join("module/hello/greeting/app.dever");
let text = fs::read_to_string(&source).unwrap();
fs::write(&source, text.replace("\"Hello, \" + name", "unknown(name)")).unwrap();
assert!(!inspect(&root, false).status.success());
assert_eq!(fs::read_to_string(&link).unwrap(), first);
}
#[test]
fn domain_links_mark_api_job_and_dispatch_without_inventing_execution_order() {
let fixtures = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../test/rust/fixtures")
.canonicalize()
.unwrap();
let job = fixtures.join("job");
let pages = Project::load(&job)
.unwrap()
.inspect()
.unwrap()
.links(&job)
.unwrap();
let text = &pages[&job.join("module/demo/queue/link.md")];
assert!(text.contains("Job [demo.queue.job.publish]"));
assert!(text.contains("| 入队 |"));
assert!(text.contains("按求值顺序"));
assert!(text.contains("不是某次运行的日志"));
assert!(text.contains("flowchart TD"));
assert!(text.contains("由 Worker 后续调度,不等待执行"));
let http = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../skills/assets/examples/http-rest")
.canonicalize()
.unwrap();
let pages = Project::load(&http)
.unwrap()
.inspect()
.unwrap()
.links(&http)
.unwrap();
assert!(pages.values().any(|text| text.contains("- API [")));
assert!(pages.values().all(|text| text.contains("自动 REST")));
}
#[test]
fn domain_links_reject_conflicting_options_and_non_project_sources() {
let directory = Directory::new();
let source = directory.0.join("main.dever");
fs::write(&source, "main() (value: Int) { value = 1 }\n").unwrap();
let invalid = inspect(&source, false);
assert!(!invalid.status.success());
assert!(String::from_utf8_lossy(&invalid.stderr).contains("project root"));
for flags in [
vec!["--check"],
vec!["--links", "--json"],
vec!["--links", "--symbol", "main.main"],
vec!["--links", "--links"],
] {
let output = Command::new(env!("CARGO_BIN_EXE_dever"))
.arg("inspect")
.arg(&source)
.args(flags)
.output()
.unwrap();
assert!(!output.status.success());
assert!(output.stdout.is_empty());
}
}