- [2-1] executePersistent: add defer sess.mu.Unlock() so all return paths release the session mutex. Remove explicit unlock calls. - [2-2] Protect c.sessions map access in Execute with c.mu. Replace c.Stop() in timeout/cancel path with per-session close/kill/delete so only the affected profile session is cleaned up. - [2-3] Add TestCLIExecutePersistentConsecutiveExecutes to verify the same persistent profile can be executed consecutively without blocking. Apply gofmt to all adapter files.
174 lines
5 KiB
Go
174 lines
5 KiB
Go
package adapters_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"iop/apps/node/internal/adapters"
|
|
noderuntime "iop/apps/node/internal/runtime"
|
|
iop "iop/proto/gen/iop"
|
|
)
|
|
|
|
// lifecycleAdapter records Start/Stop calls.
|
|
type lifecycleAdapter struct {
|
|
name string
|
|
started []string
|
|
stopped []string
|
|
log *[]string
|
|
}
|
|
|
|
func (a *lifecycleAdapter) Name() string { return a.name }
|
|
func (a *lifecycleAdapter) Capabilities(_ context.Context) (noderuntime.Capabilities, error) {
|
|
return noderuntime.Capabilities{AdapterName: a.name}, nil
|
|
}
|
|
func (a *lifecycleAdapter) Execute(_ context.Context, _ noderuntime.ExecutionSpec, _ noderuntime.EventSink) error {
|
|
return nil
|
|
}
|
|
func (a *lifecycleAdapter) Start(_ context.Context) error {
|
|
*a.log = append(*a.log, "start:"+a.name)
|
|
return nil
|
|
}
|
|
func (a *lifecycleAdapter) Stop(_ context.Context) error {
|
|
*a.log = append(*a.log, "stop:"+a.name)
|
|
return nil
|
|
}
|
|
|
|
func TestRegistryLifecycle_StartStopOrder(t *testing.T) {
|
|
log := []string{}
|
|
reg := adapters.NewRegistry()
|
|
reg.Register(&lifecycleAdapter{name: "first", log: &log})
|
|
reg.Register(&lifecycleAdapter{name: "second", log: &log})
|
|
|
|
ctx := context.Background()
|
|
if err := reg.Start(ctx); err != nil {
|
|
t.Fatalf("Start: %v", err)
|
|
}
|
|
if err := reg.Stop(ctx); err != nil {
|
|
t.Fatalf("Stop: %v", err)
|
|
}
|
|
|
|
want := []string{"start:first", "start:second", "stop:second", "stop:first"}
|
|
if len(log) != len(want) {
|
|
t.Fatalf("expected %v, got %v", want, log)
|
|
}
|
|
for i, got := range log {
|
|
if got != want[i] {
|
|
t.Fatalf("log[%d]: want %q, got %q", i, want[i], got)
|
|
}
|
|
}
|
|
}
|
|
|
|
// failingLifecycleAdapter starts successfully only once then always errors.
|
|
type failingLifecycleAdapter struct {
|
|
name string
|
|
log *[]string
|
|
}
|
|
|
|
func (a *failingLifecycleAdapter) Name() string { return a.name }
|
|
func (a *failingLifecycleAdapter) Capabilities(_ context.Context) (noderuntime.Capabilities, error) {
|
|
return noderuntime.Capabilities{AdapterName: a.name}, nil
|
|
}
|
|
func (a *failingLifecycleAdapter) Execute(_ context.Context, _ noderuntime.ExecutionSpec, _ noderuntime.EventSink) error {
|
|
return nil
|
|
}
|
|
func (a *failingLifecycleAdapter) Start(_ context.Context) error {
|
|
*a.log = append(*a.log, "start:"+a.name)
|
|
return fmt.Errorf("start failed: %s", a.name)
|
|
}
|
|
func (a *failingLifecycleAdapter) Stop(_ context.Context) error {
|
|
*a.log = append(*a.log, "stop:"+a.name)
|
|
return nil
|
|
}
|
|
|
|
func TestRegistryLifecycle_StartFailureStopsStartedAdapters(t *testing.T) {
|
|
log := []string{}
|
|
reg := adapters.NewRegistry()
|
|
reg.Register(&lifecycleAdapter{name: "first", log: &log})
|
|
reg.Register(&failingLifecycleAdapter{name: "second", log: &log})
|
|
|
|
ctx := context.Background()
|
|
if err := reg.Start(ctx); err == nil {
|
|
t.Fatal("expected Start to return error")
|
|
}
|
|
|
|
want := []string{"start:first", "start:second", "stop:first"}
|
|
if len(log) != len(want) {
|
|
t.Fatalf("expected log %v, got %v", want, log)
|
|
}
|
|
for i, got := range log {
|
|
if got != want[i] {
|
|
t.Fatalf("log[%d]: want %q, got %q", i, want[i], got)
|
|
}
|
|
}
|
|
}
|
|
|
|
// failingStopAdapter stops successfully once then always errors.
|
|
type failingStopAdapter struct {
|
|
name string
|
|
log *[]string
|
|
stopErr error
|
|
}
|
|
|
|
func (a *failingStopAdapter) Name() string { return a.name }
|
|
func (a *failingStopAdapter) Capabilities(_ context.Context) (noderuntime.Capabilities, error) {
|
|
return noderuntime.Capabilities{AdapterName: a.name}, nil
|
|
}
|
|
func (a *failingStopAdapter) Execute(_ context.Context, _ noderuntime.ExecutionSpec, _ noderuntime.EventSink) error {
|
|
return nil
|
|
}
|
|
func (a *failingStopAdapter) Start(_ context.Context) error {
|
|
*a.log = append(*a.log, "start:"+a.name)
|
|
return nil
|
|
}
|
|
func (a *failingStopAdapter) Stop(_ context.Context) error {
|
|
*a.log = append(*a.log, "stop:"+a.name)
|
|
return a.stopErr
|
|
}
|
|
|
|
func TestRegistryLifecycle_StopContinuesOnFailingAdapter(t *testing.T) {
|
|
log := []string{}
|
|
reg := adapters.NewRegistry()
|
|
// first stops successfully, second fails
|
|
reg.Register(&lifecycleAdapter{name: "first", log: &log})
|
|
reg.Register(&failingStopAdapter{name: "second", log: &log, stopErr: fmt.Errorf("stop failed: second")})
|
|
|
|
ctx := context.Background()
|
|
if err := reg.Start(ctx); err != nil {
|
|
t.Fatalf("Start: %v", err)
|
|
}
|
|
|
|
err := reg.Stop(ctx)
|
|
// Should return error because second adapter fails
|
|
if err == nil {
|
|
t.Fatal("expected non-nil error from Stop")
|
|
}
|
|
|
|
// Both adapters should have been stopped regardless of error
|
|
want := []string{"start:first", "start:second", "stop:second", "stop:first"}
|
|
if len(log) != len(want) {
|
|
t.Fatalf("expected log %v, got %v", want, log)
|
|
}
|
|
for i, got := range log {
|
|
if got != want[i] {
|
|
t.Fatalf("log[%d]: want %q, got %q", i, want[i], got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRegistryLifecycle_NonLifecycleAdapterSkipped(t *testing.T) {
|
|
reg, err := adapters.BuildFromPayload(&iop.NodeConfigPayload{}, zap.NewNop())
|
|
if err != nil {
|
|
t.Fatalf("build: %v", err)
|
|
}
|
|
ctx := context.Background()
|
|
// mock adapter has no lifecycle — should not panic or error
|
|
if err := reg.Start(ctx); err != nil {
|
|
t.Fatalf("Start: %v", err)
|
|
}
|
|
if err := reg.Stop(ctx); err != nil {
|
|
t.Fatalf("Stop: %v", err)
|
|
}
|
|
}
|