package lua import ( "os" "path/filepath" "strings" "testing" ) // A plugin hook that RAISES must not take the process down. This is the // production outage: a Lua error anywhere in a hook made golua's callEx call // L.StackTrace(), which calls lua_getinfo and SIGSEGVs on a deep enough stack — // a C-level signal Go cannot recover from, so one buggy plugin killed the whole // gateway and took every in-flight request with it. // // The fix routes hook calls through a Lua-side pcall guard, so the error comes // back as an ordinary return value. This test fires hooks that raise on purpose // and asserts three things: the process survives, the failure is RECORDED, and // a well-behaved plugin on the same state keeps working afterwards (the error // must not poison the Lua state). func TestHookThatRaisesDoesNotCrashTheProcess(t *testing.T) { ps, pdir := billingVM(t) boom := ` local plugin = {} plugin.name = "boom" plugin.version = "0.1" function plugin.request_end(payload) -- Raise on a table index, the exact shape of the billing bug that caused the -- outage. Deliberately NOT a syntax error: this must load fine and fail only -- when invoked. local x = nil return x.field end return plugin` if err := os.WriteFile(filepath.Join(pdir, "boom.lua"), []byte(boom), 0644); err != nil { t.Fatal(err) } if err := ps.LoadSource("boom", boom); err != nil { t.Fatalf("load boom: %v", err) } payload := map[string]interface{}{ "model": "m", "source": "s", "ok": true, "prompt_tokens": 100, "completion_tokens": 10, "time": 1750000000000, } // Fire many times: a single call could pass by luck, but if the error ever // escapes into golua's C path the process dies and this test never returns. for i := 0; i < 50; i++ { ps.Fire(StageRequestEnd, payload) } // Reaching this line at all is the primary assertion. errs := ps.HookErrors() end, ok := errs[string(StageRequestEnd)] if !ok { t.Fatal("a raising hook left no record — failures must be observable, not swallowed") } if end["count"] == nil || end["count"].(int) == 0 { t.Error("hook error count is zero despite 50 raising calls") } msg, _ := end["last_error"].(string) if !strings.Contains(msg, "boom") { t.Errorf("last_error does not name the offending plugin: %q", msg) } // The billing plugin shares the same Plugins registry and must still work: // one broken plugin may not disable the others. st, _ := ps.State("billing").(map[string]interface{}) if st == nil || st["total"] == nil { t.Fatalf("the healthy plugin stopped working after another plugin raised") } tot, _ := st["total"].(map[string]interface{}) if tot == nil || tot["requests"] == nil || tot["requests"].(float64) == 0 { t.Errorf("billing recorded no requests after the raising plugin ran: %v", st["total"]) } }