feat: publish Dever core with project scaffolding and versioned AI skills
This commit is contained in:
1 parent
70fa73cdc2
commit
ac458ffa7f
960 files changed
+221319
-1163
No files matched your search
@@ -0,0 +1,226 @@
|
||||
mod support;
|
||||
|
||||
use dever_core::source::SourceMap;
|
||||
use support::{checked, rejected, sources};
|
||||
|
||||
#[path = "../../../crates/dever-cli/src/api.rs"]
|
||||
mod cli_api;
|
||||
|
||||
#[test]
|
||||
fn application_exports_are_checked_only_with_a_known_entry() {
|
||||
let program = checked(&sources(
|
||||
"public main() (answer: Int) { answer = helper() }\npublic helper() (answer: Int) { answer = 1 }\npublic unused() () {}",
|
||||
));
|
||||
let warnings = program.application_warnings("main.main").unwrap();
|
||||
assert_eq!(warnings.len(), 2);
|
||||
assert!(warnings.iter().all(|warning| warning.code == "W007"));
|
||||
assert!(program.application_warnings("app.missing").is_err());
|
||||
let mut map = SourceMap::default();
|
||||
map.add(
|
||||
"main.dever",
|
||||
"public main() (answer: Int) { answer = utility.read() }",
|
||||
);
|
||||
map.add(
|
||||
"utility.dever",
|
||||
"public read() (answer: Int) { answer = 1 }",
|
||||
);
|
||||
let cross_package_program = checked(&map);
|
||||
assert!(
|
||||
cross_package_program
|
||||
.application_warnings("main.main")
|
||||
.unwrap()
|
||||
.is_empty()
|
||||
);
|
||||
assert!(
|
||||
!cross_package_program
|
||||
.api_snapshot()
|
||||
.contains("package app depends")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_baseline_is_explicit_never_overwrites_and_blocks_drift() {
|
||||
let directory = support::temp::TemporaryDirectory::new();
|
||||
let path = directory.path().join("dever.api");
|
||||
let original = checked(&sources("public main() (answer: Int) { answer = 1 }"));
|
||||
assert!(
|
||||
original
|
||||
.check_api(
|
||||
&original
|
||||
.api_snapshot()
|
||||
.replace("dever-api 5", "dever-api 4")
|
||||
)
|
||||
.unwrap_err()
|
||||
.contains("expected dever-api 5")
|
||||
);
|
||||
cli_api::check_baseline(&original, directory.path()).unwrap();
|
||||
assert!(!path.exists());
|
||||
cli_api::write(&original, Some(&path)).unwrap();
|
||||
cli_api::check_baseline(&original, directory.path()).unwrap();
|
||||
assert!(cli_api::write(&original, Some(&path)).is_err());
|
||||
let expanded = checked(&sources(
|
||||
"public main() (answer: Int) { answer = 1 }\npublic extra() () {}",
|
||||
));
|
||||
assert!(
|
||||
cli_api::check_baseline(&expanded, directory.path())
|
||||
.unwrap_err()
|
||||
.contains("+ function main.extra")
|
||||
);
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(path).unwrap(),
|
||||
original.api_snapshot()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn effects_follow_named_and_forwarded_handlers_without_tainting_pure_calls() {
|
||||
let shared = r#"
|
||||
apply(route: handler(value: Int) (answer: Int), value: Int) (answer: Int) { answer = route(value) }
|
||||
forward(route: handler(value: Int) (answer: Int), value: Int) (answer: Int) { answer = apply(route, value) }
|
||||
identity(value: Int) (answer: Int) pure { answer = value }
|
||||
noisy(value: Int) (answer: Int) { dever.io.println("called")
|
||||
answer = value }
|
||||
"#;
|
||||
checked(&sources(&format!(
|
||||
"{shared}\nmain() (answer: Int) pure {{ answer = forward(identity, 1) }}"
|
||||
)));
|
||||
rejected(
|
||||
&format!("{shared}\nmain() (answer: Int) pure {{ answer = forward(noisy, 1) }}"),
|
||||
"has effects: stdout",
|
||||
);
|
||||
rejected(
|
||||
"apply(route: handler(value: Int) (answer: Int), value: Int) (answer: Int) pure { answer = route(value) }",
|
||||
"has effects: handler",
|
||||
);
|
||||
checked(&sources(&format!(
|
||||
"{shared}\nignore(route: handler(value: Int) (answer: Int), value: Int) (answer: Int) {{ answer = value }}\nmain() (answer: Int) pure {{ answer = ignore(noisy, 1) }}"
|
||||
)));
|
||||
|
||||
let collection = r#"
|
||||
map_values(route: handler(value: Int) (answer: Int), values: List<Int>) (answers: List<Int>) { answers = each(route, values) }
|
||||
identity(value: Int) (answer: Int) pure { answer = value }
|
||||
noisy(value: Int) (answer: Int) { dever.io.println("called")
|
||||
answer = value }
|
||||
"#;
|
||||
checked(&sources(&format!(
|
||||
"{collection}\nmain() (answers: List<Int>) pure {{ answers = map_values(identity, [1]) }}"
|
||||
)));
|
||||
rejected(
|
||||
&format!(
|
||||
"{collection}\nmain() (answers: List<Int>) pure {{ answers = map_values(noisy, [1]) }}"
|
||||
),
|
||||
"has effects: stdout",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pure_arithmetic_and_text_are_allowed_but_resource_and_clock_effects_are_rejected() {
|
||||
checked(&sources(
|
||||
"public main() (answer: Text) pure { answer = text.trim(\" x \") }",
|
||||
));
|
||||
rejected(
|
||||
"clock() (result: Int) pure { result = dever.time.unix_millis() }",
|
||||
"has effects: time",
|
||||
);
|
||||
rejected(
|
||||
"close_stream(stream: Stream<Int>) () pure { close(stream) }",
|
||||
"has effects: stream",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api_snapshot_detects_expansion_and_effect_changes_but_ignores_private_helpers() {
|
||||
let original = checked(&sources("public main() (answer: Int) { answer = 1 }"));
|
||||
let baseline = original.api_snapshot();
|
||||
assert!(baseline.contains("function main.main"));
|
||||
assert!(!baseline.contains("function dever."));
|
||||
original.check_api(&baseline).unwrap();
|
||||
let helper = checked(&sources(
|
||||
"public main() (answer: Int) { answer = helper() }\nhelper() (answer: Int) { answer = 2 }",
|
||||
));
|
||||
helper.check_api(&baseline).unwrap();
|
||||
let expanded = checked(&sources(
|
||||
"public main() (answer: Int) { answer = 1 }\npublic helper() (answer: Int) { answer = 2 }",
|
||||
));
|
||||
let difference = expanded.check_api(&baseline).unwrap_err();
|
||||
assert!(difference.contains("+ function main.helper"));
|
||||
let noisy = checked(&sources(
|
||||
"public main() (answer: Int) { dever.io.println(\"x\")\nanswer = 1 }",
|
||||
));
|
||||
assert!(
|
||||
noisy
|
||||
.check_api(&baseline)
|
||||
.unwrap_err()
|
||||
.contains("effects=[stdout]")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api_includes_constraints_and_private_construction_without_internal_representation() {
|
||||
let program = checked(&sources(
|
||||
"public type Port { value: Int >= 1 and <= 65535\nprivate secret: Text }",
|
||||
));
|
||||
let snapshot = program.api_snapshot();
|
||||
assert!(snapshot.contains("construct=private"));
|
||||
assert!(snapshot.contains("1..65535"));
|
||||
assert!(!snapshot.contains("secret"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api_tracks_private_transitive_recovery_without_exposing_helper_names() {
|
||||
let original = checked(&sources("public main() (answer: Int) pure { answer = 1 }"));
|
||||
let changed = checked(&sources(
|
||||
"public main() (answer: Int) pure { answer = bridge() }\nbridge() (answer: Int) pure { answer = fallback() }\nfallback() (answer: Int) pure recover(\"missing configuration uses default\") { answer = 1 }",
|
||||
));
|
||||
let snapshot = changed.api_snapshot();
|
||||
assert!(snapshot.contains("missing configuration uses default"));
|
||||
assert!(!snapshot.contains("bridge"));
|
||||
assert!(!snapshot.contains("fallback"));
|
||||
assert!(changed.check_api(&original.api_snapshot()).is_err());
|
||||
assert!(
|
||||
snapshot.contains("effects=[]"),
|
||||
"recovery itself is not an external effect"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recovery_through_handlers_only_propagates_when_the_handler_is_invoked() {
|
||||
let helpers = "fallback(value: Int) (answer: Int) recover(\"use default\") { answer = value }\napply(route: handler(value: Int) (answer: Int), value: Int) (answer: Int) { answer = route(value) }\nignore(route: handler(value: Int) (answer: Int), value: Int) (answer: Int) { answer = value }\n";
|
||||
let invoked = checked(&sources(&format!(
|
||||
"{helpers}public main() (answer: Int) pure {{ answer = apply(fallback, 1) }}"
|
||||
)));
|
||||
assert!(invoked.api_snapshot().contains("use default"));
|
||||
let ignored = checked(&sources(&format!(
|
||||
"{helpers}public main() (answer: Int) pure {{ answer = ignore(fallback, 1) }}"
|
||||
)));
|
||||
assert!(!ignored.api_snapshot().contains("use default"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn effect_work_queue_preserves_long_chains_and_unused_handler_cycles() {
|
||||
let mut source = String::from("public main() () { step0() }\n");
|
||||
for level in 0..64 {
|
||||
source.push_str(&format!("step{level}() () {{ step{}() }}\n", level + 1));
|
||||
}
|
||||
source
|
||||
.push_str("step64() () recover(\"report optional event\") { dever.io.println(\"event\") }");
|
||||
let snapshot = checked(&sources(&source)).api_snapshot();
|
||||
assert!(snapshot.contains("effects=[stdout]"), "{snapshot}");
|
||||
assert!(snapshot.contains("report optional event"), "{snapshot}");
|
||||
rejected(
|
||||
&source.replace("main() () {", "main() () pure {"),
|
||||
"has effects: stdout",
|
||||
);
|
||||
|
||||
let cycle = "public main() () pure { bridge() }\nbridge() () { ignore(bridge) }\nignore(route: handler() ()) () recover(\"skip optional callback\") {}";
|
||||
let snapshot = checked(&sources(cycle)).api_snapshot();
|
||||
assert!(snapshot.contains("effects=[]"), "{snapshot}");
|
||||
assert!(snapshot.contains("skip optional callback"), "{snapshot}");
|
||||
let unused = cycle.replace(
|
||||
"bridge() () { ignore(bridge) }",
|
||||
"bridge() () { ignore(bridge)\nignore(noisy) }\nnoisy() () recover(\"unused recovery\") { dever.io.println(\"unused\") }",
|
||||
);
|
||||
let snapshot = checked(&sources(&unused)).api_snapshot();
|
||||
assert!(snapshot.contains("effects=[]"), "{snapshot}");
|
||||
assert!(!snapshot.contains("unused recovery"), "{snapshot}");
|
||||
}
|
||||
Reference in new issue
Block a user