Files
wehub-resource-sync e0e362d700
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
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
SDK Tests / SDK CI (push) Has been cancelled
SDK Tests / CLI Tests (push) Has been cancelled
SDK Tests / Python SDK Quality (code-interpreter) (push) Has been cancelled
SDK Tests / Python SDK Quality (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (sandbox) (push) Has been cancelled
SDK Tests / CLI Quality (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Go SDK Quality And Tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:39:33 +08:00

301 lines
8.8 KiB
Go

// Copyright 2025 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 execute
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/gorilla/websocket"
"github.com/stretchr/testify/require"
execdflag "github.com/alibaba/opensandbox/execd/pkg/flag"
)
// Create WebSocket test server
func createTestServer(t *testing.T, handleFunc func(conn *websocket.Conn)) *httptest.Server {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Validate request path
if !strings.HasPrefix(r.URL.Path, "/api/kernels/") {
t.Errorf("expected path to start with '/api/kernels/', got '%s'", r.URL.Path)
}
if !strings.HasSuffix(r.URL.Path, "/channels") {
t.Errorf("expected path to end with '/channels', got '%s'", r.URL.Path)
}
// Upgrade HTTP connection to WebSocket
upgrader := websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true },
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
t.Fatalf("failed to upgrade to WebSocket: %v", err)
}
defer conn.Close()
// Handle WebSocket connection
handleFunc(conn)
}))
return server
}
// Test streaming code execution
func TestExecuteCodeStream(t *testing.T) {
// Spin up mock WebSocket server
server := createTestServer(t, func(conn *websocket.Conn) {
// Read execution request
var executeRequest Message
err := conn.ReadJSON(&executeRequest)
if err != nil {
t.Fatalf("failed to read execution request: %v", err)
}
// Send multiple stream messages
for i := 0; i < 3; i++ {
streamContent, _ := json.Marshal(StreamOutput{
Name: StreamStdout,
Text: "Line " + string(rune('0'+i)) + "\n",
})
streamMsg := Message{
Header: Header{
MessageID: "stream-msg-id-" + string(rune('0'+i)),
Session: executeRequest.Header.Session,
MessageType: string(MsgStream),
},
ParentHeader: executeRequest.Header,
Content: json.RawMessage(streamContent),
}
conn.WriteJSON(streamMsg)
time.Sleep(100 * time.Millisecond)
}
// Send execution result
resultContent, _ := json.Marshal(ExecuteResult{
ExecutionCount: 1,
Data: map[string]interface{}{
"text/plain": "Completed",
},
Metadata: map[string]interface{}{},
})
executeResultMsg := Message{
Header: Header{
MessageID: "result-msg-id",
Session: executeRequest.Header.Session,
MessageType: string(MsgExecuteResult),
},
ParentHeader: executeRequest.Header,
Content: json.RawMessage(resultContent),
}
conn.WriteJSON(executeResultMsg)
// Send status message
statusContent, _ := json.Marshal(StatusUpdate{
ExecutionState: StateIdle,
})
statusMsg := Message{
Header: Header{
MessageID: "status-msg-id",
Session: executeRequest.Header.Session,
MessageType: string(MsgStatus),
},
ParentHeader: executeRequest.Header,
Content: json.RawMessage(statusContent),
}
conn.WriteJSON(statusMsg)
})
defer server.Close()
// Convert HTTP URL to WebSocket URL
wsURL := "ws" + strings.TrimPrefix(server.URL, "http") + "/api/kernels/test-kernel-id/channels"
// Create executor client
executor := NewExecutor(wsURL, nil)
// Connect to WebSocket
err := executor.Connect()
if err != nil {
t.Fatalf("failed to connect to WebSocket: %v", err)
}
defer executor.Disconnect()
// Execute code in streaming mode
resultChan := make(chan *ExecutionResult, 10)
err = executor.ExecuteCodeStream("for i in range(3):\n print(f'Line {i}')", resultChan)
if err != nil {
t.Fatalf("failed to start streaming execution: %v", err)
}
// Receive and verify stream results
resultCount := 0
for result := range resultChan {
if result == nil {
break
}
resultCount++
}
// Should receive at least 4 results (3 stream outputs + 1 final result)
if resultCount < 4 {
t.Errorf("expected at least 4 results, got %d", resultCount)
}
}
func TestExecuteCodeStreamWaitsForLateExecuteResultUsingConfiguredPollInterval(t *testing.T) {
previousPollInterval := execdflag.JupyterIdlePollInterval
execdflag.JupyterIdlePollInterval = time.Millisecond
t.Cleanup(func() {
execdflag.JupyterIdlePollInterval = previousPollInterval
})
server := createTestServer(t, func(conn *websocket.Conn) {
var executeRequest Message
err := conn.ReadJSON(&executeRequest)
if err != nil {
t.Fatalf("failed to read execution request: %v", err)
}
statusContent, _ := json.Marshal(StatusUpdate{ExecutionState: StateIdle})
statusMsg := Message{
Header: Header{
MessageID: "status-msg-id",
Session: executeRequest.Header.Session,
MessageType: string(MsgStatus),
},
ParentHeader: executeRequest.Header,
Content: json.RawMessage(statusContent),
}
require.NoError(t, conn.WriteJSON(statusMsg))
time.Sleep(15 * time.Millisecond)
resultContent, _ := json.Marshal(ExecuteResult{
ExecutionCount: 1,
Data: map[string]interface{}{
"text/plain": "Completed late",
},
Metadata: map[string]interface{}{},
})
executeResultMsg := Message{
Header: Header{
MessageID: "result-msg-id",
Session: executeRequest.Header.Session,
MessageType: string(MsgExecuteResult),
},
ParentHeader: executeRequest.Header,
Content: json.RawMessage(resultContent),
}
require.NoError(t, conn.WriteJSON(executeResultMsg))
})
defer server.Close()
wsURL := "ws" + strings.TrimPrefix(server.URL, "http") + "/api/kernels/test-kernel-id/channels"
executor := NewExecutor(wsURL, nil)
require.NoError(t, executor.Connect())
defer executor.Disconnect()
resultChan := make(chan *ExecutionResult, 10)
require.NoError(t, executor.ExecuteCodeStream("print('late result')", resultChan))
start := time.Now()
var gotLateResult bool
for result := range resultChan {
if result != nil && result.ExecutionCount == 1 {
gotLateResult = true
}
}
elapsed := time.Since(start)
require.True(t, gotLateResult, "expected late execute_result to be delivered before stream close")
require.Less(t, elapsed, 100*time.Millisecond, "expected stream to close promptly after late execute_result")
}
func TestExecuteCodeStreamFallsBackWhenPollIntervalIsNonPositive(t *testing.T) {
previousPollInterval := execdflag.JupyterIdlePollInterval
execdflag.JupyterIdlePollInterval = 0
t.Cleanup(func() {
execdflag.JupyterIdlePollInterval = previousPollInterval
})
server := createTestServer(t, func(conn *websocket.Conn) {
var executeRequest Message
err := conn.ReadJSON(&executeRequest)
if err != nil {
t.Fatalf("failed to read execution request: %v", err)
}
statusContent, _ := json.Marshal(StatusUpdate{ExecutionState: StateIdle})
statusMsg := Message{
Header: Header{
MessageID: "status-msg-id",
Session: executeRequest.Header.Session,
MessageType: string(MsgStatus),
},
ParentHeader: executeRequest.Header,
Content: json.RawMessage(statusContent),
}
require.NoError(t, conn.WriteJSON(statusMsg))
time.Sleep(15 * time.Millisecond)
resultContent, _ := json.Marshal(ExecuteResult{
ExecutionCount: 1,
Data: map[string]interface{}{
"text/plain": "Completed with fallback",
},
Metadata: map[string]interface{}{},
})
executeResultMsg := Message{
Header: Header{
MessageID: "result-msg-id",
Session: executeRequest.Header.Session,
MessageType: string(MsgExecuteResult),
},
ParentHeader: executeRequest.Header,
Content: json.RawMessage(resultContent),
}
require.NoError(t, conn.WriteJSON(executeResultMsg))
})
defer server.Close()
wsURL := "ws" + strings.TrimPrefix(server.URL, "http") + "/api/kernels/test-kernel-id/channels"
executor := NewExecutor(wsURL, nil)
require.NoError(t, executor.Connect())
defer executor.Disconnect()
resultChan := make(chan *ExecutionResult, 10)
require.NoError(t, executor.ExecuteCodeStream("print('fallback')", resultChan))
start := time.Now()
var gotLateResult bool
for result := range resultChan {
if result != nil && result.ExecutionCount == 1 {
gotLateResult = true
}
}
elapsed := time.Since(start)
require.True(t, gotLateResult, "expected late execute_result to be delivered before stream close")
require.GreaterOrEqual(t, elapsed, 90*time.Millisecond, "expected non-positive poll interval to fall back to runtime default (100ms)")
require.Less(t, elapsed, 300*time.Millisecond, "expected fallback poll interval to still close stream promptly")
}