278 lines
10 KiB
Rust
278 lines
10 KiB
Rust
use super::{Directory, fixture};
|
|
use dever::compiler::{build, project::Project};
|
|
|
|
mod async_stream;
|
|
mod channel;
|
|
mod group;
|
|
mod liveness;
|
|
mod parallel;
|
|
mod select;
|
|
|
|
#[test]
|
|
fn asynchronous_clauses_keep_nullable_payloads_axes_and_failure_cleanup() {
|
|
let directory = Directory::new();
|
|
let project = Project::load(&fixture().with_file_name("task-clauses.dever")).unwrap();
|
|
let program = project.check().unwrap();
|
|
let binary = build::build(
|
|
&program,
|
|
&project.sources,
|
|
"verify",
|
|
&directory.0.join("clauses"),
|
|
)
|
|
.unwrap();
|
|
let result = std::process::Command::new(binary).output().unwrap();
|
|
assert!(
|
|
result.status.success(),
|
|
"{}",
|
|
String::from_utf8_lossy(&result.stderr)
|
|
);
|
|
assert_eq!(result.stdout, b"true\n");
|
|
}
|
|
|
|
#[test]
|
|
fn native_tasks_transfer_values_wait_cancel_and_preserve_error_origins() {
|
|
let directory = Directory::new();
|
|
let project = Project::load(&fixture().with_file_name("task.dever")).unwrap();
|
|
let program = project.check().unwrap();
|
|
for (entry, expected) in [
|
|
("verify", None),
|
|
("sleepFailure", Some("SleepResult.Failed")),
|
|
("faultEntry", Some("integer arithmetic fault")),
|
|
] {
|
|
let binary =
|
|
build::build(&program, &project.sources, entry, &directory.0.join(entry)).unwrap();
|
|
let result = std::process::Command::new(binary).output().unwrap();
|
|
if let Some(message) = expected {
|
|
assert!(!result.status.success());
|
|
assert!(result.stdout.is_empty());
|
|
let error = String::from_utf8(result.stderr).unwrap();
|
|
assert!(error.contains(message), "{error}");
|
|
if entry == "faultEntry" {
|
|
let source = &project.sources[0];
|
|
let (line, column) = source.position(source.text.rfind("value + 1").unwrap());
|
|
assert!(
|
|
error.contains(&format!("task.dever:{line}:{column}:")),
|
|
"{error}"
|
|
);
|
|
} else {
|
|
assert!(error.contains("lib/dever/time.dever:"), "{error}");
|
|
}
|
|
} else {
|
|
assert!(
|
|
result.status.success(),
|
|
"{}",
|
|
String::from_utf8_lossy(&result.stderr)
|
|
);
|
|
assert_eq!(result.stdout, b"true\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn asynchronous_commands_keep_typed_inputs_outputs_and_synchronous_peers() {
|
|
let directory = Directory::new();
|
|
let mut project = Project::load(&fixture().with_file_name("application")).unwrap();
|
|
let source = project
|
|
.sources
|
|
.iter_mut()
|
|
.find(|source| source.path.ends_with("greeting/app.dever"))
|
|
.unwrap();
|
|
let text = source
|
|
.text
|
|
.replace(
|
|
"message = utility.decorate(domain.greet(name))",
|
|
"dever.task.sleep(0)\n message = utility.decorate(domain.greet(name))",
|
|
)
|
|
.replace(
|
|
"{ first = value\n second = value + 1 }",
|
|
"{ dever.task.sleep(0)\n first = value\n second = value + 1 }",
|
|
);
|
|
*source = dever::syntax::source::Source::new(source.id, source.path.clone(), text);
|
|
let program = project.check().unwrap();
|
|
let binary = build::build(
|
|
&program,
|
|
&project.sources,
|
|
"main",
|
|
&directory.0.join("commands"),
|
|
)
|
|
.unwrap();
|
|
for (command, input, expected) in [
|
|
("greet", r#"{"name":"雪"}"#, r#""[Hello, 雪]""#),
|
|
("pair", r#"{"value":41}"#, r#"{"first":41,"second":42}"#),
|
|
("double", r#"{"value":21}"#, "42"),
|
|
] {
|
|
let result = std::process::Command::new(&binary)
|
|
.args([&format!("demo.greeting.{command}"), input])
|
|
.output()
|
|
.unwrap();
|
|
assert!(
|
|
result.status.success(),
|
|
"{}",
|
|
String::from_utf8_lossy(&result.stderr)
|
|
);
|
|
assert_eq!(
|
|
String::from_utf8(result.stdout).unwrap(),
|
|
format!("{{\"code\":0,\"message\":\"ok\",\"data\":{expected}}}\n")
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn leaf_entry_preserves_business_failure_origin() {
|
|
let directory = Directory::new();
|
|
let mut project = Project::load(&fixture().with_file_name("task.dever")).unwrap();
|
|
let source = &project.sources[0];
|
|
let text = format!(
|
|
"{}\nasyncFailure() () {{ dever.task.sleep(-1) }}\n",
|
|
source.text
|
|
);
|
|
project.sources[0] = dever::syntax::source::Source::new(source.id, source.path.clone(), text);
|
|
let program = project.check().unwrap();
|
|
let binary = build::build(
|
|
&program,
|
|
&project.sources,
|
|
"asyncFailure",
|
|
&directory.0.join("failure"),
|
|
)
|
|
.unwrap();
|
|
let result = std::process::Command::new(binary).output().unwrap();
|
|
assert!(!result.status.success());
|
|
assert!(result.stdout.is_empty());
|
|
let source = project
|
|
.sources
|
|
.iter()
|
|
.find(|source| source.path.ends_with("lib/dever/task.dever"))
|
|
.unwrap();
|
|
let offset = source
|
|
.text
|
|
.find("fail(dever.time.SleepResult.Failed(message))")
|
|
.unwrap()
|
|
+ "fail(".len();
|
|
let (line, column) = source.position(offset);
|
|
let error = String::from_utf8(result.stderr).unwrap();
|
|
assert!(error.contains("SleepResult.Failed"), "{error}");
|
|
assert!(
|
|
error.contains(&format!("{}:{line}:{column}:", source.path.display())),
|
|
"{error}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn actual_entries_only_create_task_scopes_when_they_schedule() {
|
|
let directory = Directory::new();
|
|
let mut project = Project::load(&fixture().with_file_name("task-performance.dever")).unwrap();
|
|
let cases = [
|
|
("syncEntry", "answer = total([1, 2])", false, false, "3\n"),
|
|
(
|
|
"leafEntry",
|
|
"answer = asyncTotal([1, 2], false)",
|
|
true,
|
|
false,
|
|
"3\n",
|
|
),
|
|
("forwardEntry", "answer = leafEntry()", true, false, "3\n"),
|
|
(
|
|
"scheduledEntry",
|
|
"answer = asyncPair([1, 2])",
|
|
true,
|
|
true,
|
|
"6\n",
|
|
),
|
|
(
|
|
"blockingEntry",
|
|
"answer = blockingSum([1, 2])",
|
|
true,
|
|
true,
|
|
"3\n",
|
|
),
|
|
("cancelEntry", "answer = cancel()", true, true, "1\n"),
|
|
];
|
|
let mut text = project.sources[0].text.clone();
|
|
for (name, body, _, _, _) in cases {
|
|
text.push_str(&format!("{name}() (answer: Int) {{ {body} }}\n"));
|
|
}
|
|
let source = &project.sources[0];
|
|
project.sources[0] = dever::syntax::source::Source::new(source.id, source.path.clone(), text);
|
|
let program = project.check().unwrap();
|
|
for (entry, _, suspend, schedule, expected) in cases {
|
|
let signature = &program
|
|
.functions
|
|
.iter()
|
|
.find(|function| function.signature.name == entry)
|
|
.unwrap()
|
|
.signature;
|
|
assert_eq!(
|
|
(signature.effects.suspend, signature.effects.schedule),
|
|
(suspend, schedule),
|
|
"{entry}"
|
|
);
|
|
let binary =
|
|
build::build(&program, &project.sources, entry, &directory.0.join(entry)).unwrap();
|
|
let output = std::process::Command::new(binary).output().unwrap();
|
|
assert!(
|
|
output.status.success(),
|
|
"{entry}: {}",
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
assert_eq!(output.stdout, expected.as_bytes(), "{entry}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn pending_tasks_keep_cleanup_on_each_failure_exit() {
|
|
let directory = Directory::new();
|
|
let mut project = Project::load(&fixture().with_file_name("task.dever")).unwrap();
|
|
let source = &project.sources[0];
|
|
let mut text = source.text.clone();
|
|
for code in [
|
|
"explicitFailure() () { pending = run(dever.task.sleep(60000))\n fail(dever.time.SleepResult.Failed(\"explicit\")) }",
|
|
"explicitVerify() (answer: Bool) { answer = failed(result(explicitFailure())) }",
|
|
"arithmeticSleep(value: Int) () { next = value + 1\n dever.task.sleep(next) }",
|
|
"capturedFault(value: Int) (answer: dever.time.SleepResult) { pending = run(dever.task.sleep(60000))\n answer = result(arithmeticSleep(value))\n stop(pending) }",
|
|
"captureFaultEntry() (answer: dever.time.SleepResult) { answer = capturedFault(9223372036854775807) }",
|
|
"sumWithPending(values: List<Int>) (answer: Int) { pending = run(dever.task.sleep(60000))\n answer = sum(values)\n stop(pending) }",
|
|
"sumCleanupEntry() (answer: Int) { answer = sumWithPending([9223372036854775807, 1]) }",
|
|
"mapWithPending(key: Text) (answer: Map<Text, Int>) { pending = run(dever.task.sleep(60000))\n answer = { key = 1\n \"same\" = 2 }\n stop(pending) }",
|
|
"mapCleanupEntry() (answer: Map<Text, Int>) { answer = mapWithPending(\"same\") }",
|
|
] {
|
|
text.push_str(&format!("{code}\n"));
|
|
}
|
|
project.sources[0] = dever::syntax::source::Source::new(source.id, source.path.clone(), text);
|
|
let program = project.check().unwrap();
|
|
for (entry, failure) in [
|
|
("explicitVerify", None),
|
|
(
|
|
"captureFaultEntry",
|
|
Some(("value + 1", "integer arithmetic fault")),
|
|
),
|
|
("sumCleanupEntry", Some(("sum(values)", "integer overflow"))),
|
|
(
|
|
"mapCleanupEntry",
|
|
Some(("{ key = 1", "duplicate Map literal key")),
|
|
),
|
|
] {
|
|
let binary =
|
|
build::build(&program, &project.sources, entry, &directory.0.join(entry)).unwrap();
|
|
let result = std::process::Command::new(binary).output().unwrap();
|
|
if let Some((needle, message)) = failure {
|
|
assert!(!result.status.success());
|
|
assert!(result.stdout.is_empty());
|
|
let source = &project.sources[0];
|
|
let (line, column) = source.position(source.text.rfind(needle).unwrap());
|
|
let error = String::from_utf8(result.stderr).unwrap();
|
|
assert!(
|
|
error.contains(&format!("task.dever:{line}:{column}:")),
|
|
"{error}"
|
|
);
|
|
assert!(error.contains(message), "{error}");
|
|
} else {
|
|
assert!(
|
|
result.status.success(),
|
|
"{}",
|
|
String::from_utf8_lossy(&result.stderr)
|
|
);
|
|
assert_eq!(result.stdout, b"true\n");
|
|
}
|
|
}
|
|
}
|