feat: release Dever 0.1.4 with enforceable project guidance

This commit is contained in:
Antigravity committed 2026-10-09 05:33:57 +00:00
1 parent 201bdee372
commit 89629c2197
86 files changed
+2798 -338

No files matched your search

+4
View File
@@ -86,6 +86,10 @@ path = "tests/markdown_source.rs"
name = "cms_acceptance"
path = "tests/cms_acceptance.rs"
[[test]]
name = "skill_examples"
path = "tests/skill_examples.rs"
[[test]]
name = "native_cache"
path = "tests/native_cache.rs"
@@ -2,6 +2,16 @@ mod support;
use support::{checked, sources};
fn application(files: &[(&str, &str)]) -> dever_core::hir::Program {
let root = support::temp::TemporaryDirectory::new();
for (relative, source) in files {
let path = root.path().join("module").join(relative);
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(path, source).unwrap();
}
checked(&dever_core::source::SourceMap::load(&root.path().join("module")).unwrap())
}
fn warnings(source: &str, code: &str) -> Vec<dever_core::diagnostic::Diagnostic> {
let sources = sources(source);
let user_source = sources.files()[0].id();
@@ -99,3 +109,122 @@ fn existing_unused_write_warning_is_not_duplicated_as_dead_computation() {
.any(|warning| warning.code == "W006")
);
}
#[test]
fn application_rejects_reachable_private_copies_with_renamed_inputs() {
let program = application(&[
("sample/count/domain.dever", "first(value: Int) (answer: Int) pure { answer = value + 1 }\nsecond(renamed: Int) (answer: Int) pure { answer = renamed + 1 }"),
("sample/count/app.dever", "total(value: Int) (answer: Int) { first = domain.first(value)\nsecond = domain.second(value)\nanswer = first + second }"),
("sample/count/api.dever", "cmd total = app.total"),
]);
let warnings: Vec<_> = program.warnings().iter().filter(|item| item.code == "W003").collect();
let errors = program.application_errors().unwrap();
assert_eq!(errors.len(), 1, "{errors:?}");
assert_eq!(errors[0].code, "C016");
assert!(errors[0].message.contains("same implementation and contract"));
assert!(errors[0].message.contains("domain.first"));
assert!(errors[0].message.contains("domain.second"));
assert_eq!(errors[0].related.len(), 1);
assert!(warnings.iter().any(|warning| warning.primary == errors[0].primary));
assert_ne!(errors[0].primary, errors[0].related[0].span);
}
#[test]
fn application_rejects_repeated_pure_calls_using_the_warning_proof() {
let program = application(&[
("sample/count/domain.dever", "calculate(value: Int) (answer: Int) pure { answer = value + 1 }"),
("sample/count/app.dever", "total(value: Int) (answer: Int) { first = domain.calculate(value)\nsecond = domain.calculate(value)\nanswer = first + second }"),
("sample/count/api.dever", "cmd total = app.total"),
]);
let errors = program.application_errors().unwrap();
assert_eq!(errors.len(), 1, "{errors:?}");
assert_eq!(errors[0].code, "C016");
assert!(errors[0].message.contains("earlier result is still available"));
assert_eq!(errors[0].related.len(), 1);
assert!(program.warnings().iter().any(|warning| {
warning.code == "W004" && warning.primary == errors[0].primary
}));
}
#[test]
fn application_does_not_reject_changed_inputs_results_or_effectful_calls() {
for (app, domain, api) in [
(
"total(value: Int) (answer: Int) { first = domain.calculate(value)\nvalue = value + 1\nsecond = domain.calculate(value)\nanswer = first + second }",
"calculate(value: Int) (answer: Int) pure { answer = value + 1 }",
"cmd total = app.total",
),
(
"total(value: Int) (answer: Int) { first = domain.calculate(value)\nfirst = 0\nsecond = domain.calculate(value)\nanswer = first + second }",
"calculate(value: Int) (answer: Int) pure { answer = value + 1 }",
"cmd total = app.total",
),
(
"type Counter { value: Int }\ntotal(counter: Counter) (answer: Int) { first = domain.calculate(counter.value)\ncounter.value = counter.value + 1\nsecond = domain.calculate(counter.value)\nanswer = first + second }",
"calculate(value: Int) (answer: Int) pure { answer = value + 1 }",
"cmd total = app.total",
),
(
"noisy(value: Int) (answer: Int) { dever.io.println(\"called\")\nanswer = value }\ntotal(value: Int) (answer: Int) { first = noisy(value)\nsecond = noisy(value)\nanswer = first + second }",
"",
"cmd total = app.total",
),
] {
let program = application(&[
("sample/count/app.dever", app),
("sample/count/domain.dever", domain),
("sample/count/api.dever", api),
]);
let errors = program.application_errors().unwrap();
assert!(errors.is_empty(), "{app}: {errors:?}");
}
}
#[test]
fn application_preserves_different_contracts_and_owners() {
for domain in [
"first(value: Int) (answer: Int) pure { answer = value }\nsecond(value: Int) (different: Int) pure { different = value }",
"first(value: Int) (answer: Int) pure recover(\"first policy\") { answer = value }\nsecond(value: Int) (answer: Int) pure recover(\"second policy\") { answer = value }",
"first() (answer: Int >= 1) pure { answer = 1 }\nsecond() (answer: Int >= 0) pure { answer = 1 }",
] {
let inputs = if domain.contains("value:") { "1" } else { "" };
let app = format!("total() (answer: Int) {{ first = domain.first({inputs})\nsecond = domain.second({inputs})\nanswer = first + second }}");
let program = application(&[
("sample/count/domain.dever", domain),
("sample/count/app.dever", &app),
("sample/count/api.dever", "cmd total = app.total"),
]);
assert!(program.application_errors().unwrap().is_empty(), "{domain}");
}
let program = application(&[
("first/count/domain.dever", "calculate(value: Int) (answer: Int) pure { answer = value + 1 }"),
("first/count/app.dever", "total(value: Int) (answer: Int) { answer = domain.calculate(value) }"),
("first/count/api.dever", "cmd total = app.total"),
("second/count/domain.dever", "calculate(value: Int) (answer: Int) pure { answer = value + 1 }"),
("second/count/app.dever", "total(value: Int) (answer: Int) { answer = domain.calculate(value) }"),
("second/count/api.dever", "cmd total = app.total"),
]);
assert!(program.application_errors().unwrap().is_empty());
}
#[test]
fn application_keeps_effectful_private_copies_and_adapter_redundancy_as_warnings() {
let program = application(&[
("sample/count/domain.dever", "emit(value: Int) (answer: Int) { dever.io.println(\"called\")\nanswer = value }\nfirst(value: Int) (answer: Int) { answer = emit(value) + 1 }\nsecond(renamed: Int) (answer: Int) { answer = emit(renamed) + 1 }"),
("sample/count/app.dever", "total(value: Int) (answer: Int) { first = domain.first(value)\nsecond = domain.second(value)\nanswer = first + second }"),
("sample/count/api.dever", "cmd total = app.total"),
]);
assert!(program.warnings().iter().any(|warning| warning.code == "W003"));
assert!(program.application_errors().unwrap().is_empty());
let program = application(&[
("sample/count/port.dever", "read(value: Int) (answer: Int) fails dever.time.SleepResult"),
("sample/count/adapter.dever", "first(value: Int) (answer: Int) { answer = value + 1 }\nsecond(value: Int) (answer: Int) { answer = value + 1 }\nport.read(value: Int) (answer: Int) { one = first(value)\ntwo = second(value)\nthree = first(value)\nanswer = one + two + three }"),
("sample/count/app.dever", "read(value: Int) (answer: Int) { answer = port.read(value) }"),
("sample/count/api.dever", "cmd read = app.read"),
]);
for code in ["W003", "W004"] {
assert!(program.warnings().iter().any(|warning| warning.code == code));
}
assert!(program.application_errors().unwrap().is_empty());
}
+132
View File
@@ -0,0 +1,132 @@
use std::fs;
use std::path::{Path, PathBuf};
use dever_core::{hir::Program, source::SourceMap};
#[path = "support/temp.rs"]
mod temp;
fn examples() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("../../skills/assets/examples")
}
fn checked(root: &Path) -> Program {
let sources = SourceMap::load_project(&root.join("module"), &root.join("test")).unwrap();
let settings = dever_runtime::config::Settings::load_project(root).unwrap();
let program = dever_core::check_with_settings(&sources, &settings).unwrap_or_else(|errors| {
panic!(
"{}: {}",
root.display(),
errors
.iter()
.map(|error| error.render(&sources))
.collect::<String>()
)
});
let errors = program.application_errors().unwrap();
assert!(
errors.is_empty(),
"{}",
errors
.iter()
.map(|error| error.render(&sources))
.collect::<String>()
);
program.validate_database_settings(&settings).unwrap();
for source in sources.files() {
assert_eq!(
dever_core::format::format(source).unwrap(),
source.text(),
"{}",
source.path().display()
);
}
program
}
fn copy_tree(source: &Path, target: &Path) {
fs::create_dir(target).unwrap();
for entry in fs::read_dir(source).unwrap() {
let entry = entry.unwrap();
let destination = target.join(entry.file_name());
if entry.file_type().unwrap().is_dir() {
copy_tree(&entry.path(), &destination);
} else {
fs::copy(entry.path(), destination).unwrap();
}
}
}
#[test]
fn relocated_examples_check_production_roots_settings_and_case_local_effects() {
let temporary = temp::TemporaryDirectory::new();
let projects = [
("http-rest", 1, false),
("jobs", 1, true),
("external-lib", 2, false),
("collections", 2, false),
("command", 1, false),
];
assert_eq!(fs::read_dir(examples()).unwrap().count(), projects.len());
for (name, test_count, uses_database) in projects {
let root = temporary.path().join(name);
copy_tree(&examples().join(name), &root);
let program = checked(&root);
assert_eq!(program.tests().len(), test_count);
assert!(
program
.tests()
.iter()
.all(|case| case.uses_database() == uses_database)
);
}
let template = temporary.path().join("template");
copy_tree(&examples().parent().unwrap().join("template"), &template);
assert_eq!(checked(&template).tests().len(), 1);
}
#[test]
fn three_ecosystems_share_one_checked_port_contract_and_fake_suite() {
let temporary = temp::TemporaryDirectory::new();
let root = temporary.path().join("external-lib");
copy_tree(&examples().join("external-lib"), &root);
let original = checked(&root).external_worker_contracts().remove(0);
let mut schemas = std::collections::BTreeSet::new();
for (ecosystem, entry, dependency) in [
("pip", "python/render.py", "markdown@3.8.2"),
("npm", "javascript/render.mjs", "marked@15.0.12"),
("go", "go/render.go", "github.com/yuin/goldmark@v1.7.8"),
] {
fs::write(
root.join("module/document/markup/adapter.dever"),
format!("external {ecosystem} \"{entry}\" {{\n lib \"{dependency}\"\n}}\n"),
)
.unwrap();
fs::write(
root.join("config/setting.json"),
serde_json::to_vec(&serde_json::json!({
"lib": [format!("{ecosystem}:{dependency}")]
}))
.unwrap(),
)
.unwrap();
let program = checked(&root);
let contracts = program.external_worker_contracts();
let [contract] = contracts.as_slice() else {
panic!("one external Adapter must own the shared Port")
};
// 协议身份绑定生态和 Lib 声明;跨生态复用的是 Port 的类型合同。
assert!(schemas.insert(contract.schema.clone()));
assert_eq!(contract.port, original.port);
assert_eq!(contract.setting, original.setting);
assert_eq!(contract.inputs, original.inputs);
assert_eq!(contract.outputs, original.outputs);
assert_eq!(contract.errors, original.errors);
assert_eq!(contract.operations, ["render"]);
assert_eq!(contract.ecosystem, ecosystem);
assert_eq!(contract.libs, [format!("{ecosystem}:{dependency}")]);
assert!(root.join(&contract.entry).is_file());
assert_eq!(program.tests().len(), 2);
assert!(!program.tests().iter().any(|case| case.uses_database()));
}
}