293 lines
10 KiB
Rust

use prometeu_compiler::building::output::CompiledModule;
use prometeu_compiler::building::output::{compile_project, CompileError, ExportKey, ExportMetadata};
use prometeu_compiler::building::plan::{BuildStep, BuildTarget};
use prometeu_compiler::common::files::FileManager;
use prometeu_compiler::deps::resolver::ProjectKey;
use language_api::types::{ExportItem, ItemName};
use prometeu_compiler::frontends::pbs::adapter::PbsFrontendAdapter;
use prometeu_analysis::ids::ProjectId;
use std::collections::{BTreeMap, HashMap};
use std::path::PathBuf;
use tempfile::tempdir;
use std::fs;
#[test]
fn test_local_vs_dependency_conflict() {
let dir = tempdir().unwrap();
let project_dir = dir.path().to_path_buf();
// Dependency: sdk
let dep_key = ProjectKey { name: "sdk-impl".to_string(), version: "1.0.0".to_string() };
let dep_id = ProjectId(0);
let mut dep_exports = BTreeMap::new();
dep_exports.insert(ExportKey {
module_path: "math".to_string(), // normalized path
item: ExportItem::Type { name: ItemName::new("Vector").unwrap() },
}, ExportMetadata {
func_idx: None,
is_host: false,
ty: None,
});
let dep_module = CompiledModule {
project_id: dep_id,
project_key: dep_key.clone(),
target: BuildTarget::Main,
exports: dep_exports,
imports: vec![],
const_pool: vec![],
code: vec![],
function_metas: vec![],
debug_info: None,
symbols: vec![],
};
let mut dep_modules: HashMap<ProjectId, CompiledModule> = HashMap::new();
dep_modules.insert(dep_id, dep_module);
// Main project has a LOCAL module named "sdk/math"
// By creating a file in src/main/modules/sdk/math/, the module path becomes "sdk/math"
fs::create_dir_all(project_dir.join("src/main/modules/sdk/math")).unwrap();
fs::write(project_dir.join("src/main/modules/sdk/math/local.pbs"), "pub declare struct Vector(x: int)").unwrap();
let main_key = ProjectKey { name: "main".to_string(), version: "0.1.0".to_string() };
let main_id = ProjectId(1);
let mut deps: BTreeMap<String, ProjectId> = BTreeMap::new();
deps.insert("sdk".to_string(), ProjectId(0));
let step = BuildStep {
project_id: main_id,
project_key: main_key,
project_dir,
target: BuildTarget::Main,
sources: vec![PathBuf::from("src/main/modules/sdk/math/local.pbs")],
deps,
};
let mut file_manager = FileManager::new();
let fe = PbsFrontendAdapter;
let result = compile_project(step, &dep_modules, &fe, &mut file_manager);
match result {
Err(CompileError::DuplicateExport { symbol, .. }) => {
assert_eq!(symbol, "Vector");
},
_ => panic!("Expected DuplicateExport error, got {:?}", result),
}
}
#[test]
fn test_aliased_dependency_conflict() {
let dir = tempdir().unwrap();
let project_dir = dir.path().to_path_buf();
// Dependency 1: exports "b/c:Vector"
let dep1_key = ProjectKey { name: "p1".to_string(), version: "1.0.0".to_string() };
let dep1_id = ProjectId(0);
let mut dep1_exports = BTreeMap::new();
dep1_exports.insert(ExportKey {
module_path: "b/c".to_string(),
item: ExportItem::Type { name: ItemName::new("Vector").unwrap() },
}, ExportMetadata {
func_idx: None,
is_host: false,
ty: None,
});
let dep1_module = CompiledModule {
project_id: dep1_id,
project_key: dep1_key.clone(),
target: BuildTarget::Main,
exports: dep1_exports,
imports: vec![],
const_pool: vec![],
code: vec![],
function_metas: vec![],
debug_info: None,
symbols: vec![],
};
// Dependency 2: exports "c:Vector"
let dep2_key = ProjectKey { name: "p2".to_string(), version: "1.0.0".to_string() };
let dep2_id = ProjectId(1);
let mut dep2_exports = BTreeMap::new();
dep2_exports.insert(ExportKey {
module_path: "c".to_string(),
item: ExportItem::Type { name: ItemName::new("Vector").unwrap() },
}, ExportMetadata {
func_idx: None,
is_host: false,
ty: None,
});
let dep2_module = CompiledModule {
project_id: dep2_id,
project_key: dep2_key.clone(),
target: BuildTarget::Main,
exports: dep2_exports,
imports: vec![],
const_pool: vec![],
code: vec![],
function_metas: vec![],
debug_info: None,
symbols: vec![],
};
let mut dep_modules: HashMap<ProjectId, CompiledModule> = HashMap::new();
dep_modules.insert(dep1_id, dep1_module);
dep_modules.insert(dep2_id, dep2_module);
let main_key = ProjectKey { name: "main".to_string(), version: "0.1.0".to_string() };
let main_id = ProjectId(2);
let mut deps: BTreeMap<String, ProjectId> = BTreeMap::new();
deps.insert("a".to_string(), ProjectId(0));
deps.insert("a/b".to_string(), ProjectId(1));
let step = BuildStep {
project_id: main_id,
project_key: main_key,
project_dir,
target: BuildTarget::Main,
sources: vec![],
deps,
};
let mut file_manager = FileManager::new();
let fe = PbsFrontendAdapter;
let result = compile_project(step, &dep_modules, &fe, &mut file_manager);
match result {
Err(CompileError::DuplicateExport { symbol, .. }) => {
assert_eq!(symbol, "Vector");
},
_ => panic!("Expected DuplicateExport error, got {:?}", result),
}
}
#[test]
fn test_mixed_main_test_modules() {
let dir = tempdir().unwrap();
let project_dir = dir.path().to_path_buf();
fs::create_dir_all(project_dir.join("src/main/modules/math")).unwrap();
fs::write(project_dir.join("src/main/modules/math/Vector.pbs"), "pub declare struct Vector(x: int)").unwrap();
fs::create_dir_all(project_dir.join("src/test/modules/foo")).unwrap();
fs::write(project_dir.join("src/test/modules/foo/Test.pbs"), "pub declare struct Test(x: int)").unwrap();
let project_key = ProjectKey { name: "mixed".to_string(), version: "0.1.0".to_string() };
let project_id = ProjectId(0);
let step = BuildStep {
project_id,
project_key,
project_dir,
target: BuildTarget::Main,
sources: vec![
PathBuf::from("src/main/modules/math/Vector.pbs"),
PathBuf::from("src/test/modules/foo/Test.pbs"),
],
deps: BTreeMap::new(),
};
let mut file_manager = FileManager::new();
let fe = PbsFrontendAdapter;
let compiled = compile_project(step, &HashMap::new(), &fe, &mut file_manager).unwrap();
// Both should be in exports with normalized paths
assert!(compiled.exports.keys().any(|k| k.module_path == "math"));
assert!(compiled.exports.keys().any(|k| k.module_path == "foo"));
}
#[test]
fn test_module_merging_same_directory() {
let dir = tempdir().unwrap();
let project_dir = dir.path().to_path_buf();
fs::create_dir_all(project_dir.join("src/main/modules/gfx")).unwrap();
fs::write(project_dir.join("src/main/modules/gfx/api.pbs"), "pub declare struct Gfx(id: int)").unwrap();
fs::write(project_dir.join("src/main/modules/gfx/colors.pbs"), "pub declare struct Color(r: int)").unwrap();
let project_key = ProjectKey { name: "merge".to_string(), version: "0.1.0".to_string() };
let project_id = ProjectId(0);
let step = BuildStep {
project_id,
project_key,
project_dir,
target: BuildTarget::Main,
sources: vec![
PathBuf::from("src/main/modules/gfx/api.pbs"),
PathBuf::from("src/main/modules/gfx/colors.pbs"),
],
deps: BTreeMap::new(),
};
let mut file_manager = FileManager::new();
let fe = PbsFrontendAdapter;
let compiled = compile_project(step, &HashMap::new(), &fe, &mut file_manager).unwrap();
// Both should be in the same module "gfx"
assert!(compiled.exports.keys().any(|k| k.module_path == "gfx" && matches!(&k.item, ExportItem::Type { name } if name.as_str() == "Gfx")));
assert!(compiled.exports.keys().any(|k| k.module_path == "gfx" && matches!(&k.item, ExportItem::Type { name } if name.as_str() == "Color")));
}
#[test]
fn test_duplicate_symbol_in_same_module_different_files() {
let dir = tempdir().unwrap();
let project_dir = dir.path().to_path_buf();
fs::create_dir_all(project_dir.join("src/main/modules/gfx")).unwrap();
fs::write(project_dir.join("src/main/modules/gfx/a.pbs"), "pub declare struct Gfx(id: int)").unwrap();
fs::write(project_dir.join("src/main/modules/gfx/b.pbs"), "pub declare struct Gfx(id: int)").unwrap();
let project_key = ProjectKey { name: "dup".to_string(), version: "0.1.0".to_string() };
let project_id = ProjectId(0);
let step = BuildStep {
project_id,
project_key,
project_dir,
target: BuildTarget::Main,
sources: vec![
PathBuf::from("src/main/modules/gfx/a.pbs"),
PathBuf::from("src/main/modules/gfx/b.pbs"),
],
deps: BTreeMap::new(),
};
let mut file_manager = FileManager::new();
let fe = PbsFrontendAdapter;
let result = compile_project(step, &HashMap::new(), &fe, &mut file_manager);
assert!(result.is_err());
// Should be a frontend error (duplicate symbol)
}
#[test]
fn test_root_module_merging() {
let dir = tempdir().unwrap();
let project_dir = dir.path().to_path_buf();
fs::create_dir_all(project_dir.join("src/main/modules")).unwrap();
fs::write(project_dir.join("src/main/modules/main.pbs"), "pub declare struct Main(id: int)").unwrap();
fs::write(project_dir.join("src/main/modules/utils.pbs"), "pub declare struct Utils(id: int)").unwrap();
let project_key = ProjectKey { name: "root-merge".to_string(), version: "0.1.0".to_string() };
let project_id = ProjectId(0);
let step = BuildStep {
project_id,
project_key,
project_dir,
target: BuildTarget::Main,
sources: vec![
PathBuf::from("src/main/modules/main.pbs"),
PathBuf::from("src/main/modules/utils.pbs"),
],
deps: BTreeMap::new(),
};
let mut file_manager = FileManager::new();
let fe = PbsFrontendAdapter;
let compiled = compile_project(step, &HashMap::new(), &fe, &mut file_manager).unwrap();
// Both should be in the root module ""
assert!(compiled.exports.keys().any(|k| k.module_path == "" && matches!(&k.item, ExportItem::Type { name } if name.as_str() == "Main")));
assert!(compiled.exports.keys().any(|k| k.module_path == "" && matches!(&k.item, ExportItem::Type { name } if name.as_str() == "Utils")));
}