use super::{Directory, fixture}; use dever::compiler::{build, project::Project}; use std::{path::Path, process::Command}; fn execute(binary: &Path, name: &str, input: serde_json::Value) -> std::process::Output { Command::new(binary) .args([format!("demo.value.{name}"), input.to_string()]) .output() .unwrap() } #[test] fn native_adapter_selection_settings_and_tasks_share_one_binary() { let directory = Directory::new(); let source = fixture().with_file_name("adapters"); let project = Project::load(&source).unwrap(); let program = project.check().unwrap(); let binary = build::build(&program, &project.sources, "main", &directory.0.join("app")).unwrap(); let original = std::fs::read(&binary).unwrap(); let config = directory.0.join("config"); std::fs::create_dir(&config).unwrap(); let path = config.join("setting.json"); let local = serde_json::json!({"adapter":{"demo.value":{"use":"local","setting":{"offset":1,"prefix":"local:","token":"key"}}}}); let remote = serde_json::json!({"adapter":{"demo.value":{"use":"remote"}}}); for (setting, number, prefix, sign) in [(&local, 42, "local:", 247), (&remote, 141, "remote:", 82)] { std::fs::write(&path, setting.to_string()).unwrap(); for name in [ "calculate", "worker", "nested", "mapped", "parallel", "scheduled", "cancelled", ] { let result = execute(&binary, name, serde_json::json!({"value":41})); assert!( result.status.success(), "{name}: {}", String::from_utf8_lossy(&result.stderr) ); let actual: serde_json::Value = serde_json::from_slice(&result.stdout).unwrap(); assert_eq!( actual["data"], if name == "mapped" { number * 2 } else { number } ); } for (name, value, expected) in [ ("echo", "雪", serde_json::json!(format!("{prefix}雪"))), ( "sign", "The quick brown fox jumps over the lazy dog", serde_json::json!(sign), ), ] { let result = execute(&binary, name, serde_json::json!({"value":value})); assert!( result.status.success(), "{name}: {}", String::from_utf8_lossy(&result.stderr) ); let actual: serde_json::Value = serde_json::from_slice(&result.stdout).unwrap(); assert_eq!(actual["data"], expected); } } for name in ["parallel", "scheduled"] { let result = execute(&binary, name, serde_json::json!({"value":-1})); assert!(!result.status.success()); assert!(result.stdout.is_empty()); let error = String::from_utf8_lossy(&result.stderr); assert!( error.contains("Problem.Missing") && error.contains("remote.dever:"), "{error}" ); } for setting in [ "{}", "null", "[]", "{", r#"{"adapter":{"demo.value":{"use":"absent"}}}"#, r#"{"adapter":{"demo.value":{"use":"local"}}}"#, r#"{"adapter":{"demo.value":{"use":"local","setting":{"offset":"fixture-only-secret","prefix":"x","token":"x"}}}}"#, r#"{"adapter":{"demo.value":{"use":"local","setting":[1,"x","fixture-only-secret"]}}}"#, r#"{"adapter":{"demo.value":{"use":"remote","setting":{"token":"fixture-only-secret"}}}}"#, r#"{"adapter":{"demo.value":{"use":"local","setting":{"offset":1,"prefix":"x","token":"x","token":"fixture-only-secret"}}}}"#, ] { std::fs::write(&path, setting).unwrap(); let result = execute(&binary, "calculate", serde_json::json!({"value":41})); assert!(!result.status.success(), "{setting}"); assert!(result.stdout.is_empty()); let error = String::from_utf8_lossy(&result.stderr); assert!( error.contains("Adapter") && !error.contains("fixture-only-secret"), "{error}" ); } // 同一产物迁移到新目录;配置路径属于部署,不进入编译缓存或二进制。 let relocated = directory.0.join("relocated"); std::fs::create_dir(&relocated).unwrap(); let copy = relocated.join("app"); std::fs::copy(&binary, ©).unwrap(); std::fs::write(&path, local.to_string()).unwrap(); let result = Command::new(©) .arg("--config") .arg(&path) .args(["demo.value.calculate", r#"{"value":41}"#]) .output() .unwrap(); assert!( result.status.success(), "{}", String::from_utf8_lossy(&result.stderr) ); assert_eq!( serde_json::from_slice::(&result.stdout).unwrap()["data"], 42 ); assert_eq!(std::fs::read(&binary).unwrap(), original); // run 使用项目配置;只复制此测试源码到自有临时目录。 let project_root = directory.0.join("project"); let module = project_root.join("module/demo/value"); std::fs::create_dir_all(module.join("adapter")).unwrap(); for relative in [ "app.dever", "port.dever", "api.dever", "adapter/local.dever", "adapter/remote.dever", ] { std::fs::copy( source.join("module/demo/value").join(relative), module.join(relative), ) .unwrap(); } std::fs::create_dir(project_root.join("config")).unwrap(); std::fs::write(project_root.join("config/setting.json"), local.to_string()).unwrap(); let result = Command::new(env!("CARGO_BIN_EXE_dever")) .arg("run") .arg(&project_root) .args(["--", "demo.value.calculate", r#"{"value":41}"#]) .output() .unwrap(); assert!( result.status.success(), "{}", String::from_utf8_lossy(&result.stderr) ); assert_eq!( serde_json::from_slice::(&result.stdout).unwrap()["data"], 42 ); // 单实现省略 use,只保留字段输入;无需多实现选择分支。 let local_source = std::fs::read_to_string(module.join("adapter/local.dever")).unwrap(); std::fs::write( module.join("adapter.dever"), local_source.replace("demo.value.adapter.local", "demo.value.adapter"), ) .unwrap(); std::fs::remove_dir_all(module.join("adapter")).unwrap(); std::fs::write(project_root.join("config/setting.json"), serde_json::json!({"adapter":{"demo.value":{"setting":{"offset":2,"prefix":"one:","token":"key"}}}}).to_string()).unwrap(); let result = Command::new(env!("CARGO_BIN_EXE_dever")) .arg("run") .arg(&project_root) .args(["--", "demo.value.worker", r#"{"value":41}"#]) .output() .unwrap(); assert!( result.status.success(), "{}", String::from_utf8_lossy(&result.stderr) ); assert_eq!( serde_json::from_slice::(&result.stdout).unwrap()["data"], 43 ); }