Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,67 @@
|
||||
use super::{database::ERROR, package::packages};
|
||||
|
||||
mod tasks;
|
||||
|
||||
fn checked(
|
||||
app: &str,
|
||||
job: &str,
|
||||
api: &str,
|
||||
) -> Result<dever::compiler::model::Program, dever::syntax::diagnostic::Diagnostic> {
|
||||
packages(&[
|
||||
("dever.database", ERROR),
|
||||
("demo.queue.app", app),
|
||||
("demo.queue.job", job),
|
||||
("demo.queue.api", api),
|
||||
])
|
||||
}
|
||||
|
||||
const APP: &str = "type Payload { name: Text }\nsubmit(key: Text) (id: Id) { id = dever.job.enqueue(job.publish, Payload { name = key }, key) }\napply(input: Payload) () {}";
|
||||
const JOB: &str = "database default\njob publish(input: app.Payload) () retry(2) timeout(1000) { app.apply(input) }";
|
||||
|
||||
#[test]
|
||||
fn persistent_job_is_a_separate_entry_and_not_a_synchronous_call() {
|
||||
let program = checked(APP, JOB, "cmd submit = app.submit").unwrap();
|
||||
assert_eq!(program.jobs.len(), 1);
|
||||
let job = &program.jobs[0];
|
||||
assert_eq!(job.name, "demo.queue.job.publish");
|
||||
assert!(program.entries().any(|entry| entry == job.function));
|
||||
assert!(
|
||||
program.functions[program.commands[0].function]
|
||||
.signature
|
||||
.effects
|
||||
.enqueue
|
||||
);
|
||||
let called = APP.replace(
|
||||
"dever.job.enqueue(job.publish, Payload { name = key }, key)",
|
||||
"job.publish(Payload { name = key })",
|
||||
);
|
||||
assert!(checked(&called, JOB, "cmd submit = app.submit").is_err());
|
||||
assert!(checked(APP, JOB, "public post submit = app.submit").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn job_contract_rejects_bad_policy_payload_effects_and_schedules() {
|
||||
for job in [
|
||||
JOB.replace("retry(2)", "retry(0)"),
|
||||
JOB.replace("timeout(1000)", "timeout(0)"),
|
||||
JOB.replace("database default\n", ""),
|
||||
JOB.replace("app.apply(input)", "dever.sleep(1)"),
|
||||
format!("{JOB}\nschedule publish = \"* * * * *\""),
|
||||
] {
|
||||
assert!(
|
||||
checked(APP, &job, "cmd submit = app.submit").is_err(),
|
||||
"{job}"
|
||||
);
|
||||
}
|
||||
let secret = APP.replace("name: Text", "name: Secret");
|
||||
assert!(checked(&secret, JOB, "cmd submit = app.submit").is_err());
|
||||
let app = "run() () {}";
|
||||
let jobs = "database default\njob cleanup() () retry(1) timeout(100) { app.run() }\nschedule cleanup = \"*/5 * * * *\"";
|
||||
let program = checked(app, jobs, "").unwrap();
|
||||
assert_eq!(program.schedules.len(), 1);
|
||||
let features =
|
||||
dever::compiler::build::runtime_features(&program, &program.reachable(program.entries()));
|
||||
assert!(features.contains(&"database"));
|
||||
assert_eq!(features.contains(&"postgres"), cfg!(feature = "postgres"));
|
||||
assert!(checked(app, &jobs.replace("*/5", "*/0"), "").is_err());
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
use super::{APP, JOB, checked};
|
||||
|
||||
fn app(body: &str) -> String {
|
||||
format!(
|
||||
"{}\ntransaction send(key: Text) () {{ id = submit(key) }}\nlaunch() () {{ {body} }}",
|
||||
APP
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tasks_can_inherit_enqueue_identity_without_sharing_a_transaction() {
|
||||
for body in [
|
||||
"pending = run(send(\"one\"))\nwait(pending)\nsend(\"after\")",
|
||||
"pool = group(2)\nrun(pool, send(\"one\"))\nrun(pool, send(\"two\"))\nwait(pool)\nsend(\"after\")",
|
||||
"parallelEach(send, [\"one\", \"two\"], 2)\nsend(\"after\")",
|
||||
] {
|
||||
let source = app(body);
|
||||
let program = checked(&source, JOB, "long cmd launch = app.launch").unwrap();
|
||||
let signature = &program.functions[program.commands[0].function].signature;
|
||||
assert!(signature.effects.enqueue);
|
||||
assert!(!signature.effects.request);
|
||||
assert!(!signature.effects.unscoped_write);
|
||||
assert!(signature.effects.concurrent || signature.effects.parallel);
|
||||
|
||||
// 普通入口不能把自动事务变成多个独立提交;必须显式声明 long。
|
||||
let error = checked(&source, JOB, "cmd launch = app.launch").unwrap_err();
|
||||
assert_eq!(error.code, "C014", "{body}: {error:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn long_jobs_can_launch_children_only_through_explicit_transactions() {
|
||||
let source = app("parallelEach(send, [\"one\", \"two\"], 2)");
|
||||
let jobs = format!("{JOB}\nlong job fanout() () retry(1) timeout(1000) {{ app.launch() }}");
|
||||
assert!(checked(&source, &jobs, "").is_ok());
|
||||
assert_eq!(
|
||||
checked(&source, &jobs.replace("long job", "job"), "")
|
||||
.unwrap_err()
|
||||
.code,
|
||||
"C014"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enqueue_does_not_escape_transaction_or_synchronous_worker_restrictions() {
|
||||
for body in [
|
||||
"pending = run(send(\"one\"))\nwait(pending)",
|
||||
"pool = group(1)\nrun(pool, send(\"one\"))\nwait(pool)",
|
||||
"parallelEach(send, [\"one\"], 1)",
|
||||
] {
|
||||
let source = app(body).replace("launch() ()", "transaction launch() ()");
|
||||
assert_eq!(
|
||||
checked(&source, JOB, "long cmd launch = app.launch")
|
||||
.unwrap_err()
|
||||
.code,
|
||||
"C014",
|
||||
"{body}"
|
||||
);
|
||||
}
|
||||
for boundary in ["blocking", "parallel"] {
|
||||
let source = app(&format!("{boundary}(send(\"one\"))"));
|
||||
assert_eq!(
|
||||
checked(&source, JOB, "long cmd launch = app.launch")
|
||||
.unwrap_err()
|
||||
.code,
|
||||
"C013"
|
||||
);
|
||||
}
|
||||
let unscoped =
|
||||
app("pending = run(send(\"one\"))\nwait(pending)").replace("transaction send", "send");
|
||||
assert_eq!(
|
||||
checked(&unscoped, JOB, "long cmd launch = app.launch")
|
||||
.unwrap_err()
|
||||
.code,
|
||||
"C014"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_enqueue_remains_forbidden_through_tasks() {
|
||||
let source = app("pending = run(send(\"one\"))\nwait(pending)");
|
||||
assert_eq!(
|
||||
checked(&source, JOB, "public long post launch = app.launch")
|
||||
.unwrap_err()
|
||||
.code,
|
||||
"C014"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enqueue_capability_does_not_make_request_identity_transferable() {
|
||||
for body in [
|
||||
"pending = run(send(\"one\"))\nwait(pending)",
|
||||
"pool = group(1)\nrun(pool, send(\"one\"))\nwait(pool)",
|
||||
"parallelEach(send, [\"one\"], 1)",
|
||||
] {
|
||||
let source = app(body).replace(
|
||||
"id = submit(key)",
|
||||
"identity = dever.auth.id()\nid = submit(identity + key)",
|
||||
);
|
||||
let error = super::packages(&[
|
||||
("dever.database", super::ERROR),
|
||||
(
|
||||
"dever.api",
|
||||
"public type Error { error Invalid\nerror Unauthorized\nerror Forbidden }",
|
||||
),
|
||||
(
|
||||
"dever.auth",
|
||||
"public id() (value: Text) { value = dever.system.authId() }",
|
||||
),
|
||||
("demo.queue.app", &source),
|
||||
("demo.queue.job", JOB),
|
||||
("demo.queue.api", "long cmd launch = app.launch"),
|
||||
])
|
||||
.unwrap_err();
|
||||
assert_eq!(error.code, "C013", "{body}: {error:?}");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user