package kernel import ( "errors" "sync/atomic" "testing" "time" "github.com/orca/orca/pkg/bus" "github.com/orca/orca/pkg/plugin" ) // testPlugin implements Plugin for kernel testing. type testPlugin struct { name string version string initFn func(host plugin.PluginHost) error closeFn func() error } func (p *testPlugin) Name() string { return p.name } func (p *testPlugin) Version() string { return p.version } func (p *testPlugin) Init(host plugin.PluginHost) error { if p.initFn != nil { return p.initFn(host) } return nil } func (p *testPlugin) Shutdown() error { if p.closeFn != nil { return p.closeFn() } return nil } func TestNewKernel(t *testing.T) { k := New() if k == nil { t.Fatal("New() returned nil") } if k.Bus() == nil { t.Error("Bus() returned nil") } if k.Registry() == nil { t.Error("Registry() returned nil") } } func TestKernelStartStop(t *testing.T) { k := New() if err := k.Start(); err != nil { t.Fatalf("Start failed: %v", err) } if !k.IsRunning() { t.Error("expected kernel running after Start") } if err := k.Stop(); err != nil { t.Fatalf("Stop failed: %v", err) } if k.IsRunning() { t.Error("expected kernel stopped after Stop") } } func TestKernelDoubleStart(t *testing.T) { k := New() k.Start() err := k.Start() if err == nil { t.Error("expected error on double start") } k.Stop() } func TestKernelRegisterPlugin(t *testing.T) { k := New() p := &testPlugin{name: "test", version: "1.0.0"} err := k.RegisterPlugin(p) if err != nil { t.Fatalf("RegisterPlugin failed: %v", err) } got, ok := k.GetPlugin("test") if !ok { t.Fatal("GetPlugin returned not found") } if got.Name() != "test" { t.Errorf("expected name 'test', got %q", got.Name()) } } func TestKernelRegisterPluginAfterStart(t *testing.T) { k := New() k.Start() defer k.Stop() err := k.RegisterPlugin(&testPlugin{name: "test", version: "1.0.0"}) if err == nil { t.Error("expected error registering plugin after start") } } func TestKernelPluginLifecycle(t *testing.T) { k := New() var initCount int32 var shutdownCount int32 p := &testPlugin{ name: "lifecycle", version: "1.0.0", initFn: func(host plugin.PluginHost) error { atomic.AddInt32(&initCount, 1) return nil }, closeFn: func() error { atomic.AddInt32(&shutdownCount, 1) return nil }, } k.RegisterPlugin(p) k.Start() if n := atomic.LoadInt32(&initCount); n != 1 { t.Errorf("expected init called once, got %d", n) } k.Stop() if n := atomic.LoadInt32(&shutdownCount); n != 1 { t.Errorf("expected shutdown called once, got %d", n) } } func TestKernelPluginInitFailure(t *testing.T) { k := New() p := &testPlugin{ name: "failing", version: "1.0.0", initFn: func(host plugin.PluginHost) error { return errors.New("init failed") }, } k.RegisterPlugin(p) // Init failure should not prevent Start from succeeding (graceful degradation) err := k.Start() if err != nil { t.Fatalf("Start should succeed even with failing plugin: %v", err) } k.Stop() } func TestKernelPluginShutdownFailure(t *testing.T) { k := New() p := &testPlugin{ name: "failing-shutdown", version: "1.0.0", initFn: func(host plugin.PluginHost) error { return nil }, closeFn: func() error { return errors.New("shutdown failed") }, } k.RegisterPlugin(p) k.Start() // Shutdown failure should not prevent Stop from succeeding err := k.Stop() if err != nil { t.Fatalf("Stop should succeed even with failing plugin shutdown: %v", err) } } func TestKernelMultiplePlugins(t *testing.T) { k := New() names := []string{"alpha", "beta", "gamma"} for _, name := range names { k.RegisterPlugin(&testPlugin{name: name, version: "1.0.0"}) } k.Start() plugins := k.ListPlugins() if len(plugins) != len(names) { t.Errorf("expected %d plugins, got %d", len(names), len(plugins)) } k.Stop() } func TestKernelUnregisterPlugin(t *testing.T) { k := New() k.RegisterPlugin(&testPlugin{name: "remove-me", version: "1.0.0"}) err := k.UnregisterPlugin("remove-me") if err != nil { t.Fatalf("UnregisterPlugin failed: %v", err) } _, ok := k.GetPlugin("remove-me") if ok { t.Error("plugin should not exist after unregister") } } func TestKernelStopWithoutStart(t *testing.T) { k := New() err := k.Stop() if err != nil { t.Fatalf("Stop without Start should be a no-op: %v", err) } } func TestKernelPluginReceivesHost(t *testing.T) { k := New() var gotHost plugin.PluginHost p := &testPlugin{ name: "host-check", version: "1.0.0", initFn: func(host plugin.PluginHost) error { gotHost = host return nil }, } k.RegisterPlugin(p) k.Start() if gotHost == nil { t.Fatal("plugin did not receive PluginHost") } // Verify the host can access bus if gotHost.Bus() == nil { t.Error("PluginHost.Bus() returned nil") } // Verify plugin discovery through host p2, ok := gotHost.GetPlugin("host-check") if !ok { t.Error("PluginHost.GetPlugin should find itself") } if p2.Name() != "host-check" { t.Errorf("expected name 'host-check', got %q", p2.Name()) } k.Stop() } func TestKernelAllPluginsInitialized(t *testing.T) { k := New() names := []string{"a", "b", "c"} initialized := make(map[string]bool) for _, name := range names { n := name k.RegisterPlugin(&testPlugin{ name: n, initFn: func(host plugin.PluginHost) error { initialized[n] = true return nil }, }) } k.Start() for _, name := range names { if !initialized[name] { t.Errorf("plugin %q was not initialized", name) } } k.Stop() } func TestKernelShutdownAllPlugins(t *testing.T) { k := New() names := []string{"x", "y", "z"} shutdown := make(map[string]bool) for _, name := range names { n := name k.RegisterPlugin(&testPlugin{ name: n, initFn: func(host plugin.PluginHost) error { return nil }, closeFn: func() error { shutdown[n] = true return nil }, }) } k.Start() k.Stop() for _, name := range names { if !shutdown[name] { t.Errorf("plugin %q was not shut down", name) } } } func TestKernelMessageBusIntegration(t *testing.T) { k := New() k.Start() defer k.Stop() mb := k.Bus() var received int32 sub, err := mb.Subscribe("kernel-test", func(msg bus.Message) { atomic.AddInt32(&received, 1) }) if err != nil { t.Fatalf("Subscribe failed: %v", err) } defer sub.Unsubscribe() mb.Publish("kernel-test", bus.Message{ID: "test-msg"}) time.Sleep(50 * time.Millisecond) if n := atomic.LoadInt32(&received); n != 1 { t.Errorf("expected 1 message via kernel bus, got %d", n) } }