yao/sandbox/v2/bench_test.go
Max d25bfefee0 Enhance Sandbox V2 testing and integration with Workspace
- Introduce new unit test targets for Workspace and Sandbox V2 integration in the Makefile, ensuring comprehensive testing of the new features.
- Update CI workflows to support MongoDB service for Sandbox V2 tests and improve Docker image handling.
- Implement Workspace as a first-class entity, allowing for persistent storage decoupled from container lifecycles.
- Enhance the Box struct to manage workspace IDs and update related methods for improved functionality.
- Refactor tests to accommodate new Workspace features, ensuring robust testing of the integration.

These changes significantly improve the testing framework and functionality of the Sandbox V2, enhancing the overall architecture and user experience.
2026-03-05 21:00:50 +08:00

254 lines
6.2 KiB
Go

package sandbox_test
import (
"context"
"fmt"
"testing"
"time"
sandbox "github.com/yaoapp/yao/sandbox/v2"
)
// BenchmarkContainerLifecycle measures the full Create → Exec → Remove cycle.
func BenchmarkContainerLifecycle(b *testing.B) {
for _, pc := range testPools() {
b.Run(pc.Name, func(b *testing.B) {
m := setupManagerForBench(b, pc)
ensureTestImageBench(b, m, pc.Name)
b.ResetTimer()
for i := 0; i < b.N; i++ {
ctx := context.Background()
box, err := m.Create(ctx, sandbox.CreateOptions{
Image: testImage(),
Owner: "bench",
})
if err != nil {
b.Fatalf("Create: %v", err)
}
_, err = box.Exec(ctx, []string{"echo", "ok"})
if err != nil {
b.Fatalf("Exec: %v", err)
}
m.Remove(ctx, box.ID())
}
})
}
}
// BenchmarkCreate measures container creation time only.
func BenchmarkCreate(b *testing.B) {
for _, pc := range testPools() {
b.Run(pc.Name, func(b *testing.B) {
m := setupManagerForBench(b, pc)
ensureTestImageBench(b, m, pc.Name)
ids := make([]string, 0, b.N)
b.ResetTimer()
for i := 0; i < b.N; i++ {
box, err := m.Create(context.Background(), sandbox.CreateOptions{
Image: testImage(),
Owner: "bench",
})
if err != nil {
b.Fatalf("Create: %v", err)
}
ids = append(ids, box.ID())
}
b.StopTimer()
for _, id := range ids {
m.Remove(context.Background(), id)
}
})
}
}
// BenchmarkExec measures command execution latency on a pre-created container.
func BenchmarkExec(b *testing.B) {
for _, pc := range testPools() {
b.Run(pc.Name, func(b *testing.B) {
m := setupManagerForBench(b, pc)
box := createBoxForBench(b, m)
b.ResetTimer()
for i := 0; i < b.N; i++ {
result, err := box.Exec(context.Background(), []string{"echo", "bench"})
if err != nil {
b.Fatalf("Exec: %v", err)
}
if result.ExitCode != 0 {
b.Fatalf("exit code = %d", result.ExitCode)
}
}
})
}
}
// BenchmarkExecHeavy measures execution of a heavier command (write + read file).
func BenchmarkExecHeavy(b *testing.B) {
for _, pc := range testPools() {
b.Run(pc.Name, func(b *testing.B) {
m := setupManagerForBench(b, pc)
box := createBoxForBench(b, m)
b.ResetTimer()
for i := 0; i < b.N; i++ {
cmd := []string{"sh", "-c", fmt.Sprintf("echo bench-%d > /tmp/b.txt && cat /tmp/b.txt", i)}
result, err := box.Exec(context.Background(), cmd)
if err != nil {
b.Fatalf("Exec: %v", err)
}
if result.ExitCode != 0 {
b.Fatalf("exit code = %d", result.ExitCode)
}
}
})
}
}
// BenchmarkRemove measures container removal time.
func BenchmarkRemove(b *testing.B) {
for _, pc := range testPools() {
b.Run(pc.Name, func(b *testing.B) {
m := setupManagerForBench(b, pc)
ensureTestImageBench(b, m, pc.Name)
boxes := make([]*sandbox.Box, b.N)
for i := 0; i < b.N; i++ {
box, err := m.Create(context.Background(), sandbox.CreateOptions{
Image: testImage(),
Owner: "bench",
})
if err != nil {
b.Fatalf("Create: %v", err)
}
boxes[i] = box
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := m.Remove(context.Background(), boxes[i].ID()); err != nil {
b.Fatalf("Remove: %v", err)
}
}
})
}
}
// BenchmarkInfo measures Info() latency on a running container.
func BenchmarkInfo(b *testing.B) {
for _, pc := range testPools() {
b.Run(pc.Name, func(b *testing.B) {
m := setupManagerForBench(b, pc)
box := createBoxForBench(b, m)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := box.Info(context.Background())
if err != nil {
b.Fatalf("Info: %v", err)
}
}
})
}
}
// BenchmarkStopStart measures Stop → Start cycle time.
func BenchmarkStopStart(b *testing.B) {
for _, pc := range testPools() {
b.Run(pc.Name, func(b *testing.B) {
if pc.Name == "k8s" {
b.Skip("K8s Stop deletes Pod; Stop→Start cycle not applicable")
}
m := setupManagerForBench(b, pc)
box := createBoxForBench(b, m)
b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := box.Stop(context.Background()); err != nil {
b.Fatalf("Stop: %v", err)
}
if err := box.Start(context.Background()); err != nil {
b.Fatalf("Start: %v", err)
}
}
})
}
}
// BenchmarkWorkspaceReadWrite measures workspace file read/write via container Box.
func BenchmarkWorkspaceReadWrite(b *testing.B) {
for _, pc := range testPools() {
b.Run(pc.Name, func(b *testing.B) {
m := setupManagerForBench(b, pc)
box := createBoxForBench(b, m)
ws := box.Workspace()
if ws == nil {
b.Skip("workspace not available")
}
payload := []byte("package main\nfunc main() { println(\"hello\") }\n")
b.ResetTimer()
for i := 0; i < b.N; i++ {
name := fmt.Sprintf("f%d.go", i)
if err := ws.WriteFile(name, payload, 0644); err != nil {
b.Fatalf("WriteFile: %v", err)
}
data, err := ws.ReadFile(name)
if err != nil {
b.Fatalf("ReadFile: %v", err)
}
if len(data) != len(payload) {
b.Fatalf("size mismatch: %d vs %d", len(data), len(payload))
}
}
})
}
}
// --- helpers ---
func setupManagerForBench(b *testing.B, pc poolConfig) *sandbox.Manager {
b.Helper()
pool := sandbox.Pool{Name: pc.Name, Addr: pc.Addr, Options: pc.Options}
cfg := sandbox.Config{Pool: []sandbox.Pool{pool}}
if err := sandbox.Init(cfg); err != nil {
b.Fatalf("Init: %v", err)
}
m := sandbox.M()
b.Cleanup(func() { m.Close() })
return m
}
func ensureTestImageBench(b *testing.B, m *sandbox.Manager, pool string) {
b.Helper()
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()
if err := m.EnsureImage(ctx, pool, testImage(), sandbox.ImagePullOptions{}); err != nil {
b.Fatalf("EnsureImage: %v", err)
}
}
func createBoxForBench(b *testing.B, m *sandbox.Manager) *sandbox.Box {
b.Helper()
pools := m.Pools()
if len(pools) > 0 {
ensureTestImageBench(b, m, pools[0].Name)
}
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
defer cancel()
box, err := m.Create(ctx, sandbox.CreateOptions{
Image: testImage(),
Owner: "bench",
})
if err != nil {
b.Fatalf("Create: %v", err)
}
b.Cleanup(func() { m.Remove(context.Background(), box.ID()) })
return box
}