/ tests / go / command_e2e_test.go
command_e2e_test.go
  1  // Copyright 2026 Alibaba Group Holding Ltd.
  2  //
  3  // Licensed under the Apache License, Version 2.0 (the "License");
  4  // you may not use this file except in compliance with the License.
  5  // You may obtain a copy of the License at
  6  //
  7  //     http://www.apache.org/licenses/LICENSE-2.0
  8  //
  9  // Unless required by applicable law or agreed to in writing, software
 10  // distributed under the License is distributed on an "AS IS" BASIS,
 11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  // See the License for the specific language governing permissions and
 13  // limitations under the License.
 14  
 15  package e2e
 16  
 17  import (
 18  	"testing"
 19  	"time"
 20  
 21  	"github.com/alibaba/OpenSandbox/sdks/sandbox/go"
 22  	"github.com/stretchr/testify/require"
 23  )
 24  
 25  func TestCommand_RunSimple(t *testing.T) {
 26  	ctx, sb := createTestSandbox(t)
 27  
 28  	exec, err := sb.RunCommand(ctx, "echo hello-from-go-e2e", nil)
 29  	require.NoError(t, err)
 30  
 31  	require.NotNil(t, exec.ExitCode)
 32  	require.Equal(t, 0, *exec.ExitCode)
 33  
 34  	text := exec.Text()
 35  	require.Contains(t, text, "hello-from-go-e2e")
 36  	t.Logf("Output: %s", text)
 37  }
 38  
 39  func TestCommand_RunWithHandlers(t *testing.T) {
 40  	ctx, sb := createTestSandbox(t)
 41  
 42  	var stdoutLines []string
 43  	handlers := &opensandbox.ExecutionHandlers{
 44  		OnStdout: func(msg opensandbox.OutputMessage) error {
 45  			stdoutLines = append(stdoutLines, msg.Text)
 46  			return nil
 47  		},
 48  	}
 49  
 50  	exec, err := sb.RunCommand(ctx, "echo line1 && echo line2", handlers)
 51  	require.NoError(t, err)
 52  
 53  	require.NotEmpty(t, stdoutLines, "expected handler to receive stdout events")
 54  	t.Logf("Handler received %d stdout events", len(stdoutLines))
 55  	t.Logf("Execution stdout count: %d", len(exec.Stdout))
 56  }
 57  
 58  func TestCommand_ExitCode(t *testing.T) {
 59  	ctx, sb := createTestSandbox(t)
 60  
 61  	exec, err := sb.RunCommand(ctx, "true", nil)
 62  	require.NoError(t, err)
 63  	require.NotNil(t, exec.ExitCode)
 64  	require.Equal(t, 0, *exec.ExitCode)
 65  	t.Log("Exit code tests passed")
 66  }
 67  
 68  func TestCommand_MultiLine(t *testing.T) {
 69  	ctx, sb := createTestSandbox(t)
 70  
 71  	exec, err := sb.RunCommand(ctx, "echo hello && echo world && uname -a", nil)
 72  	require.NoError(t, err)
 73  
 74  	text := exec.Text()
 75  	require.Contains(t, text, "hello")
 76  	require.Contains(t, text, "world")
 77  	t.Logf("Multi-line output (%d bytes): %s", len(text), text)
 78  }
 79  
 80  func TestCommand_EnvInjection(t *testing.T) {
 81  	ctx, sb := createTestSandbox(t)
 82  
 83  	exec, err := sb.RunCommandWithOpts(ctx, opensandbox.RunCommandRequest{
 84  		Command: "echo $CUSTOM_VAR",
 85  		Envs: map[string]string{
 86  			"CUSTOM_VAR": "injected-from-go-e2e",
 87  		},
 88  	}, nil)
 89  	require.NoError(t, err)
 90  
 91  	text := exec.Text()
 92  	require.Contains(t, text, "injected-from-go-e2e")
 93  	t.Logf("Env injection: %s", text)
 94  }
 95  
 96  func TestCommand_BackgroundStatusLogs(t *testing.T) {
 97  	ctx, sb := createTestSandbox(t)
 98  
 99  	exec, err := sb.RunCommandWithOpts(ctx, opensandbox.RunCommandRequest{
100  		Command:    "echo bg-output && sleep 1 && echo bg-done",
101  		Background: true,
102  	}, nil)
103  	require.NoError(t, err)
104  
105  	if exec.ID == "" {
106  		t.Log("No execution ID from background command (server may not return init event for background)")
107  		return
108  	}
109  	t.Logf("Background command ID: %s", exec.ID)
110  }
111  
112  func TestCommand_Interrupt(t *testing.T) {
113  	ctx, sb := createTestSandbox(t)
114  
115  	exec, err := sb.RunCommandWithOpts(ctx, opensandbox.RunCommandRequest{
116  		Command:    "sleep 300",
117  		Background: true,
118  	}, nil)
119  	require.NoError(t, err)
120  	if exec.ID == "" {
121  		t.Log("No execution ID — cannot test interrupt")
122  		return
123  	}
124  
125  	pingExec, err := sb.RunCommand(ctx, "echo still-alive", nil)
126  	require.NoError(t, err)
127  	require.Contains(t, pingExec.Text(), "still-alive")
128  	t.Log("Interrupt test: sandbox responsive during background command")
129  }
130  
131  func TestCommand_StatusAndLogs(t *testing.T) {
132  	ctx, sb := createTestSandbox(t)
133  	execd := newExecdClientForSandbox(t, ctx, sb)
134  
135  	exec, err := sb.RunCommandWithOpts(ctx, opensandbox.RunCommandRequest{
136  		Command:    "echo status-log-start && sleep 1 && echo status-log-end",
137  		Background: true,
138  	}, nil)
139  	require.NoError(t, err)
140  	if exec.ID == "" {
141  		t.Skip("no execution ID returned for background command")
142  	}
143  
144  	status, err := execd.GetCommandStatus(ctx, exec.ID)
145  	require.NoError(t, err)
146  	require.Equal(t, exec.ID, status.ID)
147  
148  	var logs *opensandbox.CommandLogsResponse
149  	deadline := time.Now().Add(8 * time.Second)
150  	for time.Now().Before(deadline) {
151  		logs, err = execd.GetCommandLogs(ctx, exec.ID, nil)
152  		require.NoError(t, err)
153  		if logs != nil && logs.Output != "" {
154  			break
155  		}
156  		time.Sleep(300 * time.Millisecond)
157  	}
158  	require.NotNil(t, logs)
159  	require.Contains(t, logs.Output, "status-log-start")
160  }
161  
162  func TestCommand_InterruptCommandAPI(t *testing.T) {
163  	ctx, sb := createTestSandbox(t)
164  	execd := newExecdClientForSandbox(t, ctx, sb)
165  
166  	exec, err := sb.RunCommandWithOpts(ctx, opensandbox.RunCommandRequest{
167  		Command:    "sleep 300",
168  		Background: true,
169  	}, nil)
170  	require.NoError(t, err)
171  	if exec.ID == "" {
172  		t.Skip("no execution ID returned for background command")
173  	}
174  
175  	require.NoError(t, execd.InterruptCommand(ctx, exec.ID))
176  
177  	deadline := time.Now().Add(10 * time.Second)
178  	for time.Now().Before(deadline) {
179  		status, statusErr := execd.GetCommandStatus(ctx, exec.ID)
180  		if statusErr == nil && !status.Running {
181  			return
182  		}
183  		time.Sleep(500 * time.Millisecond)
184  	}
185  	t.Log("interrupt requested but status stayed running within timeout")
186  }