//! Actual default CLI boundary. Native cases consume an explicitly prepared //! runtime archive; ordinary negative checks never start a compiler subprocess. #![cfg(all(target_os = "linux", target_arch = "x86_64"))] use std::fs; use std::path::{Path, PathBuf}; use std::process::{Command, Output, Stdio}; use std::time::Duration; use serde_json::{Value, json}; #[path = "support/llvm_pack.rs"] mod pack; #[path = "../dever-tests/tests/support/process.rs"] mod process; #[path = "../dever-tests/tests/support/temp.rs"] mod temp; use temp::TemporaryDirectory; const APP: &str = r#"type Outcome { error Denied(message: Text, labels: List) } echo(name: Text) (value: Text) { dever.task.sleep(1) value = name } divide(divisor: Int) (value: Int) { value = 42 // divisor } deny() (value: Text) { fail(Outcome.Denied("owned denial", ["nested label", null])) } "#; fn write(root: &Path, relative: &str, source: &str) { let path = root.join(relative); fs::create_dir_all(path.parent().unwrap()).unwrap(); fs::write(path, source).unwrap(); } fn project(root: &Path) { write(root, "module/sample/echo/app.dever", APP); write( root, "module/sample/echo/api.dever", "cmd echo = app.echo\ncmd divide = app.divide\ncmd deny = app.deny", ); write(root, "config/setting.json", "{}"); } fn compiler(root: &Path, name: &str) -> PathBuf { pack::compiler(Path::new(env!("CARGO_BIN_EXE_dever")), root, name) } fn output(command: &mut Command) -> Output { let capture = TemporaryDirectory::new(); let stdout = capture.path().join("stdout"); let stderr = capture.path().join("stderr"); command .stdout(Stdio::from(fs::File::create(&stdout).unwrap())) .stderr(Stdio::from(fs::File::create(&stderr).unwrap())); let status = process::status(command, Duration::from_secs(180)).unwrap(); Output { status, stdout: fs::read(stdout).unwrap(), stderr: fs::read(stderr).unwrap(), } } fn cli(compiler: &Path, project: &Path, action: &str, args: &[&str]) -> Output { output( Command::new(compiler) .env_clear() .arg(action) .arg(project) .args(args), ) } fn success(output: &Output) { assert!( output.status.success(), "stdout: {}\nstderr: {}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) ); } fn failure(output: &Output, expected: &str) { assert!(!output.status.success(), "unexpected success"); let message = format!( "{}{}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) ); assert!( message.contains(expected), "expected {expected:?}: {message}" ); } #[test] fn missing_pack_fails_without_host_tools_and_check_and_empty_test_need_no_pack() { let toolchain = TemporaryDirectory::new(); let compiler = compiler(toolchain.path(), "dever"); let help = output(Command::new(&compiler).env_clear().arg("--help")); success(&help); assert!(String::from_utf8_lossy(&help.stdout).contains("dever run ")); let project_dir = TemporaryDirectory::new(); project(project_dir.path()); success(&cli(&compiler, project_dir.path(), "check", &[])); let tests = cli(&compiler, project_dir.path(), "test", &[]); success(&tests); assert!(String::from_utf8_lossy(&tests.stdout).contains("0 passed; 0 failed")); failure( &cli( &compiler, project_dir.path(), "run", &["--", "sample.echo.echo", "{\"name\":\"Ada\"}"], ), "runtime 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}"); } assert_eq!(diagnostic.matches("error[C016]").count(), 3, "{diagnostic}"); assert!(!diagnostic.contains("warning[W005]"), "{diagnostic}"); assert!( diagnostic.contains("sample/profile/domain.dever:1:1: note:"), "{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_proven_duplicates_before_runtime_or_database_access() { let toolchain = TemporaryDirectory::new(); let compiler = compiler(toolchain.path(), "dever"); for (domain, app, diagnostic, location, warning) in [ ( "first(value: Int) (answer: Int) pure { answer = value + 1 }\nsecond(renamed: Int) (answer: Int) pure { answer = renamed + 1 }", "total(value: Int) (answer: Int) { first = domain.first(value)\nsecond = domain.second(value)\nanswer = first + second }", "same implementation and contract", "sample/count/domain.dever:2:1: error[C016]", "warning[W003]", ), ( "calculate(value: Int) (answer: Int) pure { answer = value + 1 }", "total(value: Int) (answer: Int) { first = domain.calculate(value)\nsecond = domain.calculate(value)\nanswer = first + second }", "earlier result is still available", "sample/count/app.dever:2:", "warning[W004]", ), ] { let project_dir = TemporaryDirectory::new(); let root = project_dir.path(); write(root, "config/setting.json", "{}"); write(root, "module/sample/count/model.dever", "type Count { value: Int }"); write(root, "module/sample/count/domain.dever", domain); write(root, "module/sample/count/app.dever", app); write(root, "module/sample/count/api.dever", "cmd total = app.total"); let checked = cli(&compiler, root, "check", &[]); for expected in ["C016", diagnostic, location, "previous implementation or computation"] { failure(&checked, expected); } let stderr = String::from_utf8_lossy(&checked.stderr); assert_eq!(stderr.matches("error[C016]").count(), 1, "{stderr}"); assert!(!stderr.contains(warning), "{stderr}"); assert_eq!( stderr.matches("note: previous implementation or computation").count(), 1, "{stderr}" ); let executable = root.join("must-not-build"); for (action, arguments) in [ ("run", vec!["--", "sample.count.total", "{\"value\":1}"]), ("build", vec!["--output", executable.to_str().unwrap()]), ] { let rejected = cli(&compiler, root, action, &arguments); assert!(!rejected.status.success()); assert_eq!(rejected.stderr, checked.stderr); } let snapshot = cli(&compiler, root, "api", &[]); success(&snapshot); let stderr = String::from_utf8_lossy(&snapshot.stderr); assert!(stderr.contains(warning), "{stderr}"); assert!(!stderr.contains("C016"), "{stderr}"); write( root, "test/sample/count/value.dever", "value() () { assert_eq(app.total(1), 4) }", ); let tests = cli(&compiler, root, "test", &[]); failure(&tests, "runtime pack"); let stderr = String::from_utf8_lossy(&tests.stderr); assert!(stderr.contains(warning), "{stderr}"); assert!(!stderr.contains("C016"), "{stderr}"); write(root, "dever.api", "invalid baseline"); let baseline = cli(&compiler, root, "check", &[]); failure(&baseline, "invalid API baseline"); let stderr = String::from_utf8_lossy(&baseline.stderr); assert!(stderr.contains(warning), "{stderr}"); assert!(!stderr.contains("C016"), "{stderr}"); assert!(!executable.exists()); assert!(!root.join("data").exists()); } assert!(!toolchain.path().join("cache").exists()); } #[test] fn application_check_keeps_warnings_with_distinct_evidence_and_other_codes() { 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/count/domain.dever", "orphan(value: Int) (answer: Int) pure { answer = value + 1 }\nfirst(value: Int) (answer: Int) pure { answer = value + 1 }\nsecond(value: Int) (answer: Int) pure { answer = value + 1 }", ); write( root, "module/sample/count/app.dever", "total(value: Int) (answer: Int) { unused = 0\nfirst = domain.first(value)\nsecond = domain.second(value)\nanswer = first + second }", ); write(root, "module/sample/count/api.dever", "cmd total = app.total"); let checked = cli(&compiler, root, "check", &[]); failure(&checked, "C016"); let stderr = String::from_utf8_lossy(&checked.stderr); assert_eq!(stderr.matches("error[C016]").count(), 2, "{stderr}"); // Warning proof includes the orphan; production proof starts at the first reachable function. assert_eq!(stderr.matches("warning[W003]").count(), 2, "{stderr}"); assert_eq!(stderr.matches("warning[W001]").count(), 1, "{stderr}"); assert_eq!( stderr.matches("sample/count/domain.dever:1:1: note:").count(), 2, "{stderr}" ); assert_eq!( stderr.matches("sample/count/domain.dever:2:1: note:").count(), 1, "{stderr}" ); 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 test_filter_still_checks_unselected_sources_and_reports_no_match_before_compilation() { let toolchain = TemporaryDirectory::new(); let compiler = compiler(toolchain.path(), "dever"); let project_dir = TemporaryDirectory::new(); let root = project_dir.path(); project(root); write(root, "test/sample/echo/first.dever", "first() () { assert_eq(1, 1) }"); write(root, "test/sample/echo/second.dever", "second() () { assert_eq(1, 1) }"); for filter in ["sam", "sample.ec", "sample.echo.fir", "sample.echo.missing"] { let result = cli(&compiler, root, "test", &["--filter", filter]); failure(&result, "no tests match filter"); assert!(!String::from_utf8_lossy(&result.stdout).contains("runtime pack")); } let selected = cli(&compiler, root, "test", &["--filter", "sample.echo.second"]); failure(&selected, "runtime pack"); let report = String::from_utf8_lossy(&selected.stdout); assert!(report.contains("0 passed; 1 failed; 1 not run"), "{report}"); assert!(!report.contains("test sample/echo/first ..."), "{report}"); write(root, "test/sample/echo/first.dever", "first() () { missing() }"); failure(&cli(&compiler, root, "test", &["--filter", "sample.echo.second"]), "C004"); assert!(!toolchain.path().join("cache").exists()); } #[test] #[ignore = "requires an explicitly prepared runtime pack adjacent to CARGO_BIN_EXE_dever; owned short-lived cases only"] fn llvm_test_controls_preserve_indices_prefix_boundaries_and_timeout_continuation() { let compiler = Path::new(env!("CARGO_BIN_EXE_dever")); let directory = TemporaryDirectory::new(); let root = directory.path(); project(root); for (relative, source) in [ ("test/sample/echo/alpha.dever", "alpha() () { assert_eq(1, 2) }"), ("test/sample/echo/beta.dever", "beta() () { assert_eq(7, 7) }"), ("test/sample/echo/slow.dever", "slow() () { dever.task.sleep(30000) }"), ("test/sample/echo/zeta.dever", "zeta() () { assert_eq(9, 9) }"), ("test/sample/echoes/first.dever", "first() () { assert_eq(1, 2) }"), ("test/samples/echo/first.dever", "first() () { assert_eq(1, 2) }"), ] { write(root, relative, source); } let selected = cli(compiler, root, "test", &["--filter", "sample.echo.beta"]); success(&selected); let report = String::from_utf8_lossy(&selected.stdout); assert!(report.contains("test sample/echo/beta ... ok"), "{report}"); assert!(report.contains("1 passed; 0 failed; 5 not run"), "{report}"); assert!(!report.contains("sample/echo/alpha"), "{report}"); for (filter, counts) in [ ("sample.echo", "2 passed; 2 failed; 2 not run"), ("sample", "2 passed; 3 failed; 1 not run"), ] { let selected = cli(compiler, root, "test", &["--filter", filter, "--timeout-ms", "250"]); failure(&selected, counts); let report = String::from_utf8_lossy(&selected.stdout); assert!(report.contains("test process timed out after 250 ms"), "{report}"); assert!(report.contains("test sample/echo/zeta ... ok"), "{report}"); assert!(!report.contains("test samples/echo/first"), "{report}"); } failure(&cli(compiler, root, "test", &["--filter", "sample.echo.bet"]), "no tests match filter"); } #[test] fn cross_build_requires_the_selected_pack_even_when_host_pack_exists() { let root = TemporaryDirectory::new(); let compiler = compiler(root.path(), "dever"); pack::manifest(root.path(), false); let project_dir = TemporaryDirectory::new(); project(project_dir.path()); let executable = root.path().join("arm-application"); failure( &cli( &compiler, project_dir.path(), "build", &[ "--target", "linux-aarch64", "--output", executable.to_str().unwrap(), ], ), "runtime/linux-aarch64", ); assert!(!executable.exists()); assert!(!root.path().join("cache").exists()); } #[test] fn managed_cores_never_fall_back_to_private_packs_or_cache() { let root = TemporaryDirectory::new(); let project_dir = TemporaryDirectory::new(); project(project_dir.path()); for (directory, diagnostic) in [ ( root.path().join("standalone"), "managed compiler must be installed", ), ( root.path().join("versions/0.1.0"), "cannot inspect deverd state directory", ), ] { let compiler = compiler(&directory, "dever-core"); failure(&cli(&compiler, project_dir.path(), "run", &[]), diagnostic); assert!(!directory.join("cache").exists()); } let installed = root.path().join("versions/0.1.0/dever-core"); let worker = output( Command::new(installed) .env_clear() .current_dir(project_dir.path()) .arg("--dever-compile-worker"), ); failure(&worker, "service-owned native cache staging directory"); assert!(!project_dir.path().join("program").exists()); } #[test] fn pack_contract_rejects_drift_duplicate_paths_and_undeclared_link_inputs() { let root = TemporaryDirectory::new(); let compiler = compiler(root.path(), "dever"); let original = pack::manifest(root.path(), false); let project_dir = TemporaryDirectory::new(); project(project_dir.path()); let mutations: &[(&str, Value, &str)] = &[ ("/format", json!("future"), "does not match"), ("/compiler_version", json!("99.0.0"), "does not match"), ("/abi", json!(99), "does not match"), ( "/target", json!("aarch64-unknown-linux-gnu"), "does not match", ), ("/profiles/base", Value::Null, "missing the required 'base'"), ( "/profiles/base/runtime", json!("missing.a"), "undeclared or duplicate", ), ( "/profiles/base/libraries/0", json!("runtime.a"), "undeclared or duplicate", ), ( "/files/0/path", json!("../runtime.a"), "normalized relative", ), ( "/files/0/path", json!("nested//runtime.a"), "normalized relative", ), ( "/files/0/path", json!("C:\\runtime.a"), "normalized relative", ), ( "/files/1/path", json!("runtime.a"), "duplicate runtime file", ), ("/files/0/bytes", json!(9), "SHA-256 verification"), ( "/files/0/sha256", json!("0".repeat(64)), "SHA-256 verification", ), ]; for (pointer, value, expected) in mutations { let mut manifest = original.clone(); *manifest.pointer_mut(pointer).unwrap() = value.clone(); pack::write_manifest(root.path(), &manifest); failure(&cli(&compiler, project_dir.path(), "run", &[]), expected); assert!(!root.path().join("cache").exists()); } let text = serde_json::to_string(&original).unwrap(); fs::write( pack::pack_root(root.path()).join("manifest.json"), text.replacen("\"abi\":1", "\"abi\":1,\"abi\":1", 1), ) .unwrap(); failure( &cli(&compiler, project_dir.path(), "run", &[]), "duplicate field", ); } #[test] fn pack_rejects_symlinks_and_disguised_linker_scripts() { use std::os::unix::fs::symlink; let root = TemporaryDirectory::new(); let compiler = compiler(root.path(), "dever"); let mut manifest = pack::manifest(root.path(), false); let project_dir = TemporaryDirectory::new(); project(project_dir.path()); let runtime = pack::pack_root(root.path()).join("runtime.a"); let outside = root.path().join("outside.a"); fs::rename(&runtime, &outside).unwrap(); symlink(&outside, &runtime).unwrap(); failure( &cli(&compiler, project_dir.path(), "run", &[]), "must be a real file", ); fs::remove_file(&runtime).unwrap(); for content in [b"INPUT(/usr/lib/forbidden.a)".as_slice(), b"!\n"] { fs::write(&runtime, content).unwrap(); manifest["files"][0]["bytes"] = json!(content.len()); manifest["files"][0]["sha256"] = json!(pack::digest(&runtime)); pack::write_manifest(root.path(), &manifest); failure( &cli(&compiler, project_dir.path(), "run", &[]), "linker scripts and thin archives are forbidden", ); } assert!(!root.path().join("cache").exists()); } fn cached_programs(root: &Path) -> Vec { let mut programs = fs::read_dir(root.join("cache/native")) .unwrap() .map(|entry| entry.unwrap().path().join("program")) .filter(|path| path.is_file()) .collect::>(); programs.sort(); programs } #[test] #[ignore = "requires explicitly prepared full runtime archive and Linux CRT files; serial offline CLI execution"] fn default_llvm_runs_builds_moves_and_reuses_cache_without_host_toolchain() { let root = TemporaryDirectory::new(); let compiler = compiler(root.path(), "dever"); pack::manifest(root.path(), true); let project_dir = TemporaryDirectory::new(); project(project_dir.path()); let args = ["--", "sample.echo.echo", "{\"name\":\"你好 LLVM\"}"]; let marker = root.path().join("forbidden-tool-was-started"); for name in ["rustc", "cargo", "cc"] { use std::os::unix::fs::PermissionsExt; let path = root.path().join(name); fs::write( &path, format!( "#!/bin/sh\nprintf forbidden > '{}'\nexit 99\n", marker.display() ), ) .unwrap(); fs::set_permissions(path, fs::Permissions::from_mode(0o700)).unwrap(); } let run = output( Command::new(&compiler) .env_clear() .env("PATH", root.path()) .env("RUSTC", root.path().join("rustc")) .env("CARGO", root.path().join("cargo")) .env("CC", root.path().join("cc")) .arg("run") .arg(project_dir.path()) .args(args), ); success(&run); assert!( !marker.exists(), "LLVM compilation must not start a host compiler" ); let response: Value = serde_json::from_slice(&run.stdout).unwrap(); assert_eq!(response["data"], "你好 LLVM"); let cached = cached_programs(root.path()); assert_eq!(cached.len(), 1); let modified = fs::metadata(&cached[0]).unwrap().modified().unwrap(); let binary = project_dir.path().join("standalone"); success(&cli( &compiler, project_dir.path(), "build", &["--output", binary.to_str().unwrap()], )); assert_eq!( fs::metadata(&cached[0]).unwrap().modified().unwrap(), modified ); let first_digest = pack::digest(&binary); failure( &cli( &compiler, project_dir.path(), "build", &["--output", binary.to_str().unwrap()], ), "cannot save executable", ); assert_eq!(pack::digest(&binary), first_digest); let moved = TemporaryDirectory::new(); let relocated = moved.path().join("application"); fs::rename(&binary, &relocated).unwrap(); write(moved.path(), "config/setting.json", "{}"); success(&output( Command::new(&relocated).env_clear().args(&args[1..]), )); { use std::os::fd::OwnedFd; use std::os::unix::net::UnixStream; let (reader, writer) = UnixStream::pair().unwrap(); drop(reader); let stderr = moved.path().join("closed-stdout-error"); let status = process::status( Command::new(&relocated) .env_clear() .args(&args[1..]) .stdout(Stdio::from(OwnedFd::from(writer))) .stderr(Stdio::from(fs::File::create(&stderr).unwrap())), Duration::from_secs(30), ) .unwrap(); assert_eq!( status.code(), Some(1), "closed stdout must return a checked failure, not SIGPIPE" ); let error = fs::read_to_string(stderr).unwrap(); assert!( error.contains("Broken pipe") && error.contains(".dever"), "{error}" ); } let denied = cli( &compiler, project_dir.path(), "run", &["--", "sample.echo.deny", "{}"], ); failure(&denied, "owned denial"); assert!(String::from_utf8_lossy(&denied.stderr).contains("nested label")); assert!(String::from_utf8_lossy(&denied.stderr).contains("app.dever")); failure( &cli( &compiler, project_dir.path(), "run", &["--", "sample.echo.echo", "{}"], ), "name", ); let failure_output = cli( &compiler, project_dir.path(), "run", &["--", "sample.echo.divide", "{\"divisor\":0}"], ); failure(&failure_output, "division"); assert!(String::from_utf8_lossy(&failure_output.stderr).contains("app.dever")); // Corrupt only the fixture-owned cache copy, never a hard-linked runtime. fs::write(&cached[0], b"corrupt cached executable").unwrap(); failure( &cli(&compiler, project_dir.path(), "run", &args), "incomplete or modified", ); assert_eq!(fs::read(&cached[0]).unwrap(), b"corrupt cached executable"); write( project_dir.path(), "module/sample/echo/app.dever", &APP.replace("value = name", "value = name + \"!\""), ); let changed = cli(&compiler, project_dir.path(), "run", &args); success(&changed); assert_eq!( serde_json::from_slice::(&changed.stdout).unwrap()["data"], "你好 LLVM!" ); assert_eq!(cached_programs(root.path()).len(), 2); // Even a byte-level manifest change has its own exact pack identity. let manifest = pack::pack_root(root.path()).join("manifest.json"); let mut bytes = fs::read(&manifest).unwrap(); bytes.push(b'\n'); fs::write(&manifest, bytes).unwrap(); success(&cli(&compiler, project_dir.path(), "run", &args)); assert_eq!(cached_programs(root.path()).len(), 3); let markdown = TemporaryDirectory::new(); write(markdown.path(), "config/setting.json", "{}"); write( markdown.path(), "module/system/health/app.dever.md", include_str!("../../examples/cms/md/module/system/health/app.dever.md"), ); write( markdown.path(), "module/system/health/api.dever.md", include_str!("../../examples/cms/md/module/system/health/api.dever.md"), ); let response = cli( &compiler, markdown.path(), "run", &["--", "system.health.ping", "{}"], ); success(&response); assert_eq!( serde_json::from_slice::(&response.stdout).unwrap()["data"]["status"], "ready" ); assert_eq!(cached_programs(root.path()).len(), 4); let object = pack::pack_root(root.path()).join("crt1.o"); let mut corrupt = fs::read(&object).unwrap(); corrupt[16] ^= 1; // Break this fixture's hard link before mutation; the host CRT is immutable. fs::remove_file(&object).unwrap(); fs::write(&object, corrupt).unwrap(); failure( &cli(&compiler, project_dir.path(), "run", &args), "SHA-256 verification", ); assert_eq!(cached_programs(root.path()).len(), 4); } #[test] #[ignore = "requires explicitly prepared runtime archive and component-fixture; no downloads or language SDK"] fn unsigned_llvm_run_and_build_reject_exec_workers() { let root = TemporaryDirectory::new(); let compiler = compiler(root.path(), "dever"); pack::manifest(root.path(), true); let project_dir = TemporaryDirectory::new(); let directory = project_dir.path(); write( directory, "module/notification/delivery/app.dever", "read() (value: Int) { value = port.read() }", ); write( directory, "module/notification/delivery/port.dever", "read() (value: Int) fails dever.time.SleepResult", ); write( directory, "module/notification/delivery/adapter.dever", "setting { mode: Text }\nexternal exec \"worker\" {}", ); write( directory, "module/notification/delivery/api.dever", "cmd read = app.read", ); let settings = r#"{"adapter":{"notification.delivery":{"setting":{"mode":"embedded"}}}}"#; write(directory, "config/setting.json", settings); let fixture = Path::new(env!("CARGO_MANIFEST_DIR")) .join("../../target/native-release-inputs/component-fixture"); assert!( fixture.is_file(), "prepare the immutable component-fixture in target/native-release-inputs" ); fs::copy( fixture, directory.join("module/notification/delivery/worker"), ) .unwrap(); let args = ["--", "notification.delivery.read", "{}"]; let run = cli(&compiler, directory, "run", &args); let binary = directory.join("packaged"); let build = cli( &compiler, directory, "build", &["--output", binary.to_str().unwrap()], ); for rejected in [run, build] { assert!(!rejected.status.success()); assert!(rejected.stdout.is_empty()); assert_eq!( String::from_utf8(rejected.stderr).unwrap().trim(), "real Lib providers require a signed managed Dever release" ); } assert!(!binary.exists()); assert!(!root.path().join("cache").exists()); } #[test] #[ignore = "requires explicitly prepared full runtime archive; isolated SQLite and case-local fake processes"] fn llvm_test_preserves_case_isolation_fakes_and_failure_continuation() { let root = TemporaryDirectory::new(); let compiler = compiler(root.path(), "dever"); pack::manifest(root.path(), true); let project_dir = TemporaryDirectory::new(); let directory = project_dir.path(); write( directory, "module/sample/account/model.dever", "type Account { name: Text }", ); write( directory, "module/sample/account/app.dever", "create(name: Text) (count: Int) { row = model.create({ name = name })\n count = model.count() }", ); write( directory, "module/notification/mail/app.dever", "read() (value: Int) { value = port.read() }", ); write( directory, "module/notification/mail/port.dever", "read() (value: Int) fails dever.time.SleepResult", ); write( directory, "module/notification/mail/adapter.dever", "external exec \"worker/must-not-start\" {}", ); write( directory, "config/setting.json", r#"{"database":{"default":{"type":"sqlite","path":"deployment.db"}}}"#, ); // The CLI reads project dependency metadata from valid JSON, but test // execution must use its generated database, never this unusable directory. fs::create_dir(directory.join("deployment.db")).unwrap(); for name in ["first", "second"] { write( directory, &format!("test/sample/account/{name}.dever"), &format!("{name}() () {{ assert_eq(app.create(\"Ada\"), 1) }}"), ); } write( directory, "test/notification/mail/blocking.dever", "blocking() () { dever.time.sleep(1)\n assert_eq(app.read(), 11) }\nport.read() (value: Int) { value = 11 }", ); write( directory, "test/notification/mail/suspending.dever", "suspending() () { assert_eq(app.read(), 23) }\nport.read() (value: Int) { dever.task.sleep(1)\n value = 23 }", ); write( directory, "test/sample/account/failure.dever", "failure() () { assert_eq(1, 2) }", ); write( directory, "test/sample/account/business.dever", "type Outcome { error Denied(message: Text) }\nbusiness() () { fail(Outcome.Denied(\"test business denial\")) }", ); let result = cli(&compiler, directory, "test", &[]); failure(&result, "4 passed; 2 failed; 0 not run"); let report = String::from_utf8_lossy(&result.stdout); assert!( report.contains("test sample/account/second ... ok"), "{report}" ); assert!(report.contains("failure.dever"), "{report}"); assert!( report.contains("assert_eq failed: actual = 1, expected = 2"), "{report}" ); assert!(report.contains("test business denial"), "{report}"); assert!(!directory.join("data").exists()); assert_eq!(cached_programs(root.path()).len(), 1); let suite = &cached_programs(root.path())[0]; for args in [ vec![], vec![""], vec!["-1"], vec!["+0"], vec![" 0"], vec!["0 "], vec!["18446744073709551616"], vec!["6"], vec!["0", "extra"], ] { let rejected = output(Command::new(suite).env_clear().args(args)); failure(&rejected, "test program requires a valid case index"); assert_eq!( rejected.stderr, b"test program requires a valid case index\n" ); } }