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:
@ -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);
|
||||||
|
|||||||
@ -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");
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user