test: Add HTTP server restart and shutdown E2E tests

- Add e2e_http_server_restart: Verify server can stop/start and handle requests (returns 'B')
- Add e2e_http_server_shutdown_and_restart: Verify plugin shutdown/reinit works (returns 'Y')
- Fix active server tracking with ACTIVE_SERVER_ID for proper request routing
- All 3 HTTP plugin E2E tests now passing 

HTTPサーバー再起動とシャットダウンテスト追加
- サーバーのstop/start再起動が正常動作
- プラグインshutdown後の再初期化も確認
- アクティブサーバー管理で適切なリクエストルーティング実現

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Moe Charm
2025-08-21 23:02:05 +09:00
parent eae7f54b13
commit 26c3d153e2
2 changed files with 89 additions and 3 deletions

View File

@ -45,6 +45,7 @@ const M_CLIENT_GET: u32 = 1; // arg: url -> Handle(Response)
// Global State // Global State
static SERVER_INSTANCES: Lazy<Mutex<HashMap<u32, ServerState>>> = Lazy::new(|| Mutex::new(HashMap::new())); static SERVER_INSTANCES: Lazy<Mutex<HashMap<u32, ServerState>>> = Lazy::new(|| Mutex::new(HashMap::new()));
static ACTIVE_SERVER_ID: Lazy<Mutex<Option<u32>>> = Lazy::new(|| Mutex::new(None));
static REQUESTS: Lazy<Mutex<HashMap<u32, RequestState>>> = Lazy::new(|| Mutex::new(HashMap::new())); static REQUESTS: Lazy<Mutex<HashMap<u32, RequestState>>> = Lazy::new(|| Mutex::new(HashMap::new()));
static RESPONSES: Lazy<Mutex<HashMap<u32, ResponseState>>> = Lazy::new(|| Mutex::new(HashMap::new())); static RESPONSES: Lazy<Mutex<HashMap<u32, ResponseState>>> = Lazy::new(|| Mutex::new(HashMap::new()));
static CLIENTS: Lazy<Mutex<HashMap<u32, ClientState>>> = Lazy::new(|| Mutex::new(HashMap::new())); static CLIENTS: Lazy<Mutex<HashMap<u32, ClientState>>> = Lazy::new(|| Mutex::new(HashMap::new()));
@ -114,12 +115,17 @@ unsafe fn server_invoke(m: u32, id: u32, args: *const u8, args_len: usize, res:
if let Some(s) = SERVER_INSTANCES.lock().unwrap().get_mut(&id) { if let Some(s) = SERVER_INSTANCES.lock().unwrap().get_mut(&id) {
s.running = true; s.port = port; s.running = true; s.port = port;
} }
// mark active server
*ACTIVE_SERVER_ID.lock().unwrap() = Some(id);
write_tlv_void(res, res_len) write_tlv_void(res, res_len)
} }
M_SERVER_STOP => { M_SERVER_STOP => {
if let Some(s) = SERVER_INSTANCES.lock().unwrap().get_mut(&id) { if let Some(s) = SERVER_INSTANCES.lock().unwrap().get_mut(&id) {
s.running = false; s.running = false;
} }
// clear active if this server was active
let mut active = ACTIVE_SERVER_ID.lock().unwrap();
if active.map(|v| v == id).unwrap_or(false) { *active = None; }
write_tlv_void(res, res_len) write_tlv_void(res, res_len)
} }
M_SERVER_ACCEPT => { M_SERVER_ACCEPT => {
@ -228,9 +234,11 @@ unsafe fn client_invoke(m: u32, id: u32, args: *const u8, args_len: usize, res:
// Create Request // Create Request
let req_id = REQUEST_ID.fetch_add(1, Ordering::Relaxed); let req_id = REQUEST_ID.fetch_add(1, Ordering::Relaxed);
REQUESTS.lock().unwrap().insert(req_id, RequestState { path, body: vec![], response_id: None }); REQUESTS.lock().unwrap().insert(req_id, RequestState { path, body: vec![], response_id: None });
// Enqueue to server 1 (singleton) if present // Enqueue to last started (active) server if running
if let Some((_sid, s)) = SERVER_INSTANCES.lock().unwrap().iter_mut().next() { if let Some(sid) = *ACTIVE_SERVER_ID.lock().unwrap() {
s.pending.push_back(req_id); if let Some(s) = SERVER_INSTANCES.lock().unwrap().get_mut(&sid) {
if s.running { s.pending.push_back(req_id); }
}
} }
// Create Response handle for client side to read later // Create Response handle for client side to read later
let resp_id = RESPONSE_ID.fetch_add(1, Ordering::Relaxed); let resp_id = RESPONSE_ID.fetch_add(1, Ordering::Relaxed);

View File

@ -46,3 +46,81 @@ body
assert_eq!(result.to_string_box().value, "OK"); assert_eq!(result.to_string_box().value, "OK");
} }
#[test]
fn e2e_http_server_restart() {
if !try_init_plugins() { return; }
let code = r#"
local srv, cli, r, req, resp, body
srv = new HttpServerBox()
srv.start(8081)
cli = new HttpClientBox()
r = cli.get("http://localhost/test1")
req = srv.accept()
resp = new HttpResponseBox()
resp.write("A")
req.respond(resp)
srv.stop()
srv.start(8081)
r = cli.get("http://localhost/test2")
req = srv.accept()
resp = new HttpResponseBox()
resp.write("B")
req.respond(resp)
body = r.readBody()
body
"#;
let ast = NyashParser::parse_from_string(code).expect("parse failed");
let mut interpreter = nyash_rust::interpreter::NyashInterpreter::new();
let result = interpreter.execute(ast).expect("exec failed");
assert_eq!(result.to_string_box().value, "B");
}
#[test]
fn e2e_http_server_shutdown_and_restart() {
if !try_init_plugins() { return; }
// First run: start and respond
let code1 = r#"
local srv, cli, r, req, resp
srv = new HttpServerBox()
srv.start(8082)
cli = new HttpClientBox()
r = cli.get("http://localhost/first")
req = srv.accept()
resp = new HttpResponseBox()
resp.write("X")
req.respond(resp)
"#;
let ast1 = NyashParser::parse_from_string(code1).expect("parse1");
let mut i1 = nyash_rust::interpreter::NyashInterpreter::new();
i1.execute(ast1).expect("exec1");
// Shutdown plugins (finalize singleton) and re-init
nyash_rust::runtime::plugin_loader_v2::shutdown_plugins_v2().expect("shutdown ok");
assert!(try_init_plugins());
// Second run: ensure fresh instance works
let code2 = r#"
local srv, cli, r, req, resp, body
srv = new HttpServerBox()
srv.start(8083)
cli = new HttpClientBox()
r = cli.get("http://localhost/second")
req = srv.accept()
resp = new HttpResponseBox()
resp.write("Y")
req.respond(resp)
body = r.readBody()
body
"#;
let ast2 = NyashParser::parse_from_string(code2).expect("parse2");
let mut i2 = nyash_rust::interpreter::NyashInterpreter::new();
let result = i2.execute(ast2).expect("exec2");
assert_eq!(result.to_string_box().value, "Y");
}