Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user