//! Nyash Plugin Tester //! //! プラグイン開発者向けの診断ツール //! Box名を決め打ちせず、プラグインから取得する use clap::Parser; use colored::*; use libloading::{Library, Symbol}; use std::ffi::{CStr, CString}; use std::os::raw::{c_char, c_void}; use std::path::PathBuf; // ============ FFI Types (プラグインと同じ定義) ============ #[repr(C)] pub struct NyashHostVtable { pub alloc: unsafe extern "C" fn(size: usize) -> *mut u8, pub free: unsafe extern "C" fn(ptr: *mut u8), pub wake: unsafe extern "C" fn(handle: u64), pub log: unsafe extern "C" fn(level: i32, msg: *const c_char), } #[repr(C)] pub struct NyashMethodInfo { pub method_id: u32, pub name: *const c_char, pub signature: u32, } #[repr(C)] pub struct NyashPluginInfo { pub type_id: u32, pub type_name: *const c_char, pub method_count: usize, pub methods: *const NyashMethodInfo, } // ============ CLI Arguments ============ #[derive(Parser, Debug)] #[command(name = "plugin-tester")] #[command(about = "Nyash plugin diagnostic tool", long_about = None)] struct Args { /// Action to perform #[command(subcommand)] command: Commands, } #[derive(clap::Subcommand, Debug)] enum Commands { /// Check plugin and display information Check { /// Path to plugin .so file plugin: PathBuf, }, /// Test plugin lifecycle (birth/fini) Lifecycle { /// Path to plugin .so file plugin: PathBuf, }, } // ============ Host Functions (テスト用実装) ============ unsafe extern "C" fn test_alloc(size: usize) -> *mut u8 { let layout = std::alloc::Layout::from_size_align(size, 8).unwrap(); std::alloc::alloc(layout) } unsafe extern "C" fn test_free(ptr: *mut u8) { if !ptr.is_null() { // サイズ情報が必要だが、簡易実装のため省略 } } unsafe extern "C" fn test_wake(_handle: u64) { // テスト用なので何もしない } unsafe extern "C" fn test_log(level: i32, msg: *const c_char) { if !msg.is_null() { let c_str = CStr::from_ptr(msg); let message = c_str.to_string_lossy(); match level { 0 => println!("{}: {}", "DEBUG".blue(), message), 1 => println!("{}: {}", "INFO".green(), message), 2 => println!("{}: {}", "WARN".yellow(), message), 3 => println!("{}: {}", "ERROR".red(), message), _ => println!("{}: {}", "UNKNOWN".white(), message), } } } static HOST_VTABLE: NyashHostVtable = NyashHostVtable { alloc: test_alloc, free: test_free, wake: test_wake, log: test_log, }; // ============ Main Functions ============ fn main() { let args = Args::parse(); match args.command { Commands::Check { plugin } => check_plugin(&plugin), Commands::Lifecycle { plugin } => test_lifecycle(&plugin), } } fn check_plugin(path: &PathBuf) { println!("{}", "=== Nyash Plugin Checker ===".bold()); println!("Plugin: {}", path.display()); // プラグインをロード let library = match unsafe { Library::new(path) } { Ok(lib) => lib, Err(e) => { eprintln!("{}: Failed to load plugin: {}", "ERROR".red(), e); return; } }; println!("{}: Plugin loaded successfully", "✓".green()); // ABI version確認 unsafe { let abi_fn: Symbol u32> = match library.get(b"nyash_plugin_abi") { Ok(f) => f, Err(e) => { eprintln!("{}: nyash_plugin_abi not found: {}", "ERROR".red(), e); return; } }; let abi_version = abi_fn(); println!("{}: ABI version: {}", "✓".green(), abi_version); if abi_version != 1 { eprintln!("{}: Unsupported ABI version (expected 1)", "WARNING".yellow()); } } // Plugin初期化とBox名取得 unsafe { let init_fn: Symbol i32> = match library.get(b"nyash_plugin_init") { Ok(f) => f, Err(e) => { eprintln!("{}: nyash_plugin_init not found: {}", "ERROR".red(), e); return; } }; let mut plugin_info = std::mem::zeroed::(); let result = init_fn(&HOST_VTABLE, &mut plugin_info); if result != 0 { eprintln!("{}: nyash_plugin_init failed with code {}", "ERROR".red(), result); return; } println!("{}: Plugin initialized", "✓".green()); // 重要:Box名をプラグインから取得(決め打ちしない!) let box_name = if plugin_info.type_name.is_null() { "".to_string() } else { CStr::from_ptr(plugin_info.type_name).to_string_lossy().to_string() }; println!("\n{}", "Plugin Information:".bold()); println!(" Box Type: {} (ID: {})", box_name.cyan(), plugin_info.type_id); println!(" Methods: {}", plugin_info.method_count); // メソッド一覧表示 if plugin_info.method_count > 0 && !plugin_info.methods.is_null() { println!("\n{}", "Methods:".bold()); let methods = std::slice::from_raw_parts(plugin_info.methods, plugin_info.method_count); for method in methods { let method_name = if method.name.is_null() { "".to_string() } else { CStr::from_ptr(method.name).to_string_lossy().to_string() }; let method_type = match method.method_id { 0 => " (constructor)".yellow(), id if id == u32::MAX => " (destructor)".yellow(), _ => "".normal(), }; println!(" - {} [ID: {}, Sig: 0x{:08X}]{}", method_name, method.method_id, method.signature, method_type ); } } } // シャットダウン unsafe { if let Ok(shutdown_fn) = library.get::>(b"nyash_plugin_shutdown") { shutdown_fn(); println!("\n{}: Plugin shutdown completed", "✓".green()); } } println!("\n{}", "Check completed!".green().bold()); } fn test_lifecycle(path: &PathBuf) { println!("{}", "=== Lifecycle Test ===".bold()); println!("Testing birth/fini for: {}", path.display()); // TODO: birth/finiのテスト実装 println!("{}: Lifecycle test not yet implemented", "TODO".yellow()); }