yao/sandbox/helpers_test.go
Max 28363b973e Fix sandbox compatibility, claude-proxy streaming, and rename playwright to browser
- Fix ListDir to support BusyBox/Alpine ls by falling back from GNU
  --time-style format, resolving CI test failures
- Update parseLS to handle both GNU (epoch) and BusyBox (date string) formats
- Fix claude-proxy streaming: always include usage in message_delta events
  to prevent Claude CLI from falling back to non-streaming mode
- Fix claude-proxy non-streaming: ensure usage is always present in responses
- Add paragraph separators between text blocks in Claude executor stream parser
- Translate VNC proxy UI from Chinese to English
- Rename sandbox-claude-playwright to sandbox-claude-browser across
  Dockerfiles, build scripts, and documentation

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-06 12:31:07 +08:00

231 lines
5.5 KiB
Go

package sandbox
import (
"os"
"testing"
)
func TestParseMemory(t *testing.T) {
tests := []struct {
input string
expected int64
}{
{"1024", 1024},
{"1k", 1024},
{"1K", 1024},
{"1m", 1024 * 1024},
{"1M", 1024 * 1024},
{"2g", 2 * 1024 * 1024 * 1024},
{"2G", 2 * 1024 * 1024 * 1024},
{"1t", 1024 * 1024 * 1024 * 1024},
{"1.5g", int64(1.5 * 1024 * 1024 * 1024)},
{"", 0},
{"invalid", 0},
}
for _, tt := range tests {
result := parseMemory(tt.input)
if result != tt.expected {
t.Errorf("parseMemory(%s) = %d, want %d", tt.input, result, tt.expected)
}
}
}
func TestMapToSlice(t *testing.T) {
// Nil map
result := mapToSlice(nil)
if result != nil {
t.Errorf("mapToSlice(nil) should return nil")
}
// Empty map
result = mapToSlice(map[string]string{})
if len(result) != 0 {
t.Errorf("mapToSlice(empty) should return empty slice")
}
// Map with values
m := map[string]string{
"KEY1": "value1",
"KEY2": "value2",
}
result = mapToSlice(m)
if len(result) != 2 {
t.Errorf("expected 2 items, got %d", len(result))
}
// Check that all items are in format KEY=value
found := make(map[string]bool)
for _, item := range result {
found[item] = true
}
if !found["KEY1=value1"] || !found["KEY2=value2"] {
t.Errorf("unexpected result: %v", result)
}
}
func TestParseLS(t *testing.T) {
// Test GNU ls output with --time-style=+%s (Unix epoch timestamp)
t.Run("GNU_ls_with_time_style", func(t *testing.T) {
output := `total 8
drwxr-xr-x 2 sandbox sandbox 4096 1700000000 dir1
-rw-r--r-- 1 sandbox sandbox 100 1700000001 file1.txt
lrwxrwxrwx 1 sandbox sandbox 10 1700000002 link1 -> file1.txt
`
result := parseLS(output, true)
if len(result) != 3 {
t.Fatalf("expected 3 items, got %d", len(result))
}
// Check dir1
if result[0].Name != "dir1" {
t.Errorf("expected name 'dir1', got '%s'", result[0].Name)
}
if !result[0].IsDir {
t.Errorf("expected dir1 to be a directory")
}
// Check file1.txt
if result[1].Name != "file1.txt" {
t.Errorf("expected name 'file1.txt', got '%s'", result[1].Name)
}
if result[1].Size != 100 {
t.Errorf("expected size 100, got %d", result[1].Size)
}
if result[1].IsDir {
t.Errorf("expected file1.txt to be a file, not directory")
}
// Check link1
if result[2].Name != "link1 -> file1.txt" {
t.Errorf("expected name 'link1 -> file1.txt', got '%s'", result[2].Name)
}
})
// Test BusyBox/basic ls output (Alpine-style)
t.Run("BusyBox_ls_basic", func(t *testing.T) {
output := `total 8
drwxr-xr-x 2 sandbox sandbox 4096 Jan 1 12:00 dir1
-rw-r--r-- 1 sandbox sandbox 100 Jan 1 12:01 file1.txt
lrwxrwxrwx 1 sandbox sandbox 10 Jan 1 12:02 link1 -> file1.txt
`
result := parseLS(output, false)
if len(result) != 3 {
t.Fatalf("expected 3 items, got %d", len(result))
}
// Check dir1
if result[0].Name != "dir1" {
t.Errorf("expected name 'dir1', got '%s'", result[0].Name)
}
if !result[0].IsDir {
t.Errorf("expected dir1 to be a directory")
}
// Check file1.txt
if result[1].Name != "file1.txt" {
t.Errorf("expected name 'file1.txt', got '%s'", result[1].Name)
}
if result[1].Size != 100 {
t.Errorf("expected size 100, got %d", result[1].Size)
}
if result[1].IsDir {
t.Errorf("expected file1.txt to be a file, not directory")
}
// Check link1 (in BusyBox format, symlink target is separate field)
if result[2].Name != "link1 -> file1.txt" {
t.Errorf("expected name 'link1 -> file1.txt', got '%s'", result[2].Name)
}
})
}
func TestParseStat(t *testing.T) {
output := "/workspace/test.txt|1024|81a4|1700000000|regular file"
result := parseStat(output)
if result == nil {
t.Fatal("expected non-nil result")
}
if result.Name != "test.txt" {
t.Errorf("expected name 'test.txt', got '%s'", result.Name)
}
if result.Path != "/workspace/test.txt" {
t.Errorf("expected path '/workspace/test.txt', got '%s'", result.Path)
}
if result.Size != 1024 {
t.Errorf("expected size 1024, got %d", result.Size)
}
if result.IsDir {
t.Errorf("expected IsDir to be false")
}
}
func TestParseLSMode(t *testing.T) {
tests := []struct {
input string
isDir bool
readable bool
}{
{"drwxr-xr-x", true, true},
{"-rw-r--r--", false, true},
{"lrwxrwxrwx", false, true},
{"-rwx------", false, true},
}
for _, tt := range tests {
mode := parseLSMode(tt.input)
isDir := mode.IsDir()
if isDir != tt.isDir {
t.Errorf("parseLSMode(%s).IsDir() = %v, want %v", tt.input, isDir, tt.isDir)
}
}
}
func TestCreateAndExtractTar(t *testing.T) {
// Create temp directory with test files
tmpDir, err := os.MkdirTemp("", "sandbox-test-*")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpDir)
// Create test file
testFile := tmpDir + "/test.txt"
if err := os.WriteFile(testFile, []byte("hello world"), 0644); err != nil {
t.Fatal(err)
}
// Create tar from file
reader, err := createTarFromPath(testFile)
if err != nil {
t.Fatalf("createTarFromPath failed: %v", err)
}
defer reader.Close()
// Extract to new location
extractDir, err := os.MkdirTemp("", "sandbox-extract-*")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(extractDir)
if err := extractTarToPath(reader, extractDir); err != nil {
t.Fatalf("extractTarToPath failed: %v", err)
}
// Verify extracted file
extractedFile := extractDir + "/test.txt"
content, err := os.ReadFile(extractedFile)
if err != nil {
t.Fatalf("failed to read extracted file: %v", err)
}
if string(content) != "hello world" {
t.Errorf("expected 'hello world', got '%s'", string(content))
}
}