use dist_agent_lang::compile::{
run_compile, select_services_for_target, set_compiler_available_override, CompileError,
};
use dist_agent_lang::lexer::tokens::{get_target_constraints, CompilationTarget, TargetConstraint};
use dist_agent_lang::manifest::{resolve_dependencies, write_lockfile};
use dist_agent_lang::parser::ast::{CompilationTargetInfo, Program, ServiceStatement, Statement};
fn program_with_native_service() -> Program {
let service = ServiceStatement {
name: "MyApp".to_string(),
attributes: vec![dist_agent_lang::parser::ast::Attribute {
name: "@native".to_string(),
parameters: vec![],
target: dist_agent_lang::parser::ast::AttributeTarget::Module,
}],
fields: vec![],
methods: vec![],
events: vec![],
exported: false,
compilation_target: Some(CompilationTargetInfo {
target: CompilationTarget::Native,
constraints: TargetConstraint::new(CompilationTarget::Native),
validation_errors: vec![],
}),
};
Program {
statements: vec![Statement::Service(service)],
statement_spans: vec![None],
}
}
#[test]
fn test_select_services_for_target_native() {
let program = program_with_native_service();
let services = select_services_for_target(&program, &CompilationTarget::Native);
assert_eq!(services.len(), 1);
assert_eq!(services[0].name, "MyApp");
}
#[test]
fn test_select_services_for_target_empty_when_no_match() {
let program = program_with_native_service();
let services = select_services_for_target(&program, &CompilationTarget::Blockchain);
assert!(services.is_empty());
}
#[test]
fn test_select_services_for_target_multiple() {
let mk_service = |name: &str| ServiceStatement {
name: name.to_string(),
attributes: vec![],
fields: vec![],
methods: vec![],
events: vec![],
exported: false,
compilation_target: Some(CompilationTargetInfo {
target: CompilationTarget::Blockchain,
constraints: TargetConstraint::new(CompilationTarget::Blockchain),
validation_errors: vec![],
}),
};
let program = Program {
statements: vec![
Statement::Service(mk_service("Token")),
Statement::Service(mk_service("Vault")),
],
statement_spans: vec![None, None],
};
let services = select_services_for_target(&program, &CompilationTarget::Blockchain);
assert_eq!(services.len(), 2);
let names: Vec<&str> = services.iter().map(|s| s.name.as_str()).collect();
assert!(names.contains(&"Token"));
assert!(names.contains(&"Vault"));
}
#[test]
fn test_ct0_runtime_rejects_missing_required_attributes() {
let constraints = get_target_constraints();
let bc = constraints
.get(&CompilationTarget::Blockchain)
.cloned()
.unwrap();
let service = ServiceStatement {
name: "Bad".to_string(),
attributes: vec![], fields: vec![],
methods: vec![],
events: vec![],
exported: false,
compilation_target: Some(CompilationTargetInfo {
target: CompilationTarget::Blockchain,
constraints: bc,
validation_errors: vec![],
}),
};
let program = Program {
statements: vec![Statement::Service(service)],
statement_spans: vec![None],
};
let mut runtime = dist_agent_lang::Runtime::new();
let result = runtime.execute_program(program, None);
assert!(result.is_err());
let err = result.unwrap_err();
let msg = format!("{}", err);
assert!(
msg.contains("missing required attribute") || msg.contains("Missing required"),
"expected compile-target validation error, got: {}",
msg
);
}
#[test]
fn test_run_compile_with_imports_resolves() {
let dir = tempfile::tempdir().unwrap();
let main_path = dir.path().join("main.dal");
let lib_path = dir.path().join("lib.dal");
std::fs::write(&lib_path, "fn helper() { 0 }\n").unwrap();
let main_source = r#"
import "./lib.dal" as m;
@native
service App @compile_target("native") {
fn run() { 0 }
}
"#;
std::fs::write(&main_path, main_source).unwrap();
let out = dir.path().join("out");
std::fs::create_dir_all(&out).unwrap();
let result = run_compile(
main_path.clone(),
CompilationTarget::Native,
out.clone(),
main_source,
);
assert!(
result.is_ok(),
"build with imports should resolve and compile: {:?}",
result.err()
);
let artifacts = result.unwrap();
assert_eq!(artifacts.service_names, vec!["App"]);
}
#[test]
fn test_run_compile_resolves_package_with_only_lib_dal() {
let dir = tempfile::tempdir().unwrap();
let app_dir = dir.path().join("app");
let mylib_dir = dir.path().join("mylib");
std::fs::create_dir_all(&app_dir).unwrap();
std::fs::create_dir_all(&mylib_dir).unwrap();
std::fs::write(mylib_dir.join("lib.dal"), "fn foo() { 42 }").unwrap();
std::fs::write(
app_dir.join("dal.toml"),
r#"[package]
name = "app"
version = "0.1.0"
[dependencies]
mylib = { path = "../mylib" }
"#,
)
.unwrap();
let (resolved, version_meta) = resolve_dependencies(&app_dir.join("dal.toml")).unwrap();
write_lockfile(&app_dir.join("dal.toml"), &resolved, &version_meta).unwrap();
let main_source = r#"
import "mylib" as m;
@native
service App @compile_target("native") {
fn run() { m::foo() }
}
"#;
std::fs::write(app_dir.join("main.dal"), main_source).unwrap();
let out = dir.path().join("out");
std::fs::create_dir_all(&out).unwrap();
let result = run_compile(
app_dir.join("main.dal"),
CompilationTarget::Native,
out.clone(),
main_source,
);
assert!(
result.is_ok(),
"compile with package (only lib.dal) should resolve and compile: {:?}",
result.err()
);
let artifacts = result.unwrap();
assert_eq!(artifacts.service_names, vec!["App"]);
}
#[test]
fn test_run_compile_blockchain_backend() {
let source = r#"
@secure
@trust("hybrid")
@chain("ethereum")
service Token @compile_target("blockchain") {
fn transfer(to: string, amount: int) { }
}
"#;
let dir = tempfile::tempdir().unwrap();
let entry = dir.path().join("main.dal");
let out = dir.path().join("out");
std::fs::create_dir_all(&out).unwrap();
let result = run_compile(
entry.clone(),
CompilationTarget::Blockchain,
out.clone(),
source,
);
match result {
Ok(artifacts) => {
assert_eq!(artifacts.target, "blockchain");
assert_eq!(artifacts.service_names, vec!["Token"]);
assert!(
!artifacts.stub,
"blockchain backend should produce real artifacts when solc is available"
);
let has_bin = artifacts
.artifact_paths
.iter()
.any(|p| p.extension().map(|e| e == "bin").unwrap_or(false));
let has_abi = artifacts
.artifact_paths
.iter()
.any(|p| p.extension().map(|e| e == "abi").unwrap_or(false));
assert!(
has_bin && has_abi,
"expected .bin and .abi artifacts, got {:?}",
artifacts.artifact_paths
);
let manifest = out.join("compile-manifest.json");
assert!(manifest.exists());
let content = std::fs::read_to_string(manifest).unwrap();
assert!(content.contains("\"stub\":false"));
}
Err(CompileError::CompilerNotFound { .. }) => {
}
Err(CompileError::Parse(_)) => {
}
Err(e) => panic!("unexpected error: {}", e),
}
}
#[test]
fn test_hybrid_blockchain_autosplits_auth_namespace_for_http_artifacts() {
let source = r#"
@secure
@trust("hybrid")
@chain("ethereum")
service Token @compile_target("blockchain") {
fn transfer(to: string, amount: int) {
let session = auth::session("user1", ["user"]);
chain::call(1, "0x1234", "transfer", {"to": to, "amount": amount});
}
}
"#;
let dir = tempfile::tempdir().unwrap();
let entry = dir.path().join("main.dal");
let out = dir.path().join("out");
std::fs::create_dir_all(&out).unwrap();
let result = run_compile(entry, CompilationTarget::Blockchain, out, source);
match result {
Ok(artifacts) => {
let has_http_split = artifacts
.artifact_paths
.iter()
.any(|p| p.extension().map(|e| e == "json").unwrap_or(false));
assert!(
has_http_split,
"expected HTTP split artifact for auth-routed block, got {:?}",
artifacts.artifact_paths
);
}
Err(CompileError::CompilerNotFound { .. }) => {
}
Err(CompileError::Parse(msg)) => panic!(
"hybrid auth block should be auto-split, not parse-failed: {}",
msg
),
Err(e) => panic!("unexpected error: {}", e),
}
}
#[test]
fn test_hybrid_blockchain_autosplits_cloudadmin_namespace_for_http_artifacts() {
let source = r#"
@secure
@trust("hybrid")
@chain("ethereum")
service Governance @compile_target("blockchain") {
fn rebalance() {
cloudadmin::authorize("admin", "rebalance", "vault");
chain::call(1, "0x1234", "rebalance", {});
}
}
"#;
let dir = tempfile::tempdir().unwrap();
let entry = dir.path().join("main.dal");
let out = dir.path().join("out");
std::fs::create_dir_all(&out).unwrap();
let result = run_compile(entry, CompilationTarget::Blockchain, out, source);
match result {
Ok(artifacts) => {
let has_http_split = artifacts
.artifact_paths
.iter()
.any(|p| p.extension().map(|e| e == "json").unwrap_or(false));
assert!(
has_http_split,
"expected HTTP split artifact for cloudadmin-routed block, got {:?}",
artifacts.artifact_paths
);
}
Err(CompileError::CompilerNotFound { .. }) => {
}
Err(CompileError::Parse(msg)) => panic!(
"hybrid cloudadmin block should be auto-split, not parse-failed: {}",
msg
),
Err(e) => panic!("unexpected error: {}", e),
}
}
#[test]
fn test_run_compile_wasm_backend() {
let source = r#"
@web
service WebApp @compile_target("wasm") {
fn handle() { }
}
"#;
let dir = tempfile::tempdir().unwrap();
let entry = dir.path().join("main.dal");
let out = dir.path().join("out");
std::fs::create_dir_all(&out).unwrap();
let result = run_compile(
entry.clone(),
CompilationTarget::WebAssembly,
out.clone(),
source,
);
match result {
Ok(artifacts) => {
assert_eq!(artifacts.target, "wasm");
assert_eq!(artifacts.service_names, vec!["WebApp"]);
assert!(
!artifacts.stub,
"wasm backend should produce real artifacts when wasm32 target is available"
);
let has_wasm = artifacts
.artifact_paths
.iter()
.any(|p| p.extension().map(|e| e == "wasm").unwrap_or(false));
assert!(
has_wasm,
"expected .wasm artifact, got {:?}",
artifacts.artifact_paths
);
let manifest = out.join("compile-manifest.json");
assert!(manifest.exists());
let content = std::fs::read_to_string(manifest).unwrap();
assert!(content.contains("\"stub\":false"));
}
Err(CompileError::CompilerNotFound { .. }) => {
}
Err(CompileError::Parse(_)) => {}
Err(CompileError::Backend(_)) => {
}
Err(e) => panic!("unexpected error: {}", e),
}
}
#[test]
fn test_run_compile_native_backend() {
let source = r#"
@native
service App @compile_target("native") { fn run() { 42 } }
"#;
let dir = tempfile::tempdir().unwrap();
let entry = dir.path().join("main.dal");
let out = dir.path().join("out");
std::fs::create_dir_all(&out).unwrap();
let result = run_compile(
entry.clone(),
CompilationTarget::Native,
out.clone(),
source,
);
match result {
Ok(artifacts) => {
assert_eq!(artifacts.target, "native");
assert_eq!(artifacts.service_names, vec!["App"]);
assert!(
!artifacts.stub,
"native backend should report real codegen when cargo is available"
);
if let Some(p) = artifacts
.artifact_paths
.iter()
.find(|p| p.extension().map(|e| e == "rlib").unwrap_or(false))
{
assert!(p.exists(), "rlib path should exist: {}", p.display());
}
let manifest = out.join("compile-manifest.json");
assert!(manifest.exists());
let content = std::fs::read_to_string(manifest).unwrap();
assert!(content.contains("\"stub\":false"));
}
Err(CompileError::CompilerNotFound { .. }) => {}
Err(CompileError::Parse(_)) => {}
Err(CompileError::Backend(_)) => {}
Err(e) => panic!("unexpected error: {}", e),
}
}
#[test]
fn test_run_compile_edge_errors_when_no_edge_services() {
let source = r#"
@native
service App @compile_target("native") { fn run() { 42 } }
"#;
let dir = tempfile::tempdir().unwrap();
let entry = dir.path().join("main.dal");
let out = dir.path().join("out");
std::fs::create_dir_all(&out).unwrap();
let result = run_compile(entry.clone(), CompilationTarget::Edge, out.clone(), source);
match &result {
Err(CompileError::Backend(msg)) => {
assert!(
msg.contains("edge") || msg.contains("iot"),
"expected message about edge/iot services; got: {}",
msg
);
}
Ok(a) => panic!(
"expected Backend error when no edge services, got Ok: {:?}",
a
),
Err(e) => panic!("expected CompileError::Backend, got: {}", e),
}
}
#[test]
fn test_run_compile_stub_backend_succeeds_when_rustc_available() {
let source = r#"
@mobile
service StubApp @compile_target("mobile") { fn run() { 0 } }
"#;
let dir = tempfile::tempdir().unwrap();
let entry = dir.path().join("main.dal");
let out = dir.path().join("out");
std::fs::create_dir_all(&out).unwrap();
let result = run_compile(
entry.clone(),
CompilationTarget::Mobile,
out.clone(),
source,
);
match &result {
Ok(artifacts) => {
assert!(artifacts.stub, "StubBackend should produce stub: true");
assert_eq!(artifacts.target, "mobile");
assert_eq!(artifacts.service_names, vec!["StubApp"]);
}
Err(CompileError::CompilerNotFound { target, .. }) => {
assert_eq!(target.as_str(), "mobile");
}
Err(CompileError::Parse(e)) => panic!("parse error (fix source if grammar changed): {}", e),
Err(e) => panic!("unexpected error: {}", e),
}
}
#[test]
fn test_run_compile_returns_compiler_not_found_when_override_false() {
struct Guard;
impl Drop for Guard {
fn drop(&mut self) {
set_compiler_available_override(None);
}
}
let _guard = Guard;
set_compiler_available_override(Some(false));
let source = r#"
@mobile
service StubApp @compile_target("mobile") { fn run() { 0 } }
"#;
let dir = tempfile::tempdir().unwrap();
let entry = dir.path().join("main.dal");
let out = dir.path().join("out");
std::fs::create_dir_all(&out).unwrap();
let result = run_compile(
entry.clone(),
CompilationTarget::Mobile,
out.clone(),
source,
);
match &result {
Err(CompileError::CompilerNotFound { target, hint }) => {
assert_eq!(target.as_str(), "mobile");
assert!(!hint.is_empty());
}
Ok(_) => panic!("expected CompilerNotFound when override is false"),
Err(e) => panic!("expected CompilerNotFound, got: {}", e),
}
}
#[test]
fn test_run_compile_stub_backend_succeeds_when_override_true() {
struct Guard;
impl Drop for Guard {
fn drop(&mut self) {
set_compiler_available_override(None);
}
}
let _guard = Guard;
set_compiler_available_override(Some(true));
let source = r#"
@mobile
service StubApp @compile_target("mobile") { fn run() { 0 } }
"#;
let dir = tempfile::tempdir().unwrap();
let entry = dir.path().join("main.dal");
let out = dir.path().join("out");
std::fs::create_dir_all(&out).unwrap();
let result = run_compile(
entry.clone(),
CompilationTarget::Mobile,
out.clone(),
source,
);
assert!(
result.is_ok(),
"with override true, StubBackend should succeed: {:?}",
result.err()
);
let artifacts = result.unwrap();
assert!(artifacts.stub);
assert_eq!(artifacts.service_names, vec!["StubApp"]);
}
#[test]
fn test_run_compile_native_returns_compiler_not_found_when_override_false() {
struct Guard;
impl Drop for Guard {
fn drop(&mut self) {
set_compiler_available_override(None);
}
}
let _guard = Guard;
set_compiler_available_override(Some(false));
let source = r#"
@native
service App @compile_target("native") { fn run() { 42 } }
"#;
let dir = tempfile::tempdir().unwrap();
let entry = dir.path().join("main.dal");
let out = dir.path().join("out");
std::fs::create_dir_all(&out).unwrap();
let result = run_compile(
entry.clone(),
CompilationTarget::Native,
out.clone(),
source,
);
match &result {
Err(CompileError::CompilerNotFound { target, .. }) => assert_eq!(target.as_str(), "native"),
Ok(_) => panic!("expected CompilerNotFound when override is false"),
Err(e) => panic!("expected CompilerNotFound, got: {}", e),
}
}