- Implemented V2 sandbox initialization in the assistant loading process, allowing for standalone sandbox.yao configuration. - Added support for V2 sandbox execution paths in the Assistant's Stream method, differentiating between V1 and V2 sandboxes. - Introduced comprehensive tests for V2 sandbox configurations, ensuring correct loading and execution behavior. - Updated the context and types to accommodate V2 sandbox features, including system information and workspace management. Made-with: Cursor
228 lines
5.7 KiB
Go
228 lines
5.7 KiB
Go
package sandboxv2_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
sandbox "github.com/yaoapp/yao/sandbox/v2"
|
|
"github.com/yaoapp/yao/tai"
|
|
"github.com/yaoapp/yao/tai/registry"
|
|
taisandbox "github.com/yaoapp/yao/tai/sandbox"
|
|
"github.com/yaoapp/yao/workspace"
|
|
)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// node configuration — mirrors sandbox/v2 testutils but scoped to prepare tests
|
|
// ---------------------------------------------------------------------------
|
|
|
|
type nodeConfig struct {
|
|
Name string
|
|
Addr string
|
|
TaiID string
|
|
Options []tai.Option
|
|
}
|
|
|
|
type hostTarget struct {
|
|
Name string
|
|
Addr string
|
|
TaiID string
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// environment helpers (same conventions as sandbox/v2 + env.local.sh)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func testLocalAddr() string {
|
|
if addr := os.Getenv("SANDBOX_TEST_LOCAL_ADDR"); addr != "" {
|
|
return addr
|
|
}
|
|
return "local"
|
|
}
|
|
|
|
func testImage() string {
|
|
if img := os.Getenv("SANDBOX_TEST_IMAGE"); img != "" {
|
|
return img
|
|
}
|
|
return "alpine:latest"
|
|
}
|
|
|
|
func envPort(key string, fallback int) int {
|
|
if v := os.Getenv(key); v != "" {
|
|
if p, err := strconv.Atoi(v); err == nil {
|
|
return p
|
|
}
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// node discovery
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func boxNodes() []nodeConfig {
|
|
nodes := []nodeConfig{
|
|
{Name: "local", Addr: testLocalAddr()},
|
|
}
|
|
if addr := os.Getenv("SANDBOX_TEST_REMOTE_ADDR"); addr != "" {
|
|
nodes = append(nodes, nodeConfig{Name: "remote", Addr: addr})
|
|
}
|
|
return nodes
|
|
}
|
|
|
|
func hostTargets() []hostTarget {
|
|
var targets []hostTarget
|
|
if addr := os.Getenv("TAI_TEST_WIN_HOSTEXEC_LINUX"); addr != "" {
|
|
targets = append(targets, hostTarget{Name: "win-linux", Addr: addr})
|
|
}
|
|
if addr := os.Getenv("TAI_TEST_WIN_HOSTEXEC_NATIVE"); addr != "" {
|
|
targets = append(targets, hostTarget{Name: "win-native", Addr: addr})
|
|
}
|
|
return targets
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// TestMain — purge stale containers from previous runs
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func TestMain(m *testing.M) {
|
|
purgeStale()
|
|
os.Exit(m.Run())
|
|
}
|
|
|
|
func purgeStale() {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
for _, nc := range boxNodes() {
|
|
client, err := tai.New(nc.Addr, nc.Options...)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
sb := client.Sandbox()
|
|
if sb == nil {
|
|
client.Close()
|
|
continue
|
|
}
|
|
containers, _ := sb.List(ctx, taisandbox.ListOptions{All: true})
|
|
for _, c := range containers {
|
|
id := c.Name
|
|
if id == "" {
|
|
id = c.ID
|
|
}
|
|
if strings.HasPrefix(id, "sb-prep-") || strings.HasPrefix(id, "sb-lc-") {
|
|
sb.Remove(ctx, id, true)
|
|
log.Printf("[purge] %s: removed %s", nc.Name, id)
|
|
}
|
|
}
|
|
client.Close()
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Manager + Box helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func setupManager(t *testing.T, nc *nodeConfig) *sandbox.Manager {
|
|
t.Helper()
|
|
if registry.Global() == nil {
|
|
registry.Init(nil)
|
|
}
|
|
client, err := tai.New(nc.Addr, nc.Options...)
|
|
if err != nil {
|
|
t.Fatalf("tai.New(%s): %v", nc.Addr, err)
|
|
}
|
|
nc.TaiID = client.TaiID()
|
|
|
|
sandbox.Init()
|
|
m := sandbox.M()
|
|
t.Cleanup(func() { m.Close() })
|
|
return m
|
|
}
|
|
|
|
func createBox(t *testing.T, m *sandbox.Manager, nc nodeConfig) *sandbox.Box {
|
|
t.Helper()
|
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
|
defer cancel()
|
|
|
|
if err := m.EnsureImage(ctx, nc.TaiID, testImage(), sandbox.ImagePullOptions{}); err != nil {
|
|
t.Fatalf("EnsureImage: %v", err)
|
|
}
|
|
|
|
box, err := m.Create(ctx, sandbox.CreateOptions{
|
|
ID: fmt.Sprintf("sb-prep-%d", time.Now().UnixNano()),
|
|
Image: testImage(),
|
|
Owner: "test-prepare",
|
|
NodeID: nc.TaiID,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Create: %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
cCtx, cCancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cCancel()
|
|
if err := m.Remove(cCtx, box.ID()); err != nil {
|
|
t.Logf("cleanup Remove(%s): %v", box.ID(), err)
|
|
}
|
|
})
|
|
return box
|
|
}
|
|
|
|
func createHost(t *testing.T, m *sandbox.Manager, tgt hostTarget) *sandbox.Host {
|
|
t.Helper()
|
|
host, err := m.Host(context.Background(), tgt.TaiID)
|
|
if err != nil {
|
|
t.Skipf("Host(%s): %v", tgt.Name, err)
|
|
}
|
|
return host
|
|
}
|
|
|
|
func setupHostManager(t *testing.T, tgt *hostTarget) *sandbox.Manager {
|
|
t.Helper()
|
|
nc := nodeConfig{Name: tgt.Name, Addr: fmt.Sprintf("tai://%s", tgt.Addr)}
|
|
m := setupManager(t, &nc)
|
|
tgt.TaiID = nc.TaiID
|
|
return m
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// skip helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func skipIfNoDocker(t *testing.T) {
|
|
t.Helper()
|
|
if testLocalAddr() == "" {
|
|
t.Skip("SANDBOX_TEST_LOCAL_ADDR not set")
|
|
}
|
|
}
|
|
|
|
func skipIfNoHostExec(t *testing.T) {
|
|
t.Helper()
|
|
if len(hostTargets()) == 0 {
|
|
t.Skip("no HostExec targets configured")
|
|
}
|
|
}
|
|
|
|
func createTestWorkspace(t *testing.T, taiID, wsID string) {
|
|
t.Helper()
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
_, err := workspace.M().Create(ctx, workspace.CreateOptions{
|
|
ID: wsID,
|
|
Owner: "test",
|
|
Node: taiID,
|
|
})
|
|
if err != nil && !strings.Contains(err.Error(), "exists") {
|
|
t.Fatalf("create workspace %q: %v", wsID, err)
|
|
}
|
|
t.Cleanup(func() {
|
|
cCtx, cCancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cCancel()
|
|
workspace.M().Delete(cCtx, wsID, true)
|
|
})
|
|
}
|