Changeset 0.29.0 (#195)
This commit is contained in:
306
server/sandbox/sandbox_test.go
Normal file
306
server/sandbox/sandbox_test.go
Normal file
@@ -0,0 +1,306 @@
|
||||
package sandbox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.starlark.net/starlark"
|
||||
)
|
||||
|
||||
func TestExecBasic(t *testing.T) {
|
||||
sb := New(DefaultConfig())
|
||||
res, err := sb.Exec(context.Background(), "test.star", `
|
||||
x = 1 + 2
|
||||
name = "hello"
|
||||
`, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if v, ok := res.Globals["x"]; !ok {
|
||||
t.Fatal("missing global 'x'")
|
||||
} else if v.String() != "3" {
|
||||
t.Fatalf("expected x=3, got %s", v.String())
|
||||
}
|
||||
if v, ok := res.Globals["name"]; !ok {
|
||||
t.Fatal("missing global 'name'")
|
||||
} else if v.(starlark.String).GoString() != "hello" {
|
||||
t.Fatalf("expected name='hello', got %s", v.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecPrintCapture(t *testing.T) {
|
||||
sb := New(DefaultConfig())
|
||||
res, err := sb.Exec(context.Background(), "test.star", `
|
||||
print("line one")
|
||||
print("line two")
|
||||
`, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
expected := "line one\nline two\n"
|
||||
if res.Output != expected {
|
||||
t.Fatalf("expected output %q, got %q", expected, res.Output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecStepsTracked(t *testing.T) {
|
||||
sb := New(DefaultConfig())
|
||||
res, err := sb.Exec(context.Background(), "test.star", `
|
||||
def work():
|
||||
x = 0
|
||||
for i in range(100):
|
||||
x += i
|
||||
return x
|
||||
result = work()
|
||||
`, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if res.Steps == 0 {
|
||||
t.Fatal("expected steps > 0")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecStepLimitExceeded(t *testing.T) {
|
||||
sb := New(Config{MaxSteps: 100, Name: "limited"})
|
||||
_, err := sb.Exec(context.Background(), "test.star", `
|
||||
def work():
|
||||
x = 0
|
||||
for i in range(10000):
|
||||
x += i
|
||||
return x
|
||||
result = work()
|
||||
`, nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected step limit error")
|
||||
}
|
||||
// Error message varies: "Starlark computation cancelled: exceeded ..."
|
||||
// or "context canceled" — just verify it failed.
|
||||
}
|
||||
|
||||
func TestExecContextTimeout(t *testing.T) {
|
||||
// Use step limit of 0 (unlimited) so only context timeout triggers
|
||||
sb := New(Config{MaxSteps: 0, Name: "timeout"})
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
_, err := sb.Exec(ctx, "test.star", `
|
||||
def work():
|
||||
x = 0
|
||||
for i in range(100000000):
|
||||
x += i
|
||||
return x
|
||||
result = work()
|
||||
`, nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected timeout error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecLoadDisabled(t *testing.T) {
|
||||
sb := New(DefaultConfig())
|
||||
_, err := sb.Exec(context.Background(), "test.star", `
|
||||
load("other.star", "foo")
|
||||
`, nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected load error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "disabled") {
|
||||
t.Fatalf("expected 'disabled' in error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecModuleInjection(t *testing.T) {
|
||||
getFn := starlark.NewBuiltin("test.get", func(
|
||||
thread *starlark.Thread, b *starlark.Builtin,
|
||||
args starlark.Tuple, kwargs []starlark.Tuple,
|
||||
) (starlark.Value, error) {
|
||||
return starlark.String("secret_value"), nil
|
||||
})
|
||||
|
||||
mod := MakeModule("test", starlark.StringDict{
|
||||
"get": getFn,
|
||||
})
|
||||
|
||||
sb := New(DefaultConfig())
|
||||
res, err := sb.Exec(context.Background(), "test.star", `
|
||||
result = test.get()
|
||||
`, map[string]starlark.Value{"test": mod})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
v, ok := res.Globals["result"]
|
||||
if !ok {
|
||||
t.Fatal("missing global 'result'")
|
||||
}
|
||||
if v.(starlark.String).GoString() != "secret_value" {
|
||||
t.Fatalf("expected 'secret_value', got %s", v.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecSyntaxError(t *testing.T) {
|
||||
sb := New(DefaultConfig())
|
||||
_, err := sb.Exec(context.Background(), "bad.star", `
|
||||
def broken(
|
||||
`, nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected syntax error")
|
||||
}
|
||||
se, ok := err.(*SandboxError)
|
||||
if !ok {
|
||||
t.Fatalf("expected *SandboxError, got %T", err)
|
||||
}
|
||||
_ = se // syntax errors may not have backtrace
|
||||
}
|
||||
|
||||
func TestExecRuntimeError(t *testing.T) {
|
||||
sb := New(DefaultConfig())
|
||||
_, err := sb.Exec(context.Background(), "runtime.star", `
|
||||
x = 1 / 0
|
||||
`, nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected runtime error")
|
||||
}
|
||||
se, ok := err.(*SandboxError)
|
||||
if !ok {
|
||||
t.Fatalf("expected *SandboxError, got %T", err)
|
||||
}
|
||||
if se.Backtrace == "" {
|
||||
t.Fatal("expected backtrace for runtime error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallFunction(t *testing.T) {
|
||||
sb := New(DefaultConfig())
|
||||
|
||||
res, err := sb.Exec(context.Background(), "funcs.star", `
|
||||
def greet(name):
|
||||
return "hello, " + name
|
||||
`, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("exec error: %v", err)
|
||||
}
|
||||
|
||||
fn := res.Globals["greet"].(starlark.Callable)
|
||||
val, _, err := sb.Call(context.Background(), fn,
|
||||
starlark.Tuple{starlark.String("world")}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("call error: %v", err)
|
||||
}
|
||||
if val.(starlark.String).GoString() != "hello, world" {
|
||||
t.Fatalf("expected 'hello, world', got %s", val.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallWithPrint(t *testing.T) {
|
||||
sb := New(DefaultConfig())
|
||||
|
||||
res, err := sb.Exec(context.Background(), "funcs.star", `
|
||||
def run():
|
||||
print("running")
|
||||
return 42
|
||||
`, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("exec error: %v", err)
|
||||
}
|
||||
|
||||
fn := res.Globals["run"].(starlark.Callable)
|
||||
val, output, err := sb.Call(context.Background(), fn, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("call error: %v", err)
|
||||
}
|
||||
if val.String() != "42" {
|
||||
t.Fatalf("expected 42, got %s", val.String())
|
||||
}
|
||||
if !strings.Contains(output, "running") {
|
||||
t.Fatalf("expected 'running' in output, got %q", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallRuntimeError(t *testing.T) {
|
||||
sb := New(DefaultConfig())
|
||||
|
||||
res, err := sb.Exec(context.Background(), "funcs.star", `
|
||||
def boom():
|
||||
return 1 / 0
|
||||
`, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("exec error: %v", err)
|
||||
}
|
||||
|
||||
fn := res.Globals["boom"].(starlark.Callable)
|
||||
_, _, err = sb.Call(context.Background(), fn, nil, nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected runtime error from Call")
|
||||
}
|
||||
se, ok := err.(*SandboxError)
|
||||
if !ok {
|
||||
t.Fatalf("expected *SandboxError, got %T", err)
|
||||
}
|
||||
if se.Backtrace == "" {
|
||||
t.Fatal("expected backtrace")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewDefaults(t *testing.T) {
|
||||
sb := New(Config{})
|
||||
if sb.config.MaxSteps != 0 {
|
||||
t.Fatalf("expected MaxSteps=0 (unlimited), got %d", sb.config.MaxSteps)
|
||||
}
|
||||
if sb.config.Name != "extension" {
|
||||
t.Fatalf("expected default Name='extension', got %q", sb.config.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewWithDefaultConfig(t *testing.T) {
|
||||
sb := New(DefaultConfig())
|
||||
if sb.config.MaxSteps != 1_000_000 {
|
||||
t.Fatalf("expected MaxSteps=1000000, got %d", sb.config.MaxSteps)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeModule(t *testing.T) {
|
||||
mod := MakeModule("mymod", starlark.StringDict{
|
||||
"version": starlark.String("1.0"),
|
||||
})
|
||||
if mod.Name != "mymod" {
|
||||
t.Fatalf("expected name 'mymod', got %q", mod.Name)
|
||||
}
|
||||
v, ok := mod.Members["version"]
|
||||
if !ok {
|
||||
t.Fatal("missing 'version' member")
|
||||
}
|
||||
if v.(starlark.String).GoString() != "1.0" {
|
||||
t.Fatalf("expected '1.0', got %s", v.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSandboxErrorUnwrap(t *testing.T) {
|
||||
inner := starlark.String("test").Truth() // just need any value
|
||||
_ = inner
|
||||
se := &SandboxError{
|
||||
Err: context.DeadlineExceeded,
|
||||
Backtrace: "",
|
||||
}
|
||||
if se.Unwrap() != context.DeadlineExceeded {
|
||||
t.Fatal("Unwrap should return inner error")
|
||||
}
|
||||
if se.Error() != "context deadline exceeded" {
|
||||
t.Fatalf("unexpected error string: %q", se.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSandboxErrorWithBacktrace(t *testing.T) {
|
||||
se := &SandboxError{
|
||||
Err: context.DeadlineExceeded,
|
||||
Backtrace: " funcs.star:3:5: in boom",
|
||||
}
|
||||
if !strings.Contains(se.Error(), "context deadline exceeded") {
|
||||
t.Fatal("should contain original error")
|
||||
}
|
||||
if !strings.Contains(se.Error(), "funcs.star:3:5") {
|
||||
t.Fatal("should contain backtrace")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user