- 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>
231 lines
5.5 KiB
Go
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))
|
|
}
|
|
}
|