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,176 @@
|
||||
mod support;
|
||||
|
||||
use support::{checked, rejected, sources};
|
||||
|
||||
const ERRORS: &str = r#"public type ComputeResult {
|
||||
Done(value: Int)
|
||||
error Invalid(value: Int)
|
||||
}
|
||||
compute(value: 0) (answer: Int) {
|
||||
fail(ComputeResult.Invalid(value))
|
||||
}
|
||||
compute(value: other) (answer: Int) {
|
||||
answer = value + 1
|
||||
}
|
||||
render(result: ComputeResult.Done(value)) (text: Text) recover("render handled compute errors") {
|
||||
text = int.to_text(value)
|
||||
}
|
||||
render(result: ComputeResult.Invalid(value)) (text: Text) recover("render handled compute errors") {
|
||||
text = "invalid " + int.to_text(value)
|
||||
}
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn fail_and_result_have_stable_syntax_and_formatting() {
|
||||
let source = format!("{ERRORS}main() (text: Text) {{ text = render(result(compute(0))) }}");
|
||||
let mut map = sources(&source);
|
||||
let formatted = dever_core::format::format(map.get(map.files()[0].id())).unwrap();
|
||||
map = sources(&formatted);
|
||||
let reformatted = dever_core::format::format(map.get(map.files()[0].id())).unwrap();
|
||||
assert_eq!(formatted, reformatted);
|
||||
assert!(formatted.contains("fail(ComputeResult.Invalid(value))"));
|
||||
assert!(formatted.contains("result(compute(0))"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_sets_propagate_and_are_visible_in_the_public_api() {
|
||||
let source = format!(
|
||||
"{ERRORS}forward(value: Int) (answer: Int) {{ answer = compute(value) }}\npublic main() (answer: Int) {{ answer = forward(1) }}"
|
||||
);
|
||||
let program = checked(&sources(&source));
|
||||
let snapshot = program.api_snapshot();
|
||||
assert!(
|
||||
snapshot.contains("function main.main() (answer: Int [])")
|
||||
&& snapshot.contains("errors=[main.ComputeResult.Invalid]"),
|
||||
"{snapshot}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_capture_requires_matching_success_and_error_variants() {
|
||||
checked(&sources(&format!(
|
||||
"{ERRORS}main() (text: Text) {{ text = render(result(compute(0))) }}"
|
||||
)));
|
||||
rejected(
|
||||
&format!(
|
||||
"{ERRORS}type Wrong {{ Done(value: Int)\nerror Other(value: Int) }}\nwrong(result: Wrong.Done(value)) () recover(\"handled\") {{}}\nwrong(result: Wrong.Other(value)) () recover(\"handled\") {{}}\nmain() () {{ wrong(result(compute(0))) }}"
|
||||
),
|
||||
"result capture errors do not match the call",
|
||||
);
|
||||
rejected(
|
||||
&format!(
|
||||
"{ERRORS}type Wrong {{ Done\nerror Invalid(value: Int) }}\nwrong(result: Wrong) () recover(\"handled\") {{}}\nmain() () {{ wrong(result(compute(0))) }}"
|
||||
),
|
||||
"success payload must match",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_capture_does_not_introduce_a_concurrency_effect() {
|
||||
checked(&sources(&format!(
|
||||
"{ERRORS}main() (result: ComputeResult) pure {{ result = result(compute(0)) }}"
|
||||
)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fail_must_be_an_error_variant_and_end_its_clause() {
|
||||
rejected(
|
||||
"type Outcome { Done\nerror Failed }\npublic main() () { fail(Outcome.Done) }",
|
||||
"fail requires an error choice variant",
|
||||
);
|
||||
rejected(
|
||||
"type Outcome { error Failed }\npublic main() () { fail(Outcome.Failed)\ndever.io.println(\"unreachable\") }",
|
||||
"fail must be the final statement",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_default_propagation_reaches_the_cli_boundary_once() {
|
||||
let output = support::run(
|
||||
"type Outcome { error Failed(value: Int) }\nstep() () { fail(Outcome.Failed(7)) }\npublic main() () { step() }",
|
||||
);
|
||||
assert!(!output.status.success());
|
||||
assert!(output.stdout.is_empty());
|
||||
let stderr = String::from_utf8(output.stderr).unwrap();
|
||||
assert_eq!(stderr.lines().count(), 1, "{stderr}");
|
||||
assert!(stderr.contains("main.Outcome.Failed(7)"), "{stderr}");
|
||||
assert!(stderr.contains("main.dever:3:"), "{stderr}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_result_capture_converts_success_and_business_failure_to_choice_values() {
|
||||
for (value, expected) in [("0", "invalid 0"), ("4", "5")] {
|
||||
let output = support::stdout(&format!(
|
||||
"{ERRORS}main() (text: Text) {{ text = render(result(compute({value}))) }}"
|
||||
));
|
||||
assert_eq!(output, format!("text = {expected}\n"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn task_wait_preserves_business_error_identity() {
|
||||
let output = support::run(
|
||||
"type Outcome { error Failed(value: Int) }\nstep() () { fail(Outcome.Failed(9)) }\npublic main() () { task = run(step())\nwait(task) }",
|
||||
);
|
||||
assert!(!output.status.success());
|
||||
let stderr = String::from_utf8(output.stderr).unwrap();
|
||||
assert!(stderr.contains("main.Outcome.Failed(9)"), "{stderr}");
|
||||
assert!(stderr.contains("main.dever:4:"), "{stderr}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn capture_wraps_a_shared_error_choice_without_losing_its_identity() {
|
||||
let source = r#"public type Failure {
|
||||
error Missing(message: Text)
|
||||
error Conflict(message: Text)
|
||||
}
|
||||
public type Outcome {
|
||||
Done(value: Int)
|
||||
error Failed(error: Failure)
|
||||
}
|
||||
load(conflict: true) (value: Int) { fail(Failure.Conflict("duplicate")) }
|
||||
load(conflict: false) (value: Int) { fail(Failure.Missing("absent")) }
|
||||
public main() (outcome: Outcome) { outcome = result(load(false)) }
|
||||
"#;
|
||||
assert_eq!(
|
||||
support::stdout(source),
|
||||
"outcome = main.Outcome.Failed(main.Failure.Missing(absent))\n"
|
||||
);
|
||||
#[cfg(feature = "reference")]
|
||||
{
|
||||
let sources = sources(source);
|
||||
let result = dever_core::reference::evaluate(&checked(&sources), "main.main").unwrap();
|
||||
assert!(result.fault.is_none(), "{:?}", result.fault);
|
||||
assert_eq!(
|
||||
result.stdout,
|
||||
"outcome = main.Outcome.Failed(main.Failure.Missing(absent))\n"
|
||||
);
|
||||
}
|
||||
rejected(
|
||||
&source.replace(
|
||||
"error Failed(error: Failure)",
|
||||
"error Failed(error: Failure)\nerror Again(error: Failure)",
|
||||
),
|
||||
"overlapping error wrappers",
|
||||
);
|
||||
rejected(
|
||||
&source.replace(
|
||||
"outcome = result(load(false))",
|
||||
"outcome = result(load(false))\noutcome = Outcome.Done(0)",
|
||||
),
|
||||
"failure is not handled",
|
||||
);
|
||||
let wrapped = source.replace(
|
||||
"public main() (outcome: Outcome) { outcome = result(load(false)) }",
|
||||
r#"
|
||||
rewrap(captured: Outcome.Done(number)) (answer: Int) { answer = number }
|
||||
rewrap(captured: Outcome.Failed(error)) (answer: Int) { fail(Outcome.Failed(error)) }
|
||||
load_again() (answer: Int) { answer = rewrap(result(load(false))) }
|
||||
public main() (outcome: Outcome) { outcome = result(load_again()) }
|
||||
"#,
|
||||
);
|
||||
assert_eq!(
|
||||
support::stdout(&wrapped),
|
||||
"outcome = main.Outcome.Failed(main.Failure.Missing(absent))\n"
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user