use std::fs; use std::path::{Path, PathBuf}; use dever_core::{hir::Program, source::SourceMap}; #[path = "support/temp.rs"] mod temp; fn examples() -> PathBuf { Path::new(env!("CARGO_MANIFEST_DIR")).join("../../skills/assets/examples") } fn checked(root: &Path) -> Program { let sources = SourceMap::load_project(&root.join("module"), &root.join("test")).unwrap(); let settings = dever_runtime::config::Settings::load_project(root).unwrap(); let program = dever_core::check_with_settings(&sources, &settings).unwrap_or_else(|errors| { panic!( "{}: {}", root.display(), errors .iter() .map(|error| error.render(&sources)) .collect::() ) }); let errors = program.application_errors().unwrap(); assert!( errors.is_empty(), "{}", errors .iter() .map(|error| error.render(&sources)) .collect::() ); program.validate_database_settings(&settings).unwrap(); for source in sources.files() { assert_eq!( dever_core::format::format(source).unwrap(), source.text(), "{}", source.path().display() ); } program } fn copy_tree(source: &Path, target: &Path) { fs::create_dir(target).unwrap(); for entry in fs::read_dir(source).unwrap() { let entry = entry.unwrap(); let destination = target.join(entry.file_name()); if entry.file_type().unwrap().is_dir() { copy_tree(&entry.path(), &destination); } else { fs::copy(entry.path(), destination).unwrap(); } } } #[test] fn relocated_examples_check_production_roots_settings_and_case_local_effects() { let temporary = temp::TemporaryDirectory::new(); let projects = [ ("http-rest", 1, false), ("jobs", 1, true), ("external-lib", 2, false), ("collections", 2, false), ("command", 1, false), ]; assert_eq!(fs::read_dir(examples()).unwrap().count(), projects.len()); for (name, test_count, uses_database) in projects { let root = temporary.path().join(name); copy_tree(&examples().join(name), &root); let program = checked(&root); assert_eq!(program.tests().len(), test_count); assert!( program .tests() .iter() .all(|case| case.uses_database() == uses_database) ); } let template = temporary.path().join("template"); copy_tree(&examples().parent().unwrap().join("template"), &template); assert_eq!(checked(&template).tests().len(), 1); } #[test] fn three_ecosystems_share_one_checked_port_contract_and_fake_suite() { let temporary = temp::TemporaryDirectory::new(); let root = temporary.path().join("external-lib"); copy_tree(&examples().join("external-lib"), &root); let original = checked(&root).external_worker_contracts().remove(0); let mut schemas = std::collections::BTreeSet::new(); for (ecosystem, entry, dependency) in [ ("pip", "python/render.py", "markdown@3.8.2"), ("npm", "javascript/render.mjs", "marked@15.0.12"), ("go", "go/render.go", "github.com/yuin/goldmark@v1.7.8"), ] { fs::write( root.join("module/document/markup/adapter.dever"), format!("external {ecosystem} \"{entry}\" {{\n lib \"{dependency}\"\n}}\n"), ) .unwrap(); fs::write( root.join("config/setting.json"), serde_json::to_vec(&serde_json::json!({ "lib": [format!("{ecosystem}:{dependency}")] })) .unwrap(), ) .unwrap(); let program = checked(&root); let contracts = program.external_worker_contracts(); let [contract] = contracts.as_slice() else { panic!("one external Adapter must own the shared Port") }; // 协议身份绑定生态和 Lib 声明;跨生态复用的是 Port 的类型合同。 assert!(schemas.insert(contract.schema.clone())); assert_eq!(contract.port, original.port); assert_eq!(contract.setting, original.setting); assert_eq!(contract.inputs, original.inputs); assert_eq!(contract.outputs, original.outputs); assert_eq!(contract.errors, original.errors); assert_eq!(contract.operations, ["render"]); assert_eq!(contract.ecosystem, ecosystem); assert_eq!(contract.libs, [format!("{ecosystem}:{dependency}")]); assert!(root.join(&contract.entry).is_file()); assert_eq!(program.tests().len(), 2); assert!(!program.tests().iter().any(|case| case.uses_database())); } }