Files
wehub-resource-sync e0e362d700
SDK Tests / SDK CI (push) Blocked by required conditions
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
SDK Tests / Python SDK Tests (sandbox) (push) Waiting to run
SDK Tests / CLI Quality (push) Waiting to run
SDK Tests / CLI Tests (push) Waiting to run
SDK Tests / Python SDK Quality (code-interpreter) (push) Waiting to run
SDK Tests / Python SDK Quality (sandbox) (push) Waiting to run
SDK Tests / Python SDK Tests (code-interpreter) (push) Waiting to run
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Go SDK Quality And Tests (push) Waiting to run
Deploy Docs Pages / build (push) Has been cancelled
Deploy Docs Pages / deploy (push) Has been cancelled
Real E2E Tests / JavaScript E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Python E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Java E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / C# E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Go E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Real E2E CI (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:39:33 +08:00

348 lines
9.0 KiB
Go

// Copyright 2026 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package e2e
import (
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"os"
"strings"
"testing"
"time"
"github.com/alibaba/OpenSandbox/sdks/sandbox/go"
"github.com/stretchr/testify/require"
)
func getConnectionConfig(t *testing.T) opensandbox.ConnectionConfig {
t.Helper()
domain := os.Getenv("OPENSANDBOX_TEST_DOMAIN")
if domain == "" {
domain = "localhost:8080"
}
protocol := os.Getenv("OPENSANDBOX_TEST_PROTOCOL")
if protocol == "" {
protocol = "http"
}
apiKey := os.Getenv("OPENSANDBOX_TEST_API_KEY")
if apiKey == "" {
apiKey = "e2e-test"
}
useProxy := os.Getenv("OPENSANDBOX_TEST_USE_SERVER_PROXY") == "true"
config := opensandbox.ConnectionConfig{
Domain: domain,
Protocol: protocol,
APIKey: apiKey,
UseServerProxy: useProxy,
}
if useProxy {
config.AuthHeader = "X-API-Key"
}
return config
}
func connectionConfigForStreaming(t *testing.T) opensandbox.ConnectionConfig {
t.Helper()
c := getConnectionConfig(t)
c.RequestTimeout = 3 * time.Minute
return c
}
func getSandboxImage() string {
if img := os.Getenv("OPENSANDBOX_SANDBOX_DEFAULT_IMAGE"); img != "" {
return img
}
return "python:3.11-slim"
}
func createTestSandbox(t *testing.T) (context.Context, *opensandbox.Sandbox) {
t.Helper()
config := connectionConfigForStreaming(t)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
t.Cleanup(cancel)
sb, err := opensandbox.CreateSandbox(ctx, config, opensandbox.SandboxCreateOptions{
Image: getSandboxImage(),
Env: map[string]string{"EXECD_API_GRACE_SHUTDOWN": "3s", "EXECD_JUPYTER_IDLE_POLL_INTERVAL": "200ms"},
})
require.NoError(t, err)
t.Cleanup(func() { sb.Kill(context.Background()) })
return ctx, sb
}
func newExecdClientForSandbox(t *testing.T, ctx context.Context, sb *opensandbox.Sandbox) *opensandbox.ExecdClient {
t.Helper()
endpoint, err := sb.GetEndpoint(ctx, opensandbox.DefaultExecdPort)
require.NoError(t, err)
require.NotEmpty(t, endpoint.Endpoint)
execdURL := endpoint.Endpoint
if !strings.HasPrefix(execdURL, "http") {
execdURL = "http://" + execdURL
}
execdURL = strings.Replace(execdURL, "host.docker.internal", "localhost", 1)
token := ""
if endpoint.Headers != nil {
token = endpoint.Headers["X-EXECD-ACCESS-TOKEN"]
}
return opensandbox.NewExecdClient(execdURL, token)
}
// ---------- Pool E2E helpers ----------
// Pool test constants aligned with Python E2E tests.
const (
poolMaxIdle = 2
poolReconcileInterval = 1 * time.Second
poolPrimaryLockTTL = 4 * time.Second
poolDrainTimeout = 300 * time.Millisecond
poolAwaitTimeout = 2 * time.Minute
)
func getE2eSandboxResource() opensandbox.ResourceLimits {
cpu := os.Getenv("OPENSANDBOX_E2E_SANDBOX_CPU")
if cpu == "" {
cpu = "1"
}
memory := os.Getenv("OPENSANDBOX_E2E_SANDBOX_MEMORY")
if memory == "" {
memory = "2Gi"
}
return opensandbox.ResourceLimits{"cpu": cpu, "memory": memory}
}
func poolTag(prefix string) string {
b := make([]byte, 4)
_, _ = rand.Read(b)
return prefix + "-" + hex.EncodeToString(b)
}
type poolCreateOpts struct {
warmupConcurrency int
warmupSandboxPreparer func(ctx context.Context, sb *opensandbox.Sandbox) error
connectionConfig *opensandbox.ConnectionConfig
degradedThreshold int
warmupReadyTimeout time.Duration
acquireReadyTimeout time.Duration
primaryLockTTL time.Duration
reconcileInterval time.Duration
}
func createTestPool(
t *testing.T,
poolName, ownerID string,
store opensandbox.PoolStateStore,
tag string,
maxIdle int,
opts *poolCreateOpts,
) *opensandbox.DefaultSandboxPool {
t.Helper()
connCfg := connectionConfigForStreaming(t)
warmupConc := 1
degradedThresh := 3
warmupReady := 30 * time.Second
acquireReady := 30 * time.Second
lockTTL := poolPrimaryLockTTL
reconcileInt := poolReconcileInterval
var preparer func(ctx context.Context, sb *opensandbox.Sandbox) error
if opts != nil {
if opts.warmupConcurrency > 0 {
warmupConc = opts.warmupConcurrency
}
if opts.degradedThreshold > 0 {
degradedThresh = opts.degradedThreshold
}
if opts.warmupReadyTimeout > 0 {
warmupReady = opts.warmupReadyTimeout
}
if opts.acquireReadyTimeout > 0 {
acquireReady = opts.acquireReadyTimeout
}
if opts.primaryLockTTL > 0 {
lockTTL = opts.primaryLockTTL
}
if opts.reconcileInterval > 0 {
reconcileInt = opts.reconcileInterval
}
if opts.connectionConfig != nil {
connCfg = *opts.connectionConfig
}
preparer = opts.warmupSandboxPreparer
}
builder := opensandbox.NewSandboxPoolBuilder().
PoolName(poolName).
OwnerID(ownerID).
MaxIdle(maxIdle).
WarmupConcurrency(warmupConc).
StateStore(store).
ConnectionConfig(connCfg).
CreationSpec(opensandbox.PoolCreationSpec{
Image: getSandboxImage(),
Entrypoint: []string{"tail", "-f", "/dev/null"},
Metadata: map[string]string{"tag": tag, "suite": "sandbox-pool-go-e2e"},
Env: map[string]string{
"E2E_TEST": "true",
"EXECD_API_GRACE_SHUTDOWN": "3s",
"EXECD_JUPYTER_IDLE_POLL_INTERVAL": "1s",
},
ResourceLimits: getE2eSandboxResource(),
}).
ReconcileInterval(reconcileInt).
PrimaryLockTTL(lockTTL).
DrainTimeout(poolDrainTimeout).
DegradedThreshold(degradedThresh).
WarmupReadyTimeout(warmupReady).
AcquireReadyTimeout(acquireReady)
if preparer != nil {
builder = builder.WarmupSandboxPreparer(preparer)
}
pool, err := builder.Build()
require.NoError(t, err, "pool build failed")
return pool
}
func eventually(t *testing.T, description string, condition func() bool, timeout, interval time.Duration) {
t.Helper()
if timeout == 0 {
timeout = poolAwaitTimeout
}
if interval == 0 {
interval = 1 * time.Second
}
deadline := time.After(timeout)
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-deadline:
t.Fatalf("timed out waiting for: %s", description)
case <-ticker.C:
if condition() {
return
}
}
}
}
func cleanupPool(pool *opensandbox.DefaultSandboxPool) {
ctx := context.Background()
_ = pool.Resize(ctx, 0)
_, _ = pool.ReleaseAllIdle(ctx)
// Graceful shutdown first: waits for in-flight warmup to finish so
// orphan sandboxes are tracked and cleaned. Fall back to non-graceful.
_ = pool.Shutdown(ctx, true)
_ = pool.Shutdown(ctx, false)
}
func cleanupBorrowed(sandboxes []*opensandbox.Sandbox) {
for _, sb := range sandboxes {
_ = sb.Kill(context.Background())
_ = sb.Close()
}
}
func cleanupTaggedSandboxes(t *testing.T, tag string) {
t.Helper()
mgr := opensandbox.NewSandboxManager(getConnectionConfig(t))
defer mgr.Close()
ctx := context.Background()
// Brief wait for any in-flight sandbox creation to land before scanning.
time.Sleep(500 * time.Millisecond)
for attempt := 0; attempt < 5; attempt++ {
result, err := mgr.ListSandboxInfos(ctx, opensandbox.ListOptions{
Metadata: map[string]string{"tag": tag},
PageSize: 50,
})
if err != nil || len(result.Items) == 0 {
return
}
for _, info := range result.Items {
_ = mgr.KillSandbox(ctx, info.ID)
}
time.Sleep(300 * time.Millisecond)
}
}
func countTaggedSandboxes(t *testing.T, tag string) int {
t.Helper()
mgr := opensandbox.NewSandboxManager(getConnectionConfig(t))
defer mgr.Close()
result, err := mgr.ListSandboxInfos(context.Background(), opensandbox.ListOptions{
Metadata: map[string]string{"tag": tag},
PageSize: 50,
})
if err != nil {
t.Logf("countTaggedSandboxes: list error: %v", err)
return 0
}
return len(result.Items)
}
func brokenConnectionConfig() opensandbox.ConnectionConfig {
return opensandbox.ConnectionConfig{
Domain: "127.0.0.1:9",
Protocol: "http",
APIKey: "broken-e2e-test",
RequestTimeout: 1 * time.Second,
}
}
func newPoolManager(t *testing.T) *opensandbox.SandboxManager {
t.Helper()
mgr := opensandbox.NewSandboxManager(getConnectionConfig(t))
t.Cleanup(func() { mgr.Close() })
return mgr
}
func snapshotIdle(pool *opensandbox.DefaultSandboxPool) int {
snap, err := pool.Snapshot(context.Background())
if err != nil {
return -1
}
return snap.IdleCount
}
func snapshotState(pool *opensandbox.DefaultSandboxPool) string {
snap, err := pool.Snapshot(context.Background())
if err != nil {
return fmt.Sprintf("error: %v", err)
}
return snap.HealthState.String()
}
func snapshotLifecycle(pool *opensandbox.DefaultSandboxPool) string {
snap, err := pool.Snapshot(context.Background())
if err != nil {
return fmt.Sprintf("error: %v", err)
}
return snap.LifecycleState.String()
}