307 lines
7.2 KiB
Go
307 lines
7.2 KiB
Go
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")
|
|
}
|
|
}
|