e0e362d700
SDK Tests / SDK CI (push) Blocked by required conditions
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
SDK Tests / Python SDK Tests (sandbox) (push) Waiting to run
SDK Tests / CLI Quality (push) Waiting to run
SDK Tests / CLI Tests (push) Waiting to run
SDK Tests / Python SDK Quality (code-interpreter) (push) Waiting to run
SDK Tests / Python SDK Quality (sandbox) (push) Waiting to run
SDK Tests / Python SDK Tests (code-interpreter) (push) Waiting to run
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Go SDK Quality And Tests (push) Waiting to run
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
264 lines
8.2 KiB
Go
264 lines
8.2 KiB
Go
// Copyright 2026 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 isolation
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func newTestMergedView(t *testing.T, mode WorkspaceMode) *MergedView {
|
|
t.Helper()
|
|
lower := filepath.Join(t.TempDir(), "lower")
|
|
upper := filepath.Join(t.TempDir(), "upper")
|
|
require.NoError(t, os.MkdirAll(lower, 0o755))
|
|
require.NoError(t, os.MkdirAll(upper, 0o755))
|
|
return NewMergedView(lower, upper, mode, uint32(os.Getuid()), uint32(os.Getgid()))
|
|
}
|
|
|
|
func TestMergedView_Stat_Lower(t *testing.T) {
|
|
mv := newTestMergedView(t, WorkspaceRW)
|
|
require.NoError(t, os.WriteFile(filepath.Join(mv.LowerDir, "test.txt"), []byte("hello"), 0o644))
|
|
|
|
info, err := mv.Stat("test.txt")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "test.txt", info.Name())
|
|
assert.Equal(t, int64(5), info.Size())
|
|
}
|
|
|
|
func TestMergedView_Stat_UpperOverrides(t *testing.T) {
|
|
mv := newTestMergedView(t, WorkspaceRW)
|
|
require.NoError(t, os.WriteFile(filepath.Join(mv.LowerDir, "same.txt"), []byte("lower"), 0o644))
|
|
require.NoError(t, os.WriteFile(filepath.Join(mv.UpperDir, "same.txt"), []byte("upper"), 0o644))
|
|
|
|
info, err := mv.Stat("same.txt")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(5), info.Size(), "upper should override lower")
|
|
}
|
|
|
|
func TestMergedView_ReadFile(t *testing.T) {
|
|
mv := newTestMergedView(t, WorkspaceRW)
|
|
require.NoError(t, os.WriteFile(filepath.Join(mv.LowerDir, "data.txt"), []byte("lower-data"), 0o644))
|
|
|
|
data, err := mv.ReadFile("data.txt")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []byte("lower-data"), data)
|
|
}
|
|
|
|
func TestMergedView_WriteFile(t *testing.T) {
|
|
mv := newTestMergedView(t, WorkspaceRW)
|
|
|
|
err := mv.WriteFile("new.txt", []byte("session-data"), 0o644)
|
|
require.NoError(t, err)
|
|
|
|
// Verify in upper.
|
|
data, err := os.ReadFile(filepath.Join(mv.UpperDir, "new.txt"))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []byte("session-data"), data)
|
|
|
|
// Verify NOT in lower.
|
|
_, err = os.ReadFile(filepath.Join(mv.LowerDir, "new.txt"))
|
|
assert.True(t, os.IsNotExist(err))
|
|
}
|
|
|
|
func TestMergedView_WriteFile_DenyReadOnly(t *testing.T) {
|
|
mv := newTestMergedView(t, WorkspaceRO)
|
|
|
|
err := mv.WriteFile("new.txt", []byte("data"), 0o644)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "read-only")
|
|
}
|
|
|
|
func TestMergedView_Remove(t *testing.T) {
|
|
mv := newTestMergedView(t, WorkspaceRW)
|
|
|
|
require.NoError(t, mv.WriteFile("del.txt", []byte("tmp"), 0o644))
|
|
require.NoError(t, mv.Remove("del.txt"))
|
|
|
|
_, err := os.Stat(filepath.Join(mv.UpperDir, "del.txt"))
|
|
assert.True(t, os.IsNotExist(err))
|
|
}
|
|
|
|
func TestMergedView_Remove_LowerOnly(t *testing.T) {
|
|
mv := newTestMergedView(t, WorkspaceRW)
|
|
require.NoError(t, os.WriteFile(filepath.Join(mv.LowerDir, "lower-only.txt"), []byte("x"), 0o644))
|
|
|
|
err := mv.Remove("lower-only.txt")
|
|
require.NoError(t, err)
|
|
|
|
// Whiteout should be created
|
|
whPath := filepath.Join(mv.UpperDir, ".wh.lower-only.txt")
|
|
_, err = os.Stat(whPath)
|
|
assert.NoError(t, err, "whiteout marker should exist")
|
|
|
|
// File should no longer be visible via Stat
|
|
_, err = mv.Stat("lower-only.txt")
|
|
assert.True(t, os.IsNotExist(err))
|
|
}
|
|
|
|
func TestMergedView_RemoveAll(t *testing.T) {
|
|
mv := newTestMergedView(t, WorkspaceRW)
|
|
require.NoError(t, mv.MkdirAll("subdir", 0o755))
|
|
require.NoError(t, mv.WriteFile("subdir/f.txt", []byte("x"), 0o644))
|
|
|
|
require.NoError(t, mv.RemoveAll("subdir"))
|
|
|
|
_, err := os.Stat(filepath.Join(mv.UpperDir, "subdir"))
|
|
assert.True(t, os.IsNotExist(err))
|
|
}
|
|
|
|
func TestMergedView_MkdirAll(t *testing.T) {
|
|
mv := newTestMergedView(t, WorkspaceRW)
|
|
|
|
require.NoError(t, mv.MkdirAll("a/b/c", 0o755))
|
|
|
|
info, err := os.Stat(filepath.Join(mv.UpperDir, "a", "b", "c"))
|
|
require.NoError(t, err)
|
|
assert.True(t, info.IsDir())
|
|
}
|
|
|
|
func TestMergedView_Rename(t *testing.T) {
|
|
mv := newTestMergedView(t, WorkspaceRW)
|
|
require.NoError(t, mv.WriteFile("old.txt", []byte("renamed"), 0o644))
|
|
|
|
require.NoError(t, mv.Rename("old.txt", "new.txt"))
|
|
|
|
// Old gone.
|
|
_, err := mv.Stat("old.txt")
|
|
assert.True(t, os.IsNotExist(err))
|
|
|
|
// New exists.
|
|
data, err := mv.ReadFile("new.txt")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []byte("renamed"), data)
|
|
}
|
|
|
|
func TestMergedView_Rename_LowerToUpper(t *testing.T) {
|
|
mv := newTestMergedView(t, WorkspaceRW)
|
|
require.NoError(t, os.WriteFile(filepath.Join(mv.LowerDir, "src.txt"), []byte("from-lower"), 0o644))
|
|
|
|
require.NoError(t, mv.Rename("src.txt", "dst.txt"))
|
|
|
|
data, err := mv.ReadFile("dst.txt")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []byte("from-lower"), data)
|
|
}
|
|
|
|
func TestMergedView_Chmod(t *testing.T) {
|
|
mv := newTestMergedView(t, WorkspaceRW)
|
|
require.NoError(t, mv.WriteFile("perm.txt", []byte("x"), 0o600))
|
|
|
|
require.NoError(t, mv.Chmod("perm.txt", 0o755))
|
|
|
|
info, err := os.Stat(filepath.Join(mv.UpperDir, "perm.txt"))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, os.FileMode(0o755), info.Mode().Perm())
|
|
}
|
|
|
|
func TestMergedView_ReplaceContent(t *testing.T) {
|
|
mv := newTestMergedView(t, WorkspaceRW)
|
|
require.NoError(t, mv.WriteFile("replace.txt", []byte("hello world"), 0o644))
|
|
|
|
require.NoError(t, mv.ReplaceContent("replace.txt", "world", "gopher"))
|
|
|
|
data, err := mv.ReadFile("replace.txt")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []byte("hello gopher"), data)
|
|
}
|
|
|
|
func TestMergedView_Search(t *testing.T) {
|
|
mv := newTestMergedView(t, WorkspaceRW)
|
|
require.NoError(t, mv.WriteFile("a.txt", []byte("a"), 0o644))
|
|
require.NoError(t, mv.WriteFile("b.log", []byte("b"), 0o644))
|
|
require.NoError(t, os.WriteFile(filepath.Join(mv.LowerDir, "c.txt"), []byte("c"), 0o644))
|
|
|
|
results, err := mv.Search(".", "*.txt")
|
|
require.NoError(t, err)
|
|
assert.Len(t, results, 2)
|
|
assert.Contains(t, results, "a.txt")
|
|
assert.Contains(t, results, "c.txt")
|
|
}
|
|
|
|
func TestMergedView_ReadDir(t *testing.T) {
|
|
mv := newTestMergedView(t, WorkspaceRW)
|
|
require.NoError(t, mv.WriteFile("upper.txt", []byte("u"), 0o644))
|
|
require.NoError(t, os.WriteFile(filepath.Join(mv.LowerDir, "lower.txt"), []byte("l"), 0o644))
|
|
|
|
entries, err := mv.ReadDir(".")
|
|
require.NoError(t, err)
|
|
names := make([]string, len(entries))
|
|
for i, e := range entries {
|
|
names[i] = e.Name()
|
|
}
|
|
assert.Contains(t, names, "upper.txt")
|
|
assert.Contains(t, names, "lower.txt")
|
|
}
|
|
|
|
func TestMergedView_ReadDir_Whiteout(t *testing.T) {
|
|
mv := newTestMergedView(t, WorkspaceRW)
|
|
require.NoError(t, os.WriteFile(filepath.Join(mv.LowerDir, "hidden.txt"), []byte("secret"), 0o644))
|
|
// Create whiteout file in upper to hide lower entry.
|
|
require.NoError(t, os.WriteFile(filepath.Join(mv.UpperDir, ".wh.hidden.txt"), nil, 0o644))
|
|
|
|
entries, err := mv.ReadDir(".")
|
|
require.NoError(t, err)
|
|
for _, e := range entries {
|
|
assert.NotEqual(t, "hidden.txt", e.Name(), "whiteout should hide lower entry")
|
|
}
|
|
}
|
|
|
|
func TestMergedView_WriteFileReader(t *testing.T) {
|
|
mv := newTestMergedView(t, WorkspaceRW)
|
|
|
|
n, err := mv.WriteFileReader("stream.txt", strings.NewReader("streamed-data"), 0o644)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(13), n)
|
|
|
|
data, err := mv.ReadFile("stream.txt")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []byte("streamed-data"), data)
|
|
}
|
|
|
|
func TestMergedView_PathTraversal(t *testing.T) {
|
|
mv := newTestMergedView(t, WorkspaceRW)
|
|
|
|
_, err := mv.Stat("../etc/passwd")
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "traversal")
|
|
|
|
_, err = mv.ReadFile("../../host")
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "traversal")
|
|
|
|
err = mv.WriteFile("../escape", []byte("x"), 0o644)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "traversal")
|
|
}
|
|
|
|
func TestMergedView_ReadOnly_AllWritesDenied(t *testing.T) {
|
|
mv := newTestMergedView(t, WorkspaceRO)
|
|
|
|
assert.Error(t, mv.WriteFile("x.txt", []byte("x"), 0o644))
|
|
assert.Error(t, mv.Remove("x.txt"))
|
|
assert.Error(t, mv.MkdirAll("d", 0o755))
|
|
assert.Error(t, mv.Rename("a", "b"))
|
|
assert.Error(t, mv.Chmod("x.txt", 0o755))
|
|
}
|