feat: unify source and skills for Dever 0.1.2 installation
This commit is contained in:
1 parent
a2e0436ed4
commit
19f122b4b4
47 files changed
+1951
-63
No files matched your search
@@ -116,6 +116,193 @@ fn missing_pack_fails_without_host_tools_and_check_and_empty_test_need_no_pack()
|
||||
assert!(!toolchain.path().join("cache").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn application_check_rejects_redundancy_before_preparing_build_resources() {
|
||||
let toolchain = TemporaryDirectory::new();
|
||||
let compiler = compiler(toolchain.path(), "dever");
|
||||
let project_dir = TemporaryDirectory::new();
|
||||
let root = project_dir.path();
|
||||
write(root, "config/setting.json", "{}");
|
||||
write(
|
||||
root,
|
||||
"module/sample/profile/domain.dever",
|
||||
"normalize(value: Text) (result: Text) pure { result = text.trim(value) }\nalias(value: Text) (result: Text) pure { result = normalize(value) }\norphan() (result: Text) pure { result = \"unused\" }",
|
||||
);
|
||||
write(
|
||||
root,
|
||||
"module/sample/profile/app.dever",
|
||||
"greet(value: Text) (result: Text) { result = domain.alias(value) }\nunused() (result: Text) { result = \"unused\" }",
|
||||
);
|
||||
write(
|
||||
root,
|
||||
"module/sample/profile/api.dever",
|
||||
"cmd greet = app.greet",
|
||||
);
|
||||
let checked = cli(&compiler, root, "check", &[]);
|
||||
failure(&checked, "C016");
|
||||
let diagnostic = String::from_utf8_lossy(&checked.stderr);
|
||||
for expected in [
|
||||
"sample/profile/app.dever:2:1: error[C016]",
|
||||
"function 'sample.profile.unused' is unreachable",
|
||||
"sample/profile/domain.dever:2:1: error[C016]",
|
||||
"only forwards unchanged inputs",
|
||||
"sample/profile/domain.dever:3:1: error[C016]",
|
||||
"function 'sample.profile.domain.orphan' is unreachable",
|
||||
] {
|
||||
assert!(diagnostic.contains(expected), "{expected}: {diagnostic}");
|
||||
}
|
||||
// The same source fails before native packs, output publication or execution.
|
||||
let executable = root.join("must-not-build");
|
||||
for (action, args) in [
|
||||
("run", Vec::new()),
|
||||
("build", vec!["--output", executable.to_str().unwrap()]),
|
||||
] {
|
||||
let rejected = cli(&compiler, root, action, &args);
|
||||
assert!(!rejected.status.success());
|
||||
assert_eq!(rejected.stderr, checked.stderr);
|
||||
}
|
||||
assert!(!executable.exists());
|
||||
assert!(!root.join("data").exists());
|
||||
assert!(!toolchain.path().join("cache").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn application_check_rejects_plain_model_get_wrapper_without_opening_database() {
|
||||
let toolchain = TemporaryDirectory::new();
|
||||
let compiler = compiler(toolchain.path(), "dever");
|
||||
let project_dir = TemporaryDirectory::new();
|
||||
let root = project_dir.path();
|
||||
write(root, "config/setting.json", "{}");
|
||||
write(
|
||||
root,
|
||||
"module/sample/profile/model.dever",
|
||||
"type Profile { name: Text }",
|
||||
);
|
||||
write(
|
||||
root,
|
||||
"module/sample/profile/app.dever",
|
||||
"lookup(id: model.id) (profile: model.Profile) { profile = model.get(id) }",
|
||||
);
|
||||
write(
|
||||
root,
|
||||
"module/sample/profile/api.dever",
|
||||
"cmd lookup = app.lookup",
|
||||
);
|
||||
let rejected = cli(&compiler, root, "check", &[]);
|
||||
failure(&rejected, "C016");
|
||||
let diagnostic = String::from_utf8_lossy(&rejected.stderr);
|
||||
for expected in [
|
||||
"sample/profile/app.dever:1:1: error[C016]",
|
||||
"only forwards a Model CRUD operation; declare rest for plain CRUD",
|
||||
] {
|
||||
assert!(diagnostic.contains(expected), "{expected}: {diagnostic}");
|
||||
}
|
||||
assert!(!root.join("data").exists());
|
||||
assert!(!toolchain.path().join("cache").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn application_check_accepts_both_templates_and_preserves_markdown_locations() {
|
||||
let toolchain = TemporaryDirectory::new();
|
||||
let compiler = compiler(toolchain.path(), "dever");
|
||||
let workspace = TemporaryDirectory::new();
|
||||
for markdown in [false, true] {
|
||||
let root = workspace
|
||||
.path()
|
||||
.join(if markdown { "markdown" } else { "plain" });
|
||||
dever_cli::project::create(&root, markdown).unwrap();
|
||||
success(&cli(&compiler, &root, "check", &[]));
|
||||
// Removing the production declaration must not make the test a production root.
|
||||
let suffix = if markdown { "dever.md" } else { "dever" };
|
||||
fs::remove_file(root.join(format!("module/hello/greeting/api.{suffix}"))).unwrap();
|
||||
let rejected = cli(&compiler, &root, "check", &[]);
|
||||
failure(&rejected, "C016");
|
||||
failure(
|
||||
&rejected,
|
||||
"unreachable from API, CMD, or Job production entries",
|
||||
);
|
||||
let app = root.join(format!("module/hello/greeting/app.{suffix}"));
|
||||
let source = fs::read_to_string(&app).unwrap();
|
||||
let line = source
|
||||
.lines()
|
||||
.position(|line| line.starts_with("greet("))
|
||||
.unwrap()
|
||||
+ 1;
|
||||
failure(&rejected, &format!("hello/greeting/app.{suffix}:{line}:1"));
|
||||
assert!(!root.join("data").exists());
|
||||
}
|
||||
assert!(!toolchain.path().join("cache").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn application_check_needs_no_database_worker_or_runtime_pack() {
|
||||
let toolchain = TemporaryDirectory::new();
|
||||
let compiler = compiler(toolchain.path(), "dever");
|
||||
let project_dir = TemporaryDirectory::new();
|
||||
let root = project_dir.path();
|
||||
write(root, "config/setting.json", "{}");
|
||||
write(
|
||||
root,
|
||||
"module/sample/profile/model.dever",
|
||||
"type Profile { name: Text }",
|
||||
);
|
||||
// A model-only project has no App/Domain code to reject, even without entries.
|
||||
success(&cli(&compiler, root, "check", &[]));
|
||||
write(
|
||||
root,
|
||||
"module/notification/mail/app.dever",
|
||||
"read() (value: Int) { value = port.read() }",
|
||||
);
|
||||
write(
|
||||
root,
|
||||
"module/notification/mail/port.dever",
|
||||
"read() (value: Int) fails dever.time.SleepResult",
|
||||
);
|
||||
write(
|
||||
root,
|
||||
"module/notification/mail/adapter.dever",
|
||||
"external exec \"worker/must-not-start\" {}",
|
||||
);
|
||||
write(
|
||||
root,
|
||||
"module/notification/mail/api.dever",
|
||||
"cmd read = app.read",
|
||||
);
|
||||
success(&cli(&compiler, root, "check", &[]));
|
||||
assert!(!root.join("data").exists());
|
||||
assert!(!root.join("dever.lock").exists());
|
||||
assert!(!toolchain.path().join("cache").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn application_check_does_not_apply_production_reachability_to_tests() {
|
||||
let toolchain = TemporaryDirectory::new();
|
||||
let compiler = compiler(toolchain.path(), "dever");
|
||||
let project_dir = TemporaryDirectory::new();
|
||||
let root = project_dir.path();
|
||||
write(
|
||||
root,
|
||||
"module/sample/profile/app.dever",
|
||||
"greet() (value: Text) { value = \"hello\" }",
|
||||
);
|
||||
write(root, "config/setting.json", "{}");
|
||||
failure(&cli(&compiler, root, "check", &[]), "C016");
|
||||
let empty = cli(&compiler, root, "test", &[]);
|
||||
success(&empty);
|
||||
assert!(String::from_utf8_lossy(&empty.stdout).contains("0 passed; 0 failed"));
|
||||
write(
|
||||
root,
|
||||
"test/sample/profile/greeting.dever",
|
||||
"greeting() () { assert_eq(app.greet(), \"hello\") }",
|
||||
);
|
||||
// The real case reaches the test compiler's missing-pack boundary, not C016.
|
||||
let tests = cli(&compiler, root, "test", &[]);
|
||||
failure(&tests, "runtime pack");
|
||||
assert!(!String::from_utf8_lossy(&tests.stderr).contains("C016"));
|
||||
assert!(!String::from_utf8_lossy(&tests.stdout).contains("C016"));
|
||||
assert!(!toolchain.path().join("cache").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cross_build_requires_the_selected_pack_even_when_host_pack_exists() {
|
||||
let root = TemporaryDirectory::new();
|
||||
|
||||
@@ -108,22 +108,24 @@ fn signed_catalog_paths_and_total_size_are_bounded_before_extraction() {
|
||||
|
||||
#[test]
|
||||
fn official_redirects_reject_http_credentials_ports_and_foreign_hosts() {
|
||||
let current = "https://github.com/shemic/dever-main/releases/latest/download/a";
|
||||
let current = "https://git.dever.cc/shemic/dever/dist/latest/a";
|
||||
assert!(
|
||||
redirect(
|
||||
current,
|
||||
"https://release-assets.githubusercontent.com/asset?token=opaque"
|
||||
"https://git.dever.cc/shemic/dever/dist/1.2.3/a?token=opaque"
|
||||
)
|
||||
.is_ok()
|
||||
);
|
||||
assert!(redirect(current, "/shemic/dever-main/releases/download/v1.2.3/a").is_ok());
|
||||
assert!(redirect(current, "/shemic/dever/dist/1.2.3/a").is_ok());
|
||||
for address in [
|
||||
"http://github.com/a",
|
||||
"http://git.dever.cc/a",
|
||||
"https://evil.example/a",
|
||||
"https://github.com.evil.example/a",
|
||||
"https://user@github.com/a",
|
||||
"https://github.com:444/a",
|
||||
"https://github.com/a#fragment",
|
||||
"https://git.dever.cc.evil.example/a",
|
||||
"https://user@git.dever.cc/a",
|
||||
"https://git.dever.cc:444/a",
|
||||
"https://git.dever.cc/a#fragment",
|
||||
"https://github.com/a",
|
||||
"https://release-assets.githubusercontent.com/a",
|
||||
] {
|
||||
assert!(redirect(current, address).is_err(), "{address}");
|
||||
}
|
||||
@@ -135,14 +137,21 @@ fn official_asset_names_are_versioned_and_platform_specific() {
|
||||
assert_eq!(
|
||||
source.asset("latest", "manifest.json"),
|
||||
format!(
|
||||
"{OFFICIAL_RELEASES}/latest/download/dever-{}.manifest.json",
|
||||
"https://git.dever.cc/shemic/dever/dist/latest/dever-{}.manifest.json",
|
||||
platform_identity()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
source.asset("1.2.3", "tar.zst"),
|
||||
format!(
|
||||
"{OFFICIAL_RELEASES}/download/v1.2.3/dever-{}.tar.zst",
|
||||
"https://git.dever.cc/shemic/dever/dist/1.2.3/dever-{}.tar.zst",
|
||||
platform_identity()
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
source.asset("1.2.3", "ext-runtime-pip-linux-x86_64.tar.zst"),
|
||||
format!(
|
||||
"https://git.dever.cc/shemic/dever/dist/1.2.3/dever-{}.ext-runtime-pip-linux-x86_64.tar.zst",
|
||||
platform_identity()
|
||||
)
|
||||
);
|
||||
@@ -174,7 +183,7 @@ fn server(responses: Vec<Vec<u8>>) -> (ReleaseSource, std::thread::JoinHandle<()
|
||||
let base = format!("http://{}/releases", listener.local_addr().unwrap());
|
||||
let worker = std::thread::spawn(move || {
|
||||
let deadline = Instant::now() + Duration::from_secs(5);
|
||||
for response in responses {
|
||||
for (index, response) in responses.into_iter().enumerate() {
|
||||
let mut stream = loop {
|
||||
match listener.accept() {
|
||||
Ok((stream, _)) => break stream,
|
||||
@@ -194,7 +203,18 @@ fn server(responses: Vec<Vec<u8>>) -> (ReleaseSource, std::thread::JoinHandle<()
|
||||
let mut reader = io::BufReader::new(stream.try_clone().unwrap());
|
||||
let mut request = String::new();
|
||||
reader.read_line(&mut request).unwrap();
|
||||
assert!(request.starts_with("GET /releases/"));
|
||||
let (selection, suffix) = [
|
||||
("latest", "manifest.json"),
|
||||
("latest", "manifest.sig"),
|
||||
("1.2.3", "tar.zst"),
|
||||
][index];
|
||||
assert_eq!(
|
||||
request,
|
||||
format!(
|
||||
"GET /releases/{selection}/dever-{}.{suffix} HTTP/1.1\r\n",
|
||||
platform_identity()
|
||||
)
|
||||
);
|
||||
loop {
|
||||
let mut line = String::new();
|
||||
reader.read_line(&mut line).unwrap();
|
||||
|
||||
Reference in new issue
Block a user