Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,384 @@
|
||||
use super::Directory;
|
||||
use dever::compiler::testing::{self, Suite};
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[path = "testing/process.rs"]
|
||||
mod process;
|
||||
|
||||
fn project() -> Directory {
|
||||
let directory = Directory::new();
|
||||
let root = directory.0.join("module/demo/value");
|
||||
std::fs::create_dir_all(&root).unwrap();
|
||||
for (name, text) in [
|
||||
(
|
||||
"app",
|
||||
include_str!("../fixtures/testing/module/demo/value/app.dever"),
|
||||
),
|
||||
(
|
||||
"port",
|
||||
include_str!("../fixtures/testing/module/demo/value/port.dever"),
|
||||
),
|
||||
(
|
||||
"adapter",
|
||||
include_str!("../fixtures/testing/module/demo/value/adapter.dever"),
|
||||
),
|
||||
(
|
||||
"api",
|
||||
include_str!("../fixtures/testing/module/demo/value/api.dever"),
|
||||
),
|
||||
] {
|
||||
std::fs::write(root.join(format!("{name}.dever")), text).unwrap();
|
||||
}
|
||||
std::fs::create_dir_all(directory.0.join("test/demo/value")).unwrap();
|
||||
directory
|
||||
}
|
||||
|
||||
fn case(root: &Path, name: &str, body: &str, fake: Option<&str>) {
|
||||
let mut text = format!("{name}() () {{\n{body}\n}}\n");
|
||||
if let Some(fake) = fake {
|
||||
text.push_str(&format!(
|
||||
"port.read(value: Int) (answer: Int) {{\n{fake}\n}}\n"
|
||||
));
|
||||
if fake.contains("echo") {
|
||||
text.push_str("echo(value: Int) (answer: Int) { answer = value + 10 }\n");
|
||||
}
|
||||
}
|
||||
std::fs::write(root.join(format!("test/demo/value/{name}.dever")), text).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_test_suite_isolates_fake_effects_reports_failures_and_continues_after_timeout() {
|
||||
let directory = project();
|
||||
case(
|
||||
&directory.0,
|
||||
"first",
|
||||
"assertEq(app.read(4), 5)",
|
||||
Some("answer = value + 1"),
|
||||
);
|
||||
case(
|
||||
&directory.0,
|
||||
"second",
|
||||
"assertEq(app.read(4), 14)",
|
||||
Some("answer = wait(run(echo(value)))"),
|
||||
);
|
||||
case(
|
||||
&directory.0,
|
||||
"third",
|
||||
"pending = run(dever.task.sleep(1000))\nassertEq(app.read(4), 99)\nwait(pending)",
|
||||
Some("answer = value"),
|
||||
);
|
||||
case(&directory.0, "timeout", "dever.time.sleep(500)", None);
|
||||
case(
|
||||
&directory.0,
|
||||
"valid",
|
||||
"key = secret(\"case input\")\ndigest = dever.crypto.hmacSha256(key, dever.bytes.fromText(\"x\"))\nassertEq(dever.bytes.length(digest), 32)\nassertEq(dever.test.drainJobs(10), 0)",
|
||||
None,
|
||||
);
|
||||
let suite = Suite::load(&directory.0).unwrap();
|
||||
assert_eq!(suite.select(Some("demo.value.second")).unwrap(), [1]);
|
||||
assert!(suite.select(Some("demo.valu")).is_err());
|
||||
let report = testing::run(&suite, None, Duration::from_millis(150)).unwrap();
|
||||
assert!(!report.success());
|
||||
assert_eq!(
|
||||
report
|
||||
.cases
|
||||
.iter()
|
||||
.map(|case| case.success)
|
||||
.collect::<Vec<_>>(),
|
||||
[true, true, false, false, true]
|
||||
);
|
||||
assert!(report.cases[2].stderr.contains("actual: 4\nexpected: 99"));
|
||||
assert!(report.cases[2].stderr.contains("third.dever:"));
|
||||
assert!(report.cases[3].timed_out);
|
||||
assert!(!directory.0.join("data").exists());
|
||||
let filtered = testing::run(&suite, Some("demo.value.second"), Duration::from_secs(3)).unwrap();
|
||||
assert!(filtered.success());
|
||||
assert_eq!(filtered.not_run, 4);
|
||||
assert_eq!(filtered.cases[0].index, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_rejects_missing_fake_and_bad_unselected_case() {
|
||||
let directory = project();
|
||||
case(&directory.0, "first", "assertEq(app.read(4), 5)", None);
|
||||
let error = Suite::load(&directory.0).err().unwrap();
|
||||
assert!(
|
||||
error.contains("fake") || error.contains("production Adapter"),
|
||||
"{error}"
|
||||
);
|
||||
case(
|
||||
&directory.0,
|
||||
"first",
|
||||
"assertEq(app.read(4), 5)",
|
||||
Some("answer = value + 1"),
|
||||
);
|
||||
case(&directory.0, "second", "assert(42)", None);
|
||||
assert!(Suite::load(&directory.0).is_err());
|
||||
case(&directory.0, "second", "assertEq(1 +, 2)", None);
|
||||
let output = std::process::Command::new(env!("CARGO_BIN_EXE_dever"))
|
||||
.arg("test")
|
||||
.arg(&directory.0)
|
||||
.args(["--filter", "demo.value.first"])
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(!output.status.success());
|
||||
assert!(String::from_utf8_lossy(&output.stderr).contains("second.dever:"));
|
||||
assert!(!String::from_utf8_lossy(&output.stdout).contains("PASS"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tooling_cli_formats_checks_and_executes_without_deployment_settings() {
|
||||
let directory = project();
|
||||
case(&directory.0, "simple", "assertEq(1 + 1, 2)", None);
|
||||
std::fs::create_dir(directory.0.join("config")).unwrap();
|
||||
std::fs::write(directory.0.join("config/setting.json"), r#"{"database":{"default":{"type":"postgres","host":"invalid.test","database":"production"}}}"#).unwrap();
|
||||
let cli = |arguments: &[&str]| {
|
||||
std::process::Command::new(env!("CARGO_BIN_EXE_dever"))
|
||||
.arg(arguments[0])
|
||||
.arg(&directory.0)
|
||||
.args(&arguments[1..])
|
||||
.output()
|
||||
.unwrap()
|
||||
};
|
||||
assert!(!cli(&["fmt", "--check"]).status.success());
|
||||
let formatted = cli(&["fmt"]);
|
||||
assert!(
|
||||
formatted.status.success(),
|
||||
"{}",
|
||||
String::from_utf8_lossy(&formatted.stderr)
|
||||
);
|
||||
assert!(cli(&["fmt", "--check"]).status.success());
|
||||
let tested = cli(&[
|
||||
"test",
|
||||
"--filter",
|
||||
"demo.value.simple",
|
||||
"--timeout-ms",
|
||||
"3000",
|
||||
]);
|
||||
assert!(
|
||||
tested.status.success(),
|
||||
"{}",
|
||||
String::from_utf8_lossy(&tested.stderr)
|
||||
);
|
||||
assert!(String::from_utf8_lossy(&tested.stdout).contains("1 passed; 0 failed; 0 not run"));
|
||||
assert!(!cli(&["test", "--filter", "demo.missing"]).status.success());
|
||||
assert!(!cli(&["test", "--timeout-ms", "0"]).status.success());
|
||||
assert!(!directory.0.join("data").exists());
|
||||
std::fs::write(directory.0.join("dever.api"), "{}").unwrap();
|
||||
assert!(
|
||||
Suite::load(&directory.0).is_err(),
|
||||
"tests must honor the API baseline"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_helpers_cannot_leak_into_production_or_call_ports_directly() {
|
||||
let directory = project();
|
||||
case(
|
||||
&directory.0,
|
||||
"direct",
|
||||
"assertEq(port.read(4), 5)",
|
||||
Some("answer = value + 1"),
|
||||
);
|
||||
assert!(Suite::load(&directory.0).is_err());
|
||||
let source =
|
||||
dever::syntax::source::Source::new(0, "case.dever", "main() () { assert(true) }".into());
|
||||
let error = dever::compiler::check::check(
|
||||
vec![dever::syntax::parse(&source).unwrap()],
|
||||
&["main".into()],
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(error.code, "C006");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_private_choice_helpers_use_the_same_type_contract() {
|
||||
let directory = project();
|
||||
case(
|
||||
&directory.0,
|
||||
"local",
|
||||
"assertEq(choose(State.Idle), 0)\nassertEq(choose(State.Ready), 1)",
|
||||
None,
|
||||
);
|
||||
let path = directory.0.join("test/demo/value/local.dever");
|
||||
let mut text = std::fs::read_to_string(&path).unwrap();
|
||||
text.push_str("type State { Idle\nReady }\n\nchoose(value: State.Idle) (answer: Int) { answer = 0 }\nchoose(value: State.Ready) (answer: Int) { answer = 1 }\n");
|
||||
std::fs::write(&path, text).unwrap();
|
||||
let suite = Suite::load(&directory.0).unwrap();
|
||||
assert_eq!(suite.cases.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fake_can_implement_multiple_ports_but_must_complete_each_contract() {
|
||||
let directory = project();
|
||||
let module = directory.0.join("module/demo/value");
|
||||
let port = std::fs::read_to_string(module.join("port.dever")).unwrap();
|
||||
let adapter = std::fs::read_to_string(module.join("adapter.dever")).unwrap();
|
||||
for role in ["port", "adapter"] {
|
||||
std::fs::remove_file(module.join(format!("{role}.dever"))).unwrap();
|
||||
std::fs::create_dir(module.join(role)).unwrap();
|
||||
}
|
||||
for topic in ["first", "second"] {
|
||||
std::fs::write(
|
||||
module.join(format!("port/{topic}.dever")),
|
||||
port.replace("demo.value.port`", &format!("demo.value.port.{topic}`")),
|
||||
)
|
||||
.unwrap();
|
||||
std::fs::write(
|
||||
module.join(format!("adapter/{topic}.dever")),
|
||||
adapter
|
||||
.replace(
|
||||
"demo.value.adapter`",
|
||||
&format!("demo.value.adapter.{topic}`"),
|
||||
)
|
||||
.replace("port.read", &format!("port.{topic}.read")),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
let app = module.join("app.dever");
|
||||
let text = std::fs::read_to_string(&app).unwrap();
|
||||
std::fs::write(
|
||||
&app,
|
||||
text.replace(
|
||||
"port.read(value)",
|
||||
"port.first.read(value) + port.second.read(value)",
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
case(
|
||||
&directory.0,
|
||||
"multiple",
|
||||
"assertEq(app.read(4), 11)",
|
||||
Some("answer = value + 1"),
|
||||
);
|
||||
let path = directory.0.join("test/demo/value/multiple.dever");
|
||||
let first = std::fs::read_to_string(&path)
|
||||
.unwrap()
|
||||
.replace("port.read", "port.first.read");
|
||||
std::fs::write(&path, &first).unwrap();
|
||||
assert!(
|
||||
Suite::load(&directory.0).is_err(),
|
||||
"one missing fake must not use production"
|
||||
);
|
||||
let section = first[first.find("port.first.read(").unwrap()..]
|
||||
.replace("port.first.read", "port.second.read")
|
||||
.replace("value + 1", "value + 2");
|
||||
std::fs::write(&path, format!("{first}\n{section}")).unwrap();
|
||||
let suite = Suite::load(&directory.0).unwrap();
|
||||
let report = testing::run(&suite, None, Duration::from_secs(5)).unwrap();
|
||||
assert!(report.success(), "{}", report.cases[0].stderr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_test_jobs_use_isolated_databases_virtual_clock_and_production_rollback() {
|
||||
let directory = Directory::new();
|
||||
std::fs::create_dir(directory.0.join("config")).unwrap();
|
||||
std::fs::write(directory.0.join("config/setting.json"), r#"{"database":{"default":{"type":"postgres","host":"invalid.test","database":"production"}}}"#).unwrap();
|
||||
let source_root =
|
||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("../test/rust/fixtures/job/module/demo/queue");
|
||||
let module = directory.0.join("module/demo/queue");
|
||||
std::fs::create_dir_all(&module).unwrap();
|
||||
for name in ["api", "app", "job", "model"] {
|
||||
std::fs::copy(
|
||||
source_root.join(format!("{name}.dever")),
|
||||
module.join(format!("{name}.dever")),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
std::fs::create_dir_all(directory.0.join("test/demo/queue")).unwrap();
|
||||
std::fs::write(
|
||||
directory.0.join("test/demo/queue/idle.dever"),
|
||||
"idle() () { assertEq(app.count(), 0) }\n",
|
||||
)
|
||||
.unwrap();
|
||||
for topic in ["first", "second"] {
|
||||
let body = "assertEq(app.count(), 0)\nfirst = app.submit(\"ok\", \"one\")\nsecond = app.submit(\"fail\", \"bad\")\nassert(first != second)\nassertEq(dever.test.drainJobs(10), 2)\nassertEq(app.count(), 1)\nassertEq(dever.test.drainJobs(10), 0)\nbefore = dever.time.now()\ndever.test.advanceClock(1000)\nassert(dever.time.now() != before)\nassertEq(dever.test.drainJobs(10), 1)\nassertEq(app.count(), 1)";
|
||||
let text = format!("{topic}() () {{\n{body}\n}}\n");
|
||||
std::fs::write(
|
||||
directory.0.join(format!("test/demo/queue/{topic}.dever")),
|
||||
text,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
let suite = Suite::load(&directory.0).unwrap();
|
||||
let report = testing::run(&suite, None, Duration::from_secs(10)).unwrap();
|
||||
for case in &report.cases {
|
||||
assert!(case.success, "{}: {}", case.name, case.stderr);
|
||||
}
|
||||
assert_eq!(report.cases.len(), 3);
|
||||
assert!(!directory.0.join("data").exists());
|
||||
for topic in ["first", "idle", "second"] {
|
||||
std::fs::remove_file(directory.0.join(format!("test/demo/queue/{topic}.dever"))).unwrap();
|
||||
}
|
||||
let job = module.join("job.dever");
|
||||
let source = std::fs::read_to_string(&job).unwrap();
|
||||
std::fs::write(job, format!("{source}\njob scheduled() () retry(1) timeout(1000) {{ app.apply(app.Payload {{ name = \"scheduled\" }}) }}\n\nschedule scheduled = \"* * * * *\"\n")).unwrap();
|
||||
let app = module.join("app.dever");
|
||||
let source = std::fs::read_to_string(&app).unwrap();
|
||||
std::fs::write(app, format!("{source}\nsubmitEmpty(key: Text) (id: Id) {{ id = dever.job.enqueue(job.scheduled, key) }}\n")).unwrap();
|
||||
std::fs::write(directory.0.join("test/demo/queue/cron.dever"), "cron() () {\n assertEq(dever.test.drainJobs(1), 1)\n assertEq(dever.test.drainJobs(1), 0)\n dever.test.advanceClock(60000)\n assertEq(dever.test.drainJobs(1), 1)\n assertEq(app.count(), 2)\n dever.test.advanceClock(180000)\n assertEq(dever.test.drainJobs(2), 2)\n assertEq(dever.test.drainJobs(2), 1)\n assertEq(dever.test.drainJobs(2), 0)\n assertEq(app.count(), 5)\n first = app.submitEmpty(\"manual\")\n second = app.submitEmpty(\"manual\")\n assertEq(first, second)\n assertEq(dever.test.drainJobs(2), 1)\n assertEq(app.count(), 6)\n}\n").unwrap();
|
||||
let suite = Suite::load(&directory.0).unwrap();
|
||||
let report = testing::run(&suite, None, Duration::from_secs(10)).unwrap();
|
||||
assert!(report.success(), "{}", report.cases[0].stderr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_test_jobs_from_protected_http_application_use_system_identity() {
|
||||
let directory = Directory::new();
|
||||
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../test/rust/fixtures/auth/module");
|
||||
let mut pending = vec![fixture.clone()];
|
||||
while let Some(source) = pending.pop() {
|
||||
let destination = directory
|
||||
.0
|
||||
.join("module")
|
||||
.join(source.strip_prefix(&fixture).unwrap());
|
||||
std::fs::create_dir_all(&destination).unwrap();
|
||||
for entry in std::fs::read_dir(source).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
if entry.file_type().unwrap().is_dir() {
|
||||
pending.push(entry.path());
|
||||
} else {
|
||||
std::fs::copy(entry.path(), destination.join(entry.file_name())).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
let domain = directory.0.join("module/demo/account");
|
||||
let jobs = domain.join("app/jobs.dever");
|
||||
let source = std::fs::read_to_string(&jobs).unwrap();
|
||||
std::fs::write(
|
||||
&jobs,
|
||||
source.replace("seed(input.subject,", "seed(port.identity(input.subject),"),
|
||||
)
|
||||
.unwrap();
|
||||
std::fs::write(
|
||||
domain.join("port.dever"),
|
||||
"identity(value: Text) (answer: Text) fails dever.api.Error\n",
|
||||
)
|
||||
.unwrap();
|
||||
let implementation = "port.identity(value: Text) (answer: Text) { answer = value }\n";
|
||||
std::fs::write(domain.join("adapter.dever"), implementation).unwrap();
|
||||
let production = dever::compiler::project::Project::load(&directory.0)
|
||||
.unwrap()
|
||||
.check()
|
||||
.unwrap();
|
||||
assert!(production.has_roles());
|
||||
assert!(!production.jobs.is_empty());
|
||||
std::fs::create_dir_all(directory.0.join("test/demo/account")).unwrap();
|
||||
std::fs::write(directory.0.join("test/demo/account/jobs.dever"), "jobs() () {\n assertEq(app.jobCount(), 0)\n first = app.jobSubmit(\"from-test\", \"once\")\n second = app.jobSubmit(\"from-test\", \"once\")\n assertEq(first, second)\n assertEq(dever.test.drainJobs(3), 1)\n assertEq(app.jobCount(), 1)\n}\n").unwrap();
|
||||
let test = directory.0.join("test/demo/account/jobs.dever");
|
||||
let source = std::fs::read_to_string(&test).unwrap().replace("assertEq(app.jobCount(), 1)", "assertEq(app.jobCount(), 1)\n verified = app.verify(dever.auth.Claims { subject = \"fake-from-test\", session = \"from-job\", site = \"unused\", tenant = null })\n assertEq(verified.id, \"fake-from-test\")");
|
||||
std::fs::write(
|
||||
test,
|
||||
format!(
|
||||
"{source}\n{}",
|
||||
implementation.replace("answer = value", "answer = \"fake-\" + value")
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
let suite = Suite::load(&directory.0).unwrap();
|
||||
let report = testing::run(&suite, None, Duration::from_secs(10)).unwrap();
|
||||
assert!(report.success(), "{}", report.cases[0].stderr);
|
||||
assert!(!directory.0.join("data").exists());
|
||||
}
|
||||
Reference in new issue
Block a user