orca.ai/pkg/kernel/kernel_test.go
大森 6b94476347 Initial commit: Orca Agent Framework
Core features:
- Microkernel architecture with Actor model
- Session management with JSONL persistence
- Tool system (5 built-in tools)
- Skill system with SKILL.md parsing
- Sandbox security execution
- Ollama integration with gemma4:e4b
- Prompt-based tool calling (compatible with native function calling)
- REPL interface

11 packages, all tests passing
2026-05-08 00:55:48 +08:00

344 lines
6.5 KiB
Go

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)
}
}