use super::*;
use std::panic::{self, AssertUnwindSafe};
use std::path::Path;
use crate::runtime::render_host_visible_path;
#[test]
fn managed_io_read_text_decodes_gb18030() {
let lua = Lua::new();
let io_table =
create_vulcan_io_table(&lua, RuntimeTextEncoding::Utf8).expect("create vulcan.io");
let path = std::env::temp_dir().join(format!(
"luaskills_managed_io_gb18030_{}.txt",
std::process::id()
));
let bytes =
encode_runtime_text("中文", RuntimeTextEncoding::Gb18030).expect("encode gb18030 content");
fs::write(&path, bytes).expect("write test file");
lua.globals().set("vio", io_table).expect("set io table");
let script = format!(
"return vio.read_text({}, {{ encoding = 'gb18030' }})",
lua_path_literal(&path)
);
let value: String = lua.load(&script).eval().expect("read text through Lua");
assert_eq!(value, "中文");
let _ = fs::remove_file(path);
}
#[test]
fn managed_io_file_state_recovers_after_poisoned_lock() {
let file = ManagedIoFile::from_read_buffer(
"poisoned-managed-file".to_string(),
ManagedIoOpenMode {
kind: ManagedIoModeKind::Read,
binary: false,
update: false,
},
RuntimeTextEncoding::Utf8,
b"hello".to_vec(),
None,
);
let poison_result = panic::catch_unwind(AssertUnwindSafe(|| {
let _guard = file.state.lock().expect("initial managed file state lock");
panic!("poison managed file state lock for recovery test");
}));
assert!(poison_result.is_err());
assert!(
!file
.is_closed()
.expect("read managed file state after poison")
);
}
#[test]
fn managed_io_compat_state_recovers_after_poisoned_lock() {
let state = Arc::new(Mutex::new(ManagedIoCompatState {
current_input: None,
current_output: None,
}));
let poison_result = panic::catch_unwind(AssertUnwindSafe(|| {
let _guard = state.lock().expect("initial managed io compat state lock");
panic!("poison managed io compat state lock for recovery test");
}));
assert!(poison_result.is_err());
assert!(flush_compat_output(state).expect("flush compat output after poison"));
}
#[test]
fn managed_io_read_text_uses_default_encoding() {
let lua = Lua::new();
let io_table =
create_vulcan_io_table(&lua, RuntimeTextEncoding::Gb18030).expect("create vulcan.io");
let path = std::env::temp_dir().join(format!(
"luaskills_managed_io_default_gb18030_{}.txt",
std::process::id()
));
let bytes = encode_runtime_text("默认编码", RuntimeTextEncoding::Gb18030)
.expect("encode default gb18030 content");
fs::write(&path, bytes).expect("write default encoding test file");
lua.globals().set("vio", io_table).expect("set io table");
let script = format!("return vio.read_text({})", lua_path_literal(&path));
let value: String = lua
.load(&script)
.eval()
.expect("read text through default encoding");
assert_eq!(value, "默认编码");
let _ = fs::remove_file(path);
}
#[test]
fn managed_io_compat_open_reads_all() {
let lua = Lua::new();
let io_table =
create_vulcan_io_table(&lua, RuntimeTextEncoding::Utf8).expect("create vulcan.io");
install_managed_io_compat(&lua, &io_table, RuntimeTextEncoding::Utf8)
.expect("install managed io compat");
let path = std::env::temp_dir().join(format!(
"luaskills_managed_io_compat_{}.txt",
std::process::id()
));
fs::write(&path, "hello").expect("write test file");
let script = format!(
"local f = io.open({}, 'r'); local v = f:read('*a'); f:close(); return v",
lua_path_literal(&path)
);
let value: String = lua.load(&script).eval().expect("read through io.open");
assert_eq!(value, "hello");
let _ = fs::remove_file(path);
}
#[test]
fn managed_io_compat_input_feeds_read() {
let lua = Lua::new();
let io_table =
create_vulcan_io_table(&lua, RuntimeTextEncoding::Utf8).expect("create vulcan.io");
install_managed_io_compat(&lua, &io_table, RuntimeTextEncoding::Utf8)
.expect("install managed io compat");
let path = std::env::temp_dir().join(format!(
"luaskills_managed_io_input_{}.txt",
std::process::id()
));
fs::write(&path, "input-value").expect("write test file");
let script = format!(
"io.input({}); return io.read('*a')",
lua_path_literal(&path)
);
let value: String = lua.load(&script).eval().expect("read through io.input");
assert_eq!(value, "input-value");
let _ = fs::remove_file(path);
}
#[test]
fn managed_io_compat_output_receives_write() {
let lua = Lua::new();
let io_table =
create_vulcan_io_table(&lua, RuntimeTextEncoding::Utf8).expect("create vulcan.io");
install_managed_io_compat(&lua, &io_table, RuntimeTextEncoding::Utf8)
.expect("install managed io compat");
let path = std::env::temp_dir().join(format!(
"luaskills_managed_io_output_{}.txt",
std::process::id()
));
let _ = fs::remove_file(&path);
let script = format!(
"io.output({}); io.write('out', '-', 'value'); io.close(); return true",
lua_path_literal(&path)
);
let value: bool = lua.load(&script).eval().expect("write through io.output");
assert!(value);
assert_eq!(
fs::read_to_string(&path).expect("read output file"),
"out-value"
);
let _ = fs::remove_file(path);
}
#[test]
fn managed_io_compat_tmpfile_supports_update_reads() {
let lua = Lua::new();
let io_table =
create_vulcan_io_table(&lua, RuntimeTextEncoding::Utf8).expect("create vulcan.io");
install_managed_io_compat(&lua, &io_table, RuntimeTextEncoding::Utf8)
.expect("install managed io compat");
let script = "local f = io.tmpfile(); f:write('tmp-value'); f:seek('set', 0); local value = f:read('*a'); local ok = f:close(); return value, ok";
let (value, ok): (String, bool) = lua.load(script).eval().expect("use managed tmpfile");
assert_eq!(value, "tmp-value");
assert!(ok);
}
#[test]
fn managed_io_open_update_mode_supports_seek_read() {
let lua = Lua::new();
let io_table =
create_vulcan_io_table(&lua, RuntimeTextEncoding::Utf8).expect("create vulcan.io");
install_managed_io_compat(&lua, &io_table, RuntimeTextEncoding::Utf8)
.expect("install managed io compat");
let path = std::env::temp_dir().join(format!(
"luaskills_managed_io_update_{}.txt",
std::process::id()
));
let _ = fs::remove_file(&path);
let script = format!(
"local f = io.open({}, 'w+'); f:write('update-value'); f:seek('set', 0); local value = f:read('*a'); f:close(); return value",
lua_path_literal(&path)
);
let value: String = lua.load(&script).eval().expect("use managed update mode");
assert_eq!(value, "update-value");
assert_eq!(
fs::read_to_string(&path).expect("read update mode file"),
"update-value"
);
let _ = fs::remove_file(path);
}
#[test]
fn managed_io_open_append_update_creates_missing_file() {
let lua = Lua::new();
let io_table =
create_vulcan_io_table(&lua, RuntimeTextEncoding::Utf8).expect("create vulcan.io");
let path = std::env::temp_dir().join(format!(
"luaskills_managed_io_append_update_missing_{}.txt",
std::process::id()
));
let _ = fs::remove_file(&path);
lua.globals().set("vio", io_table).expect("set io table");
let script = format!(
"local f = vio.open({}, 'a+'); f:write('append-new'); f:seek('set', 0); local value = f:read('*a'); f:close(); return value",
lua_path_literal(&path)
);
let value: String = lua.load(&script).eval().expect("use append update mode");
assert_eq!(value, "append-new");
assert_eq!(
fs::read_to_string(&path).expect("read append update output"),
"append-new"
);
let _ = fs::remove_file(path);
}
#[test]
fn managed_io_open_append_update_rejects_directory_path() {
let lua = Lua::new();
let io_table =
create_vulcan_io_table(&lua, RuntimeTextEncoding::Utf8).expect("create vulcan.io");
let path = std::env::temp_dir().join(format!(
"luaskills_managed_io_append_update_dir_{}",
std::process::id()
));
let _ = fs::remove_dir_all(&path);
fs::create_dir_all(&path).expect("create append update directory target");
lua.globals().set("vio", io_table).expect("set io table");
let script = format!(
"local ok, err = pcall(function() return vio.open({}, 'a+') end); return ok, tostring(err)",
lua_path_literal(&path)
);
let (ok, error): (bool, String) = lua
.load(&script)
.eval()
.expect("append update directory open should be captured");
assert!(!ok);
assert!(error.contains("vulcan.io.open"));
let _ = fs::remove_dir_all(path);
}
#[test]
fn managed_io_display_text_rejects_invalid_utf8_string() {
let lua = Lua::new();
let invalid_text = LuaValue::String(
lua.create_string([0xff])
.expect("create invalid utf-8 lua string"),
);
let error =
lua_value_to_display_text(invalid_text).expect_err("invalid utf-8 should be rejected");
assert!(
error.to_string().contains("valid UTF-8"),
"unexpected error: {error}"
);
}
fn lua_quote(value: &str) -> String {
format!("{:?}", value)
}
fn lua_path_literal(path: &Path) -> String {
lua_quote(&render_host_visible_path(path))
}