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 }