use std::path::PathBuf;
use pasta_dsl::parser::parse_str;
use pasta_lua::LuaTranspiler;
use pasta_lua::debug::source_map::canonicalize_chunk_name;
use pasta_lua::loader::{CacheManager, PastaLoader};
const PASTA_A: &str = "\
*あいさつ
さくら:「おはよう!」
さくら:「げんき?」
";
const PASTA_B: &str = "\
*おやすみ
さくら:「おやすみなさい。」
";
fn write_pasta(base_dir: &std::path::Path, rel: &str, src: &str) -> PathBuf {
let path = base_dir.join(rel);
std::fs::create_dir_all(path.parent().unwrap()).expect("mkdir dic");
std::fs::write(&path, src).expect("write .pasta");
path
}
#[test]
fn loader_builds_and_aggregates_multi_chunk_source_map() {
let temp = tempfile::TempDir::new().expect("temp dir");
let base_dir = temp.path().to_path_buf();
let file_a = write_pasta(&base_dir, "dic/baseware/a.pasta", PASTA_A);
let file_b = write_pasta(&base_dir, "dic/baseware/b.pasta", PASTA_B);
let cache_manager = CacheManager::new(base_dir.clone(), "profile/pasta/cache/lua");
let source_map =
PastaLoader::build_source_map(&[file_a.clone(), file_b.clone()], &cache_manager, false);
let chunk_a = cache_manager
.source_to_cache_path(&file_a)
.to_string_lossy()
.to_string();
let chunk_b = cache_manager
.source_to_cache_path(&file_b)
.to_string_lossy()
.to_string();
assert_ne!(
canonicalize_chunk_name(&chunk_a),
canonicalize_chunk_name(&chunk_b),
"2 つの `.pasta` は別チャンクへ写像される"
);
let file_a_key = file_a.to_string_lossy().to_string();
let file_b_key = file_b.to_string_lossy().to_string();
let resolved_a: Vec<_> = (1u32..=200)
.filter_map(|lua_line| {
source_map
.resolve_lua_to_pasta(&chunk_a, lua_line)
.map(|pos| (lua_line, pos.clone()))
})
.collect();
assert!(
!resolved_a.is_empty(),
"チャンク A は少なくとも 1 つの `.lua`→`.pasta` 対応を持つ(1.1)"
);
for (_lua_line, pos) in &resolved_a {
assert_eq!(
canonicalize_chunk_name(&pos.file),
canonicalize_chunk_name(&file_a_key),
"チャンク A の解決先 `.pasta` ファイルは file_a"
);
}
let resolved_b: Vec<_> = (1u32..=200)
.filter_map(|lua_line| {
source_map
.resolve_lua_to_pasta(&chunk_b, lua_line)
.map(|pos| (lua_line, pos.clone()))
})
.collect();
assert!(
!resolved_b.is_empty(),
"チャンク B は少なくとも 1 つの `.lua`→`.pasta` 対応を持つ(1.1)"
);
for (_lua_line, pos) in &resolved_b {
assert_eq!(
canonicalize_chunk_name(&pos.file),
canonicalize_chunk_name(&file_b_key),
"チャンク B の解決先 `.pasta` ファイルは file_b"
);
}
let (lua_line_a, pos_a) = &resolved_a[0];
let back_a = source_map.resolve_pasta_to_lua(&pos_a.file, pos_a.line);
assert!(
back_a
.iter()
.any(|(chunk, lua)| canonicalize_chunk_name(chunk)
== canonicalize_chunk_name(&chunk_a)
&& lua == lua_line_a),
"`.pasta` 行 {} の逆引きはチャンク A の `.lua` 行 {} を含む(3.1 双方向): {:?}",
pos_a.line,
lua_line_a,
back_a
);
let (lua_line_b, pos_b) = &resolved_b[0];
let back_b = source_map.resolve_pasta_to_lua(&pos_b.file, pos_b.line);
assert!(
back_b
.iter()
.any(|(chunk, lua)| canonicalize_chunk_name(chunk)
== canonicalize_chunk_name(&chunk_b)
&& lua == lua_line_b),
"`.pasta` 行 {} の逆引きはチャンク B の `.lua` 行 {} を含む: {:?}",
pos_b.line,
lua_line_b,
back_b
);
assert!(
!back_b
.iter()
.any(|(chunk, _)| canonicalize_chunk_name(chunk)
== canonicalize_chunk_name(&chunk_a)),
"B のファイル行の逆引きに A のチャンクが混入してはならない: {:?}",
back_b
);
}
#[test]
fn sink_attachment_is_byte_invariant_against_sinkless_transpile() {
for src in [PASTA_A, PASTA_B] {
let pasta_file = parse_str(src, "dic/x.pasta").expect("parse");
let transpiler = LuaTranspiler::default();
let mut out_sinkless = Vec::new();
transpiler
.transpile(&pasta_file, &mut out_sinkless)
.expect("transpile sinkless");
let mut sink = pasta_lua::debug::source_map::MapBuilderSink::new(
"dic/x.pasta".to_string(),
"chunk-x".to_string(),
);
let mut out_with_sink = Vec::new();
transpiler
.transpile_with_source_map(&pasta_file, &mut out_with_sink, Some(&mut sink))
.expect("transpile with sink");
assert_eq!(
out_sinkless, out_with_sink,
"7.1: シンク装着は生成 `.lua` を 1 バイトも変えない(無効時バイト不変の根拠)"
);
}
}
#[test]
fn no_map_built_when_no_files_passed() {
let temp = tempfile::TempDir::new().expect("temp dir");
let base_dir = temp.path().to_path_buf();
let cache_manager = CacheManager::new(base_dir, "profile/pasta/cache/lua");
let source_map = PastaLoader::build_source_map(&[], &cache_manager, false);
assert!(source_map.resolve_lua_to_pasta("any-chunk", 1).is_none());
assert!(
source_map
.resolve_pasta_to_lua("any.pasta", 1)
.is_empty()
);
}
#[test]
fn loader_writes_sidecar_when_enabled_and_round_trips() {
use pasta_lua::debug::source_map::{read_sidecar, sidecar_path_for_lua};
let temp = tempfile::TempDir::new().expect("temp dir");
let base_dir = temp.path().to_path_buf();
let file_a = write_pasta(&base_dir, "dic/baseware/a.pasta", PASTA_A);
let file_b = write_pasta(&base_dir, "dic/baseware/b.pasta", PASTA_B);
let cache_manager = CacheManager::new(base_dir.clone(), "profile/pasta/cache/lua");
cache_manager
.prepare_cache_dir()
.expect("prepare cache dir");
let source_map =
PastaLoader::build_source_map(&[file_a.clone(), file_b.clone()], &cache_manager, true);
for file in [&file_a, &file_b] {
let lua_path = cache_manager.source_to_cache_path(file);
let chunk = lua_path.to_string_lossy().to_string();
let sidecar = sidecar_path_for_lua(&lua_path);
assert!(
sidecar.exists(),
"サイドカー有効時、{} が生成されること(3.2)",
sidecar.display()
);
let reread = read_sidecar(&lua_path).expect("read_sidecar");
let mut compared = 0usize;
for lua_line in 1u32..=500 {
let mem = source_map.resolve_lua_to_pasta(&chunk, lua_line).cloned();
let disk = reread.pasta_for_lua(lua_line).cloned();
assert_eq!(
mem.as_ref().map(|p| p.line),
disk.as_ref().map(|p| p.line),
"再読込サイドカーの `.lua`{} → `.pasta` 行はメモリ写像と一致(3.2 往復)",
lua_line
);
if mem.is_some() {
compared += 1;
}
}
assert!(
compared > 0,
"当該チャンクは少なくとも 1 対応を持つ(サイドカーが空でない)"
);
}
}
#[test]
fn loader_writes_no_sidecar_when_disabled() {
use pasta_lua::debug::source_map::sidecar_path_for_lua;
let temp = tempfile::TempDir::new().expect("temp dir");
let base_dir = temp.path().to_path_buf();
let file_a = write_pasta(&base_dir, "dic/baseware/a.pasta", PASTA_A);
let cache_manager = CacheManager::new(base_dir.clone(), "profile/pasta/cache/lua");
cache_manager
.prepare_cache_dir()
.expect("prepare cache dir");
let _source_map =
PastaLoader::build_source_map(std::slice::from_ref(&file_a), &cache_manager, false);
let lua_path = cache_manager.source_to_cache_path(&file_a);
let sidecar = sidecar_path_for_lua(&lua_path);
assert!(
!sidecar.exists(),
"サイドカー無効時は `.lua.map` を出力しない(3.1・既定経路不変): {}",
sidecar.display()
);
}