package workspace import ( "fmt" "os" "strings" "sync" "testing" iop "iop/proto/gen/iop" ) func testWorkspaceConfig(root string) *iop.WorkspaceConfig { return &iop.WorkspaceConfig{ Ref: "mac-workspace", Platform: "darwin", Root: root, Operations: []iop.WorkspaceOperation{ iop.WorkspaceOperation_WORKSPACE_OPERATION_READ, iop.WorkspaceOperation_WORKSPACE_OPERATION_LIST, iop.WorkspaceOperation_WORKSPACE_OPERATION_WRITE, iop.WorkspaceOperation_WORKSPACE_OPERATION_DELETE, }, MaxReadBytes: 64, MaxWriteBytes: 64, MaxOutputBytes: 64, } } func testRequestAuthority(requestID string) RequestAuthority { return RequestAuthority{ RequestID: requestID, WorkspaceRef: "mac-workspace", Operations: []iop.WorkspaceOperation{ iop.WorkspaceOperation_WORKSPACE_OPERATION_READ, iop.WorkspaceOperation_WORKSPACE_OPERATION_LIST, iop.WorkspaceOperation_WORKSPACE_OPERATION_WRITE, iop.WorkspaceOperation_WORKSPACE_OPERATION_DELETE, }, MaxReadBytes: 64, MaxWriteBytes: 64, MaxOutputBytes: 64, } } func TestRuntimeCatalog(t *testing.T) { root := t.TempDir() if _, err := NewRuntime([]*iop.WorkspaceConfig{testWorkspaceConfig(root)}, "darwin", nil); err != nil { t.Fatalf("NewRuntime(valid): %v", err) } for name, configs := range map[string][]*iop.WorkspaceConfig{ "wrong host": []*iop.WorkspaceConfig{testWorkspaceConfig(root)}, "missing": []*iop.WorkspaceConfig{testWorkspaceConfig(root + "/missing")}, "root": []*iop.WorkspaceConfig{testWorkspaceConfig("/")}, } { t.Run(name, func(t *testing.T) { host := "darwin" if name == "wrong host" { host = "linux" } if _, err := NewRuntime(configs, host, nil); err == nil { t.Fatal("NewRuntime succeeded") } }) } link := root + "-link" if err := os.Symlink(root, link); err != nil { t.Fatal(err) } if _, err := NewRuntime([]*iop.WorkspaceConfig{testWorkspaceConfig(link)}, "darwin", nil); err == nil { t.Fatal("symlink root was admitted") } const sentinel = "workspace-root-sentinel-do-not-disclose" _, err := NewRuntime([]*iop.WorkspaceConfig{testWorkspaceConfig(root + "/" + sentinel)}, "darwin", nil) if err == nil || strings.Contains(err.Error(), sentinel) { t.Fatalf("startup error = %v", err) } readOnly := testWorkspaceConfig(root) readOnly.Operations = []iop.WorkspaceOperation{iop.WorkspaceOperation_WORKSPACE_OPERATION_READ} readOnly.MaxWriteBytes = 0 readOnly.MaxOutputBytes = 0 readRuntime, err := NewRuntime([]*iop.WorkspaceConfig{readOnly}, "darwin", nil) if err != nil { t.Fatalf("read-only catalog: %v", err) } _ = readRuntime.Close() commandOnly := testWorkspaceConfig(root) commandOnly.Operations = []iop.WorkspaceOperation{iop.WorkspaceOperation_WORKSPACE_OPERATION_COMMAND} commandOnly.Commands = []*iop.WorkspaceCommandConfig{{Id: "test", Executable: "/usr/bin/true"}} commandOnly.MaxReadBytes = 0 commandOnly.MaxWriteBytes = 0 commandOnly.MaxOutputBytes = 1 commandOnly.MaxCommandTimeoutMs = 1 commandRuntime, err := NewRuntime([]*iop.WorkspaceConfig{commandOnly}, "darwin", nil) if err != nil { t.Fatalf("command-only catalog: %v", err) } _ = commandRuntime.Close() } func TestRuntimeOpen(t *testing.T) { rt, err := NewRuntime([]*iop.WorkspaceConfig{testWorkspaceConfig(t.TempDir())}, "darwin", nil) if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = rt.Close() }) authority := testRequestAuthority("request-1") first, err := rt.Open(authority) if err != nil { t.Fatal(err) } if first.internalPrefix != ".iop/job/request-1" { t.Fatalf("prefix=%q", first.internalPrefix) } if _, err := rt.Open(authority); err != nil { t.Fatalf("idempotent open: %v", err) } conflict := authority conflict.MaxReadBytes = 32 if _, err := rt.Open(conflict); err != ErrRequestConflict { t.Fatalf("conflict=%v", err) } invalid := authority invalid.RequestID = "bad/id" if _, err := rt.Open(invalid); err != ErrInvalidRequest { t.Fatalf("invalid=%v", err) } for name, mutate := range map[string]func(*RequestAuthority){ "operation widening": func(value *RequestAuthority) { value.Operations = append(value.Operations, iop.WorkspaceOperation_WORKSPACE_OPERATION_COMMAND) }, "read limit widening": func(value *RequestAuthority) { value.MaxReadBytes = 65 }, "disabled limit": func(value *RequestAuthority) { value.Operations = []iop.WorkspaceOperation{iop.WorkspaceOperation_WORKSPACE_OPERATION_READ} value.MaxWriteBytes = 1 value.MaxOutputBytes = 0 }, "unknown command": func(value *RequestAuthority) { value.Operations = []iop.WorkspaceOperation{iop.WorkspaceOperation_WORKSPACE_OPERATION_COMMAND} value.CommandIDs = []string{"missing"} value.MaxReadBytes, value.MaxWriteBytes, value.MaxOutputBytes, value.MaxCommandTimeoutMS = 0, 0, 1, 1 }, } { t.Run(name, func(t *testing.T) { candidate := testRequestAuthority("request-" + strings.ReplaceAll(name, " ", "-")) mutate(&candidate) if _, err := rt.Open(candidate); err != ErrInvalidRequest { t.Fatalf("Open = %v", err) } }) } lowered := RequestAuthority{ RequestID: "request-lowered", WorkspaceRef: "mac-workspace", Operations: []iop.WorkspaceOperation{iop.WorkspaceOperation_WORKSPACE_OPERATION_READ}, MaxReadBytes: 32, } request, err := rt.Open(lowered) if err != nil { t.Fatalf("lowered read-only authority: %v", err) } lowered.Operations[0] = iop.WorkspaceOperation_WORKSPACE_OPERATION_WRITE lowered.MaxReadBytes = 1 if _, ok := request.operations[iop.WorkspaceOperation_WORKSPACE_OPERATION_READ]; !ok || request.maxRead != 32 { t.Fatalf("request authority changed through caller mutation: %+v", request) } if _, err := first.internalPath(".iop/job/request-2/plan.md"); err == nil { t.Fatal("sibling internal path admitted") } if _, err := first.internalPath(".iop/job/request-1/plan.md"); err != nil { t.Fatalf("owned internal path: %v", err) } } func TestRuntimeCloseAndConcurrentIsolation(t *testing.T) { root := t.TempDir() rt, err := NewRuntime([]*iop.WorkspaceConfig{testWorkspaceConfig(root)}, "darwin", nil) if err != nil { t.Fatal(err) } var group sync.WaitGroup for i := 0; i < 32; i++ { group.Add(1) go func(index int) { defer group.Done() id := fmt.Sprintf("request-%d", index) authority := testRequestAuthority(id) if _, err := rt.Open(authority); err != nil { t.Errorf("Open(%s): %v", id, err) } }(i) } group.Wait() if err := rt.Close(); err != nil { t.Fatal(err) } if _, err := rt.Request("request-a"); err != ErrClosed { t.Fatalf("Request after close=%v", err) } if err := rt.Close(); err != nil { t.Fatalf("second close: %v", err) } } func TestRuntimeOpenCommandAuthority(t *testing.T) { config := testWorkspaceConfig(t.TempDir()) config.Operations = []iop.WorkspaceOperation{iop.WorkspaceOperation_WORKSPACE_OPERATION_COMMAND} config.Commands = []*iop.WorkspaceCommandConfig{ {Id: "format", Executable: "/usr/bin/true"}, {Id: "test", Executable: "/usr/bin/true"}, } config.MaxReadBytes = 0 config.MaxWriteBytes = 0 config.MaxOutputBytes = 64 config.MaxCommandTimeoutMs = 1000 rt, err := NewRuntime([]*iop.WorkspaceConfig{config}, "darwin", nil) if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = rt.Close() }) authority := RequestAuthority{ RequestID: "request-command", WorkspaceRef: "mac-workspace", Operations: []iop.WorkspaceOperation{iop.WorkspaceOperation_WORKSPACE_OPERATION_COMMAND}, CommandIDs: []string{"test"}, MaxOutputBytes: 32, MaxCommandTimeoutMS: 500, } request, err := rt.Open(authority) if err != nil { t.Fatalf("command-only open: %v", err) } authority.CommandIDs[0] = "format" if len(request.commandIDs) != 1 || request.commandIDs[0] != "test" || request.maxCommandTimeout != 500 { t.Fatalf("command authority was not copied: %+v", request) } for name, mutate := range map[string]func(*RequestAuthority){ "unknown command": func(value *RequestAuthority) { value.CommandIDs = []string{"unknown"} }, "missing command": func(value *RequestAuthority) { value.CommandIDs = nil }, "output widening": func(value *RequestAuthority) { value.MaxOutputBytes = 65 }, "timeout widening": func(value *RequestAuthority) { value.MaxCommandTimeoutMS = 1001 }, } { t.Run(name, func(t *testing.T) { candidate := RequestAuthority{ RequestID: "request-" + strings.ReplaceAll(name, " ", "-"), WorkspaceRef: "mac-workspace", Operations: []iop.WorkspaceOperation{iop.WorkspaceOperation_WORKSPACE_OPERATION_COMMAND}, CommandIDs: []string{"test"}, MaxOutputBytes: 32, MaxCommandTimeoutMS: 500, } mutate(&candidate) if _, err := rt.Open(candidate); err != ErrInvalidRequest { t.Fatalf("Open = %v", err) } }) } }