use std::path::{Path, PathBuf};
use std::process::Command;
fn unique_temp_dir(tag: &str) -> PathBuf {
let id = uuid::Uuid::new_v4().simple().to_string();
let dir = std::env::temp_dir().join(format!("asd-lang-{tag}-{id}"));
std::fs::create_dir_all(&dir).expect("create temp dir");
dir
}
fn asd(dir: &Path, args: &[&str]) -> std::process::Output {
let db = dir.join(".asd-state.db");
Command::new(env!("CARGO_BIN_EXE_asd"))
.args(["--db", db.to_str().unwrap()])
.args(args)
.current_dir(dir)
.output()
.expect("run asd")
}
fn init_and_index(dir: &Path) {
let o = asd(dir, &["init", "--no-hooks"]);
assert!(
o.status.success(),
"init failed: {}",
String::from_utf8_lossy(&o.stderr)
);
let o = asd(dir, &["index", "."]);
assert!(
o.status.success(),
"index failed: {}",
String::from_utf8_lossy(&o.stderr)
);
}
fn index_json(dir: &Path) -> serde_json::Value {
let o = asd(dir, &["index", "."]);
assert!(
o.status.success(),
"index failed: {}",
String::from_utf8_lossy(&o.stderr)
);
serde_json::from_slice(&o.stdout).expect("parse index output")
}
fn read_symbol(dir: &Path, qname: &str) -> serde_json::Value {
let o = asd(dir, &["read", qname]);
assert!(
o.status.success(),
"asd read {qname} failed: {}",
String::from_utf8_lossy(&o.stderr)
);
serde_json::from_slice(&o.stdout).expect("parse read output")
}
fn declared_effect_categories(sym: &serde_json::Value) -> Vec<String> {
let empty = vec![];
sym["effects"]["declared"]
.as_array()
.unwrap_or(&empty)
.iter()
.filter_map(|e| e["effect"].as_str().map(String::from))
.collect()
}
#[test]
fn rust_indexes_struct_and_impl_method() {
let dir = unique_temp_dir("rust");
std::fs::write(
dir.join("payments.rs"),
r#"
pub struct PaymentService { api_key: String }
impl PaymentService {
pub fn charge(&self, amount: f64) -> bool {
println!("charging {}", amount);
amount > 0.0
}
pub fn refund(&self, amount: f64) -> bool { amount > 0.0 }
}
"#,
)
.unwrap();
init_and_index(&dir);
let idx = index_json(&dir);
assert!(
idx["symbols"].as_u64().unwrap_or(0) >= 2,
"expected >=2 symbols; got {idx}"
);
let sym = read_symbol(&dir, "payments.PaymentService.charge");
assert_eq!(
sym["symbol"]["qname"].as_str().unwrap(),
"payments.PaymentService.charge"
);
assert_eq!(sym["symbol"]["kind"].as_str().unwrap(), "method");
}
#[test]
fn rust_infers_log_effect() {
let dir = unique_temp_dir("rust-fx");
std::fs::write(
dir.join("logger.rs"),
r#"
pub fn log_event(msg: &str) { println!("{}", msg); }
"#,
)
.unwrap();
init_and_index(&dir);
let sym = read_symbol(&dir, "logger.log_event");
let cats = declared_effect_categories(&sym);
assert!(
cats.iter().any(|c| c == "log"),
"expected log effect; got {cats:?}"
);
}
#[test]
fn go_indexes_package_functions() {
let dir = unique_temp_dir("go");
std::fs::write(
dir.join("payments.go"),
r#"package payments
import "fmt"
func ChargeCard(userID string, amount float64) error {
fmt.Printf("charging %s\n", userID)
return nil
}
func Refund(userID string, amount float64) error { return nil }
"#,
)
.unwrap();
init_and_index(&dir);
let idx = index_json(&dir);
assert!(
idx["symbols"].as_u64().unwrap_or(0) >= 2,
"expected >=2 symbols; got {idx}"
);
let sym = read_symbol(&dir, "payments.ChargeCard");
assert_eq!(
sym["symbol"]["qname"].as_str().unwrap(),
"payments.ChargeCard"
);
assert_eq!(sym["symbol"]["kind"].as_str().unwrap(), "function");
}
#[test]
fn go_infers_log_effect() {
let dir = unique_temp_dir("go-fx");
std::fs::write(
dir.join("svc.go"),
r#"package svc
import "fmt"
func Run() { fmt.Println("running") }
"#,
)
.unwrap();
init_and_index(&dir);
let sym = read_symbol(&dir, "svc.Run");
let cats = declared_effect_categories(&sym);
assert!(
cats.iter().any(|c| c == "log"),
"expected log effect; got {cats:?}"
);
}
#[test]
fn java_indexes_class_and_methods() {
let dir = unique_temp_dir("java");
std::fs::write(
dir.join("PaymentService.java"),
r#"public class PaymentService {
public boolean charge(String userId, double amount) {
System.out.println("charging " + userId);
return amount > 0;
}
public boolean refund(String userId, double amount) { return amount > 0; }
}
"#,
)
.unwrap();
init_and_index(&dir);
let idx = index_json(&dir);
assert!(
idx["symbols"].as_u64().unwrap_or(0) >= 2,
"expected >=2 symbols; got {idx}"
);
let sym = read_symbol(&dir, "PaymentService.charge");
assert_eq!(sym["symbol"]["kind"].as_str().unwrap(), "method");
}
#[test]
fn java_infers_log_effect() {
let dir = unique_temp_dir("java-fx");
std::fs::write(
dir.join("Logger.java"),
r#"public class Logger {
public void log(String msg) { System.out.println(msg); }
}
"#,
)
.unwrap();
init_and_index(&dir);
let sym = read_symbol(&dir, "Logger.log");
let cats = declared_effect_categories(&sym);
assert!(
cats.iter().any(|c| c == "log"),
"expected log effect; got {cats:?}"
);
}
#[test]
fn csharp_indexes_class_and_methods() {
let dir = unique_temp_dir("csharp");
std::fs::write(
dir.join("PaymentService.cs"),
r#"using System;
public class PaymentService {
public bool Charge(string userId, decimal amount) {
Console.WriteLine($"charging {userId}");
return amount > 0;
}
public bool Refund(string userId, decimal amount) { return amount > 0; }
}
"#,
)
.unwrap();
init_and_index(&dir);
let idx = index_json(&dir);
assert!(
idx["symbols"].as_u64().unwrap_or(0) >= 2,
"expected >=2 symbols; got {idx}"
);
let sym = read_symbol(&dir, "PaymentService.Charge");
assert_eq!(sym["symbol"]["kind"].as_str().unwrap(), "method");
}
#[test]
fn csharp_infers_log_effect() {
let dir = unique_temp_dir("csharp-fx");
std::fs::write(
dir.join("Logger.cs"),
r#"using System;
public class Logger {
public void Log(string msg) { Console.WriteLine(msg); }
}
"#,
)
.unwrap();
init_and_index(&dir);
let sym = read_symbol(&dir, "Logger.Log");
let cats = declared_effect_categories(&sym);
assert!(
cats.iter().any(|c| c == "log"),
"expected log effect; got {cats:?}"
);
}