separate SessionManager (lifecycle) from DirStore (view)
SessionManager no longer embeds *DirStore. Instead: - SessionManager.Store() returns the DirStore view - Server uses mgr.Store() in route table, mgr for lifecycle - DirStore gains WithStat hook for custom stat logic - Removed: RunnableStore interface, storeConfig type - Tests updated to use .Store() for store operations
This commit is contained in:
parent
f5841c4a0f
commit
60d140acba
26
server.go
26
server.go
|
|
@ -29,7 +29,7 @@ type (
|
||||||
StoreEntry = store.StoreEntry
|
StoreEntry = store.StoreEntry
|
||||||
Store = store.Store
|
Store = store.Store
|
||||||
Session = store.Session
|
Session = store.Session
|
||||||
SessionStore = store.SessionStore
|
SessionManager = store.SessionManager
|
||||||
|
|
||||||
syntheticFileInfo = store.SyntheticFileInfo
|
syntheticFileInfo = store.SyntheticFileInfo
|
||||||
)
|
)
|
||||||
|
|
@ -170,7 +170,7 @@ type Server struct {
|
||||||
utilStore Store
|
utilStore Store
|
||||||
pluginStore Store
|
pluginStore Store
|
||||||
skillStore Store
|
skillStore Store
|
||||||
sessionStore *SessionStore
|
sessionStore *SessionManager
|
||||||
transcriptStore Store
|
transcriptStore Store
|
||||||
tmpStore Store
|
tmpStore Store
|
||||||
rootStore *store.DirStore
|
rootStore *store.DirStore
|
||||||
|
|
@ -215,7 +215,7 @@ func New(sink *olog.Sink, opts ...ServerOption) *Server {
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
o(s)
|
o(s)
|
||||||
}
|
}
|
||||||
s.sessionStore = store.NewSessionStore(store.SessionStoreConfig{
|
s.sessionStore = store.NewSessionManager(store.SessionManagerConfig{
|
||||||
AgentsDir: agentsDir,
|
AgentsDir: agentsDir,
|
||||||
SessionsDir: sessionsDir,
|
SessionsDir: sessionsDir,
|
||||||
Log: s.log,
|
Log: s.log,
|
||||||
|
|
@ -262,7 +262,7 @@ func (s *Server) storeRoutes() []storeRoute {
|
||||||
{"/a/", func() Store { return s.agentStore }},
|
{"/a/", func() Store { return s.agentStore }},
|
||||||
{"/p/", func() Store { return s.promptStore }},
|
{"/p/", func() Store { return s.promptStore }},
|
||||||
{"/m/", func() Store { return s.memStore }},
|
{"/m/", func() Store { return s.memStore }},
|
||||||
{"/s/", func() Store { return s.sessionStore }},
|
{"/s/", func() Store { return s.sessionStore.Store() }},
|
||||||
{"/sk/", func() Store { return s.skillStore }},
|
{"/sk/", func() Store { return s.skillStore }},
|
||||||
{"/u/", func() Store { return s.utilStore }},
|
{"/u/", func() Store { return s.utilStore }},
|
||||||
{"/x/", func() Store { return s.pluginStore }},
|
{"/x/", func() Store { return s.pluginStore }},
|
||||||
|
|
@ -342,7 +342,7 @@ func (s *Server) InGroup(group, user string) bool {
|
||||||
func (s *Server) fileOwnerGroup(path string) (uid, gid string) {
|
func (s *Server) fileOwnerGroup(path string) (uid, gid string) {
|
||||||
if strings.HasPrefix(path, "/s/") {
|
if strings.HasPrefix(path, "/s/") {
|
||||||
parts := strings.SplitN(strings.TrimPrefix(path, "/s/"), "/", 2)
|
parts := strings.SplitN(strings.TrimPrefix(path, "/s/"), "/", 2)
|
||||||
if len(parts) >= 1 && parts[0] != "new" && !isSessionStoreFile(path) {
|
if len(parts) >= 1 && parts[0] != "new" && !isSessionManagerFile(path) {
|
||||||
if sess := s.sessionStore.Session(parts[0]); sess != nil {
|
if sess := s.sessionStore.Session(parts[0]); sess != nil {
|
||||||
return sess.Uname(), "agent"
|
return sess.Uname(), "agent"
|
||||||
}
|
}
|
||||||
|
|
@ -557,14 +557,14 @@ func (s *Server) handle(cs *connState, fc *plan9.Fcall, ctx context.Context) *pl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// isSessionStoreFile reports whether path is a fixed file directly under /s/
|
// isSessionManagerFile reports whether path is a fixed file directly under /s/
|
||||||
// (i.e. /s/<name> where name is in sessionStoreFiles).
|
// (i.e. /s/<name> where name is in sessionStoreFiles).
|
||||||
func isSessionStoreFile(path string) bool {
|
func isSessionManagerFile(path string) bool {
|
||||||
name, ok := strings.CutPrefix(path, "/s/")
|
name, ok := strings.CutPrefix(path, "/s/")
|
||||||
if !ok || strings.Contains(name, "/") {
|
if !ok || strings.Contains(name, "/") {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
_, ok = store.SessionStoreFileMode(name)
|
_, ok = store.SessionManagerFileMode(name)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1174,8 +1174,8 @@ func (s *Server) makeStat(path string) plan9.Dir {
|
||||||
default:
|
default:
|
||||||
if path == "/backends" || path == "/help" {
|
if path == "/backends" || path == "/help" {
|
||||||
mode = 0444
|
mode = 0444
|
||||||
} else if isSessionStoreFile(path) {
|
} else if isSessionManagerFile(path) {
|
||||||
mode_, _ := store.SessionStoreFileMode(base)
|
mode_, _ := store.SessionManagerFileMode(base)
|
||||||
mode = plan9.Perm(mode_)
|
mode = plan9.Perm(mode_)
|
||||||
} else if strings.HasPrefix(path, "/a/") {
|
} else if strings.HasPrefix(path, "/a/") {
|
||||||
mode = 0666
|
mode = 0666
|
||||||
|
|
@ -1245,8 +1245,8 @@ func (s *Server) makeStat(path string) plan9.Dir {
|
||||||
// that check stat before reading (cat, 9pfuse, etc.) see non-zero size.
|
// that check stat before reading (cat, 9pfuse, etc.) see non-zero size.
|
||||||
if dir.Length == 0 && !isDir {
|
if dir.Length == 0 && !isDir {
|
||||||
switch {
|
switch {
|
||||||
case isSessionStoreFile(path):
|
case isSessionManagerFile(path):
|
||||||
if content, err := storeRead(s.sessionStore, base); err == nil {
|
if content, err := storeRead(s.sessionStore.Store(), base); err == nil {
|
||||||
dir.Length = uint64(len(content))
|
dir.Length = uint64(len(content))
|
||||||
}
|
}
|
||||||
case path == "/backends":
|
case path == "/backends":
|
||||||
|
|
@ -1296,7 +1296,7 @@ func (s *Server) makeStat(path string) plan9.Dir {
|
||||||
}
|
}
|
||||||
case strings.HasPrefix(path, "/s/"):
|
case strings.HasPrefix(path, "/s/"):
|
||||||
name := strings.TrimPrefix(path, "/s/")
|
name := strings.TrimPrefix(path, "/s/")
|
||||||
if info, err := s.sessionStore.Stat(name); err == nil {
|
if info, err := s.sessionStore.Store().Stat(name); err == nil {
|
||||||
dir.Length = uint64(info.Size())
|
dir.Length = uint64(info.Size())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ type DirStore struct {
|
||||||
deleteFn func([]string, string) error
|
deleteFn func([]string, string) error
|
||||||
renameFn func([]string, string, string) error
|
renameFn func([]string, string, string) error
|
||||||
openFn func([]string, string) (StoreEntry, error)
|
openFn func([]string, string) (StoreEntry, error)
|
||||||
|
statFn func([]string, string) (os.FileInfo, error)
|
||||||
children map[string]*DirStore
|
children map[string]*DirStore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,6 +68,11 @@ func WithOpener(fn func(dirs []string, name string) (StoreEntry, error)) DirOpti
|
||||||
return func(d *DirStore) { d.openFn = fn }
|
return func(d *DirStore) { d.openFn = fn }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithStat overrides how entries are stat'd.
|
||||||
|
func WithStat(fn func(dirs []string, name string) (os.FileInfo, error)) DirOption {
|
||||||
|
return func(d *DirStore) { d.statFn = fn }
|
||||||
|
}
|
||||||
|
|
||||||
// NewDirStore creates a DirStore backed by one or more directories.
|
// NewDirStore creates a DirStore backed by one or more directories.
|
||||||
// The first directory is the write target for unions.
|
// The first directory is the write target for unions.
|
||||||
func NewDirStore(dirs []string, perm os.FileMode, opts ...DirOption) *DirStore {
|
func NewDirStore(dirs []string, perm os.FileMode, opts ...DirOption) *DirStore {
|
||||||
|
|
@ -155,6 +161,10 @@ func (d *DirStore) Stat(name string) (os.FileInfo, error) {
|
||||||
if name == "idx" && d.indexFn != nil {
|
if name == "idx" && d.indexFn != nil {
|
||||||
return &SyntheticFileInfo{Name_: "idx", Mode_: 0444}, nil
|
return &SyntheticFileInfo{Name_: "idx", Mode_: 0444}, nil
|
||||||
}
|
}
|
||||||
|
// Custom stat hook.
|
||||||
|
if d.statFn != nil {
|
||||||
|
return d.statFn(d.dirs, name)
|
||||||
|
}
|
||||||
// Synthetic files: use opener for stat.
|
// Synthetic files: use opener for stat.
|
||||||
if d.openFn != nil {
|
if d.openFn != nil {
|
||||||
e, err := d.openFn(d.dirs, name)
|
e, err := d.openFn(d.dirs, name)
|
||||||
|
|
|
||||||
|
|
@ -92,15 +92,15 @@ var sessionStoreFiles = map[string]os.FileMode{
|
||||||
|
|
||||||
var sessionStoreOrder = []string{"new", "idx", "ls", "kill", "sh", "b", "bfg", "bbg", "cleanup"}
|
var sessionStoreOrder = []string{"new", "idx", "ls", "kill", "sh", "b", "bfg", "bbg", "cleanup"}
|
||||||
|
|
||||||
// SessionStoreFileMode returns the mode for a fixed session store file,
|
// SessionManagerFileMode returns the mode for a fixed session store file,
|
||||||
// or 0 and false if the name is not a fixed file.
|
// or 0 and false if the name is not a fixed file.
|
||||||
func SessionStoreFileMode(name string) (os.FileMode, bool) {
|
func SessionManagerFileMode(name string) (os.FileMode, bool) {
|
||||||
m, ok := sessionStoreFiles[name]
|
m, ok := sessionStoreFiles[name]
|
||||||
return m, ok
|
return m, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// SessionStoreConfig holds the dependencies for a SessionStore.
|
// SessionManagerConfig holds the dependencies for a SessionManager.
|
||||||
type SessionStoreConfig struct {
|
type SessionManagerConfig struct {
|
||||||
AgentsDir string
|
AgentsDir string
|
||||||
SessionsDir string
|
SessionsDir string
|
||||||
Log *olog.Logger
|
Log *olog.Logger
|
||||||
|
|
@ -122,33 +122,37 @@ type SessionStoreConfig struct {
|
||||||
Yolo bool
|
Yolo bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// SessionStore implements Store for session management.
|
// SessionManager manages session lifecycle and exposes sessions as a DirStore.
|
||||||
type SessionStore struct {
|
type SessionManager struct {
|
||||||
*DirStore
|
store *DirStore
|
||||||
cfg SessionStoreConfig
|
cfg SessionManagerConfig
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
sessions map[string]*Session
|
sessions map[string]*Session
|
||||||
nextUID atomic.Uint32 // incrementing principal counter
|
nextUID atomic.Uint32 // incrementing principal counter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store returns the DirStore view of the session namespace.
|
||||||
|
func (s *SessionManager) Store() *DirStore { return s.store }
|
||||||
|
|
||||||
// nextUname generates the next uname atomically.
|
// nextUname generates the next uname atomically.
|
||||||
func (s *SessionStore) nextUname() string {
|
func (s *SessionManager) nextUname() string {
|
||||||
return fmt.Sprintf("%d", s.nextUID.Add(1))
|
return fmt.Sprintf("%d", s.nextUID.Add(1))
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSessionStore(cfg SessionStoreConfig) *SessionStore {
|
func NewSessionManager(cfg SessionManagerConfig) *SessionManager {
|
||||||
if cfg.ReadFile == nil {
|
if cfg.ReadFile == nil {
|
||||||
cfg.ReadFile = os.ReadFile
|
cfg.ReadFile = os.ReadFile
|
||||||
}
|
}
|
||||||
if cfg.MkdirAll == nil {
|
if cfg.MkdirAll == nil {
|
||||||
cfg.MkdirAll = os.MkdirAll
|
cfg.MkdirAll = os.MkdirAll
|
||||||
}
|
}
|
||||||
ss := &SessionStore{
|
ss := &SessionManager{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
sessions: make(map[string]*Session),
|
sessions: make(map[string]*Session),
|
||||||
}
|
}
|
||||||
ss.nextUID.Store(9999)
|
ss.nextUID.Store(9999)
|
||||||
ss.DirStore = NewDirStore(nil, 0,
|
ss.store = NewDirStore(nil, 0,
|
||||||
|
WithStat(func(_ []string, name string) (os.FileInfo, error) { return ss.stat(name) }),
|
||||||
WithOpener(func(_ []string, name string) (StoreEntry, error) { return ss.openEntry(name) }),
|
WithOpener(func(_ []string, name string) (StoreEntry, error) { return ss.openEntry(name) }),
|
||||||
WithLister(func(_ []string) ([]os.DirEntry, error) { return ss.list() }),
|
WithLister(func(_ []string) ([]os.DirEntry, error) { return ss.list() }),
|
||||||
WithDeleter(func(_ []string, name string) error { return ss.del(name) }),
|
WithDeleter(func(_ []string, name string) error { return ss.del(name) }),
|
||||||
|
|
@ -159,13 +163,13 @@ func NewSessionStore(cfg SessionStoreConfig) *SessionStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddSession inserts a pre-built session into the store.
|
// AddSession inserts a pre-built session into the store.
|
||||||
func (s *SessionStore) AddSession(sess *Session) {
|
func (s *SessionManager) AddSession(sess *Session) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.sessions[sess.RunnableID()] = sess
|
s.sessions[sess.RunnableID()] = sess
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SessionStore) list() ([]os.DirEntry, error) {
|
func (s *SessionManager) list() ([]os.DirEntry, error) {
|
||||||
entries := make([]os.DirEntry, 0, len(sessionStoreOrder))
|
entries := make([]os.DirEntry, 0, len(sessionStoreOrder))
|
||||||
for _, name := range sessionStoreOrder {
|
for _, name := range sessionStoreOrder {
|
||||||
entries = append(entries, FileEntry(name, sessionStoreFiles[name]))
|
entries = append(entries, FileEntry(name, sessionStoreFiles[name]))
|
||||||
|
|
@ -179,7 +183,7 @@ func (s *SessionStore) list() ([]os.DirEntry, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListDir lists entries in a subdirectory (e.g. "{id}", "{id}/t", "{id}/t/{subdir}").
|
// ListDir lists entries in a subdirectory (e.g. "{id}", "{id}/t", "{id}/t/{subdir}").
|
||||||
func (s *SessionStore) ListDir(name string) ([]os.DirEntry, error) {
|
func (s *SessionManager) ListDir(name string) ([]os.DirEntry, error) {
|
||||||
parts := strings.SplitN(name, "/", 3)
|
parts := strings.SplitN(name, "/", 3)
|
||||||
sessID := parts[0]
|
sessID := parts[0]
|
||||||
sess := s.Session(sessID)
|
sess := s.Session(sessID)
|
||||||
|
|
@ -215,11 +219,7 @@ func (s *SessionStore) ListDir(name string) ([]os.DirEntry, error) {
|
||||||
return nil, fmt.Errorf("%s: not a directory", name)
|
return nil, fmt.Errorf("%s: not a directory", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SessionStore) Stat(name string) (os.FileInfo, error) {
|
func (s *SessionManager) stat(name string) (os.FileInfo, error) {
|
||||||
return s.stat(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SessionStore) stat(name string) (os.FileInfo, error) {
|
|
||||||
// Top-level fixed files (new, idx, sh, etc.)
|
// Top-level fixed files (new, idx, sh, etc.)
|
||||||
if mode, ok := sessionStoreFiles[name]; ok {
|
if mode, ok := sessionStoreFiles[name]; ok {
|
||||||
return &SyntheticFileInfo{Name_: name, Mode_: mode}, nil
|
return &SyntheticFileInfo{Name_: name, Mode_: mode}, nil
|
||||||
|
|
@ -255,7 +255,7 @@ func (s *SessionStore) stat(name string) (os.FileInfo, error) {
|
||||||
return sfs.Stat(parts[1])
|
return sfs.Stat(parts[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SessionStore) openEntry(name string) (StoreEntry, error) {
|
func (s *SessionManager) openEntry(name string) (StoreEntry, error) {
|
||||||
notBlocking := func(context.Context, string) ([]byte, string, error) {
|
notBlocking := func(context.Context, string) ([]byte, string, error) {
|
||||||
return nil, "", fmt.Errorf("blocking read not supported")
|
return nil, "", fmt.Errorf("blocking read not supported")
|
||||||
}
|
}
|
||||||
|
|
@ -330,7 +330,7 @@ func (s *SessionStore) openEntry(name string) (StoreEntry, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (s *SessionStore) openFilteredToolIdx(allowed map[string]bool) (StoreEntry, error) {
|
func (s *SessionManager) openFilteredToolIdx(allowed map[string]bool) (StoreEntry, error) {
|
||||||
base, err := s.cfg.ToolStore.Open("idx")
|
base, err := s.cfg.ToolStore.Open("idx")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -364,7 +364,7 @@ func filterToolIdx(data []byte, allowed map[string]bool) []byte {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SessionStore) create(name string) error {
|
func (s *SessionManager) create(name string) error {
|
||||||
// Tool create: {id}/t/{rel}
|
// Tool create: {id}/t/{rel}
|
||||||
parts := strings.SplitN(name, "/", 3)
|
parts := strings.SplitN(name, "/", 3)
|
||||||
if len(parts) == 3 && parts[1] == "t" && s.cfg.ToolStore != nil {
|
if len(parts) == 3 && parts[1] == "t" && s.cfg.ToolStore != nil {
|
||||||
|
|
@ -374,7 +374,7 @@ func (s *SessionStore) create(name string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MkdirAll creates a directory within the session's tool store.
|
// MkdirAll creates a directory within the session's tool store.
|
||||||
func (s *SessionStore) MkdirAll(name string) error {
|
func (s *SessionManager) MkdirAll(name string) error {
|
||||||
parts := strings.SplitN(name, "/", 3)
|
parts := strings.SplitN(name, "/", 3)
|
||||||
if len(parts) == 3 && parts[1] == "t" {
|
if len(parts) == 3 && parts[1] == "t" {
|
||||||
type mkdirAller interface {
|
type mkdirAller interface {
|
||||||
|
|
@ -387,7 +387,7 @@ func (s *SessionStore) MkdirAll(name string) error {
|
||||||
return fmt.Errorf("mkdir not supported: %s", name)
|
return fmt.Errorf("mkdir not supported: %s", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SessionStore) del(name string) error {
|
func (s *SessionManager) del(name string) error {
|
||||||
parts := strings.SplitN(name, "/", 3)
|
parts := strings.SplitN(name, "/", 3)
|
||||||
sessID := parts[0]
|
sessID := parts[0]
|
||||||
// Delete session directory itself.
|
// Delete session directory itself.
|
||||||
|
|
@ -413,14 +413,14 @@ func (s *SessionStore) del(name string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Session returns the session for the given ID, or nil.
|
// Session returns the session for the given ID, or nil.
|
||||||
func (s *SessionStore) Session(id string) *Session {
|
func (s *SessionManager) Session(id string) *Session {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
defer s.mu.RUnlock()
|
defer s.mu.RUnlock()
|
||||||
return s.sessions[id]
|
return s.sessions[id]
|
||||||
}
|
}
|
||||||
|
|
||||||
// SessionByUname returns the session with the given uname (principal), or nil.
|
// SessionByUname returns the session with the given uname (principal), or nil.
|
||||||
func (s *SessionStore) SessionByUname(uname string) *Session {
|
func (s *SessionManager) SessionByUname(uname string) *Session {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
defer s.mu.RUnlock()
|
defer s.mu.RUnlock()
|
||||||
for _, sess := range s.sessions {
|
for _, sess := range s.sessions {
|
||||||
|
|
@ -432,26 +432,26 @@ func (s *SessionStore) SessionByUname(uname string) *Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenStore returns a DirStore for the given session ID.
|
// OpenStore returns a DirStore for the given session ID.
|
||||||
func (s *SessionStore) OpenStore(id string) (*DirStore, error) {
|
func (s *SessionManager) OpenStore(id string) (*DirStore, error) {
|
||||||
if sess := s.Session(id); sess != nil {
|
if sess := s.Session(id); sess != nil {
|
||||||
return s.openStore(sess)
|
return s.openStore(sess)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("session not found: %s", id)
|
return nil, fmt.Errorf("session not found: %s", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SessionStore) openStore(sess *Session) (*DirStore, error) {
|
func (s *SessionManager) openStore(sess *Session) (*DirStore, error) {
|
||||||
id := sess.RunnableID()
|
id := sess.RunnableID()
|
||||||
return NewSessionFileStore(
|
return NewSessionFileStore(
|
||||||
sess,
|
sess,
|
||||||
s.cfg.Log,
|
s.cfg.Log,
|
||||||
func() { s.KillSession(id) },
|
func() { s.KillSession(id) },
|
||||||
func(newID string) error { return s.Rename(id, newID) },
|
func(newID string) error { return s.renameSession(id, newID) },
|
||||||
s.cfg.SaveTranscript,
|
s.cfg.SaveTranscript,
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InterruptAll interrupts every active session.
|
// InterruptAll interrupts every active session.
|
||||||
func (s *SessionStore) InterruptAll() {
|
func (s *SessionManager) InterruptAll() {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
defer s.mu.RUnlock()
|
defer s.mu.RUnlock()
|
||||||
for _, sess := range s.sessions {
|
for _, sess := range s.sessions {
|
||||||
|
|
@ -460,7 +460,7 @@ func (s *SessionStore) InterruptAll() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown kills all active sessions.
|
// Shutdown kills all active sessions.
|
||||||
func (s *SessionStore) Shutdown() {
|
func (s *SessionManager) Shutdown() {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
ids := make([]string, 0, len(s.sessions))
|
ids := make([]string, 0, len(s.sessions))
|
||||||
for id := range s.sessions {
|
for id := range s.sessions {
|
||||||
|
|
@ -472,7 +472,7 @@ func (s *SessionStore) Shutdown() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SessionStore) KillSession(id string) {
|
func (s *SessionManager) KillSession(id string) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
sess := s.sessions[id]
|
sess := s.sessions[id]
|
||||||
delete(s.sessions, id)
|
delete(s.sessions, id)
|
||||||
|
|
@ -484,7 +484,7 @@ func (s *SessionStore) KillSession(id string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SessionStore) index() []byte {
|
func (s *SessionManager) index() []byte {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
ids := make([]string, 0, len(s.sessions))
|
ids := make([]string, 0, len(s.sessions))
|
||||||
|
|
@ -506,7 +506,7 @@ func (s *SessionStore) index() []byte {
|
||||||
return []byte(sb.String())
|
return []byte(sb.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SessionStore) createSession(args []string) error {
|
func (s *SessionManager) createSession(args []string) error {
|
||||||
name := ""
|
name := ""
|
||||||
backendOverride := ""
|
backendOverride := ""
|
||||||
modelOverride := ""
|
modelOverride := ""
|
||||||
|
|
@ -641,7 +641,7 @@ func (s *SessionStore) createSession(args []string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SessionStore) renameSession(old, new string) error {
|
func (s *SessionManager) renameSession(old, new string) error {
|
||||||
// Tool rename: {id}/t/{oldRel} -> newName
|
// Tool rename: {id}/t/{oldRel} -> newName
|
||||||
if strings.Contains(old, "/t/") {
|
if strings.Contains(old, "/t/") {
|
||||||
parts := strings.SplitN(old, "/", 3)
|
parts := strings.SplitN(old, "/", 3)
|
||||||
|
|
@ -689,7 +689,7 @@ func (s *SessionStore) renameSession(old, new string) error {
|
||||||
|
|
||||||
// OnRename must be called without holding s.mu to avoid deadlock
|
// OnRename must be called without holding s.mu to avoid deadlock
|
||||||
// with connection handlers that hold connState.mu and call into
|
// with connection handlers that hold connState.mu and call into
|
||||||
// SessionStore (e.g. SessionByUname during Tattach).
|
// SessionManager (e.g. SessionByUname during Tattach).
|
||||||
if s.cfg.OnRename != nil {
|
if s.cfg.OnRename != nil {
|
||||||
s.cfg.OnRename(oldID, newID)
|
s.cfg.OnRename(oldID, newID)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -142,10 +142,10 @@ func seedSkill(t *testing.T, base, name string) {
|
||||||
os.WriteFile(filepath.Join(d, "SKILL.md"), []byte("---\ndescription: test\n---\n"), 0644)
|
os.WriteFile(filepath.Join(d, "SKILL.md"), []byte("---\ndescription: test\n---\n"), 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSessionStore(t *testing.T) *store.SessionStore {
|
func newSessionManager(t *testing.T) *store.SessionManager {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
sink := testSink()
|
sink := testSink()
|
||||||
return store.NewSessionStore(store.SessionStoreConfig{
|
return store.NewSessionManager(store.SessionManagerConfig{
|
||||||
Log: sink.NewLogger("test"),
|
Log: sink.NewLogger("test"),
|
||||||
Sink: sink,
|
Sink: sink,
|
||||||
ReadFile: func(string) ([]byte, error) { return []byte("#!/bin/sh\n"), nil },
|
ReadFile: func(string) ([]byte, error) { return []byte("#!/bin/sh\n"), nil },
|
||||||
|
|
@ -153,10 +153,10 @@ func newSessionStore(t *testing.T) *store.SessionStore {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSessionStoreWithCore(t *testing.T) *store.SessionStore {
|
func newSessionManagerWithCore(t *testing.T) *store.SessionManager {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
sink := testSink()
|
sink := testSink()
|
||||||
return store.NewSessionStore(store.SessionStoreConfig{
|
return store.NewSessionManager(store.SessionManagerConfig{
|
||||||
Log: sink.NewLogger("test"),
|
Log: sink.NewLogger("test"),
|
||||||
Sink: sink,
|
Sink: sink,
|
||||||
ReadFile: func(string) ([]byte, error) { return []byte("#!/bin/sh\n"), nil },
|
ReadFile: func(string) ([]byte, error) { return []byte("#!/bin/sh\n"), nil },
|
||||||
|
|
@ -449,48 +449,48 @@ func TestSessionAppendLog(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionStoreFileMode(t *testing.T) {
|
func TestSessionManagerFileMode(t *testing.T) {
|
||||||
if m, ok := store.SessionStoreFileMode("new"); !ok || m != 0666 {
|
if m, ok := store.SessionManagerFileMode("new"); !ok || m != 0666 {
|
||||||
t.Errorf("SessionStoreFileMode(new) = %o, %v", m, ok)
|
t.Errorf("SessionManagerFileMode(new) = %o, %v", m, ok)
|
||||||
}
|
}
|
||||||
if _, ok := store.SessionStoreFileMode("bogus"); ok {
|
if _, ok := store.SessionManagerFileMode("bogus"); ok {
|
||||||
t.Error("SessionStoreFileMode(bogus) should be false")
|
t.Error("SessionManagerFileMode(bogus) should be false")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== SessionStore =====
|
// ===== SessionManager =====
|
||||||
|
|
||||||
func TestSessionStoreReadableContract(t *testing.T) {
|
func TestSessionManagerReadableContract(t *testing.T) {
|
||||||
checkReadableContract(t, newSessionStore(t), "new")
|
checkReadableContract(t, newSessionManager(t).Store(), "new")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionStoreGetIdx(t *testing.T) {
|
func TestSessionManagerGetIdx(t *testing.T) {
|
||||||
s := newSessionStore(t)
|
s := newSessionManager(t)
|
||||||
sess := testSession("abc")
|
sess := testSession("abc")
|
||||||
defer sess.Cancel()
|
defer sess.Cancel()
|
||||||
s.AddSession(sess)
|
s.AddSession(sess)
|
||||||
|
|
||||||
data := storeRead(t, s, "idx")
|
data := storeRead(t, s.Store(), "idx")
|
||||||
if !strings.Contains(string(data), "abc") {
|
if !strings.Contains(string(data), "abc") {
|
||||||
t.Errorf("idx = %q; want to contain abc", data)
|
t.Errorf("idx = %q; want to contain abc", data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionStoreGetScript(t *testing.T) {
|
func TestSessionManagerGetScript(t *testing.T) {
|
||||||
s := newSessionStore(t)
|
s := newSessionManager(t)
|
||||||
data := storeRead(t, s, "ls")
|
data := storeRead(t, s.Store(), "ls")
|
||||||
if string(data) != "#!/bin/sh\n" {
|
if string(data) != "#!/bin/sh\n" {
|
||||||
t.Errorf("Read(ls) = %q", data)
|
t.Errorf("Read(ls) = %q", data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionStoreStatSession(t *testing.T) {
|
func TestSessionManagerStatSession(t *testing.T) {
|
||||||
s := newSessionStore(t)
|
s := newSessionManager(t)
|
||||||
sess := testSession("s1")
|
sess := testSession("s1")
|
||||||
defer sess.Cancel()
|
defer sess.Cancel()
|
||||||
s.AddSession(sess)
|
s.AddSession(sess)
|
||||||
|
|
||||||
fi, err := s.Stat("s1")
|
fi, err := s.Store().Stat("s1")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Stat(s1): %v", err)
|
t.Fatalf("Stat(s1): %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -499,13 +499,13 @@ func TestSessionStoreStatSession(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionStoreListIncludesSessions(t *testing.T) {
|
func TestSessionManagerListIncludesSessions(t *testing.T) {
|
||||||
s := newSessionStore(t)
|
s := newSessionManager(t)
|
||||||
sess := testSession("s1")
|
sess := testSession("s1")
|
||||||
defer sess.Cancel()
|
defer sess.Cancel()
|
||||||
s.AddSession(sess)
|
s.AddSession(sess)
|
||||||
|
|
||||||
entries, _ := s.List()
|
entries, _ := s.Store().List()
|
||||||
found := false
|
found := false
|
||||||
for _, e := range entries {
|
for _, e := range entries {
|
||||||
if e.Name() == "s1" {
|
if e.Name() == "s1" {
|
||||||
|
|
@ -517,9 +517,9 @@ func TestSessionStoreListIncludesSessions(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionStoreWriteNotWritable(t *testing.T) {
|
func TestSessionManagerWriteNotWritable(t *testing.T) {
|
||||||
s := newSessionStore(t)
|
s := newSessionManager(t)
|
||||||
e, err := s.Open("idx")
|
e, err := s.Store().Open("idx")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Open: %v", err)
|
t.Fatalf("Open: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -528,19 +528,19 @@ func TestSessionStoreWriteNotWritable(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionStoreCreateErrors(t *testing.T) {
|
func TestSessionManagerCreateErrors(t *testing.T) {
|
||||||
s := newSessionStore(t)
|
s := newSessionManager(t)
|
||||||
if err := s.Create("x"); err == nil {
|
if err := s.Store().Create("x"); err == nil {
|
||||||
t.Error("Create should always error")
|
t.Error("Create should always error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionStoreDeleteAndKill(t *testing.T) {
|
func TestSessionManagerDeleteAndKill(t *testing.T) {
|
||||||
s := newSessionStore(t)
|
s := newSessionManager(t)
|
||||||
sess := testSession("s1")
|
sess := testSession("s1")
|
||||||
s.AddSession(sess)
|
s.AddSession(sess)
|
||||||
|
|
||||||
if err := s.Delete("s1"); err != nil {
|
if err := s.Store().Delete("s1"); err != nil {
|
||||||
t.Fatalf("Delete: %v", err)
|
t.Fatalf("Delete: %v", err)
|
||||||
}
|
}
|
||||||
if s.Session("s1") != nil {
|
if s.Session("s1") != nil {
|
||||||
|
|
@ -550,13 +550,13 @@ func TestSessionStoreDeleteAndKill(t *testing.T) {
|
||||||
if !core.closed {
|
if !core.closed {
|
||||||
t.Error("core should be closed after Delete")
|
t.Error("core should be closed after Delete")
|
||||||
}
|
}
|
||||||
if err := s.Delete("nope"); err == nil {
|
if err := s.Store().Delete("nope"); err == nil {
|
||||||
t.Error("Delete(nonexistent) should error")
|
t.Error("Delete(nonexistent) should error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionStoreSession(t *testing.T) {
|
func TestSessionManagerSession(t *testing.T) {
|
||||||
s := newSessionStore(t)
|
s := newSessionManager(t)
|
||||||
if s.Session("nope") != nil {
|
if s.Session("nope") != nil {
|
||||||
t.Error("Session(nonexistent) should be nil")
|
t.Error("Session(nonexistent) should be nil")
|
||||||
}
|
}
|
||||||
|
|
@ -568,8 +568,8 @@ func TestSessionStoreSession(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionStoreInterruptAll(t *testing.T) {
|
func TestSessionManagerInterruptAll(t *testing.T) {
|
||||||
s := newSessionStore(t)
|
s := newSessionManager(t)
|
||||||
sess := testSession("s1")
|
sess := testSession("s1")
|
||||||
defer sess.Cancel()
|
defer sess.Cancel()
|
||||||
sess.Core.(*stubCore).running = true
|
sess.Core.(*stubCore).running = true
|
||||||
|
|
@ -578,8 +578,8 @@ func TestSessionStoreInterruptAll(t *testing.T) {
|
||||||
s.InterruptAll() // should not panic
|
s.InterruptAll() // should not panic
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionStoreShutdown(t *testing.T) {
|
func TestSessionManagerShutdown(t *testing.T) {
|
||||||
s := newSessionStore(t)
|
s := newSessionManager(t)
|
||||||
sess := testSession("s1")
|
sess := testSession("s1")
|
||||||
s.AddSession(sess)
|
s.AddSession(sess)
|
||||||
|
|
||||||
|
|
@ -589,10 +589,10 @@ func TestSessionStoreShutdown(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionStoreRename(t *testing.T) {
|
func TestSessionManagerRename(t *testing.T) {
|
||||||
sink := testSink()
|
sink := testSink()
|
||||||
var renamed [2]string
|
var renamed [2]string
|
||||||
s := store.NewSessionStore(store.SessionStoreConfig{
|
s := store.NewSessionManager(store.SessionManagerConfig{
|
||||||
Log: sink.NewLogger("test"),
|
Log: sink.NewLogger("test"),
|
||||||
Sink: sink,
|
Sink: sink,
|
||||||
ReadFile: func(string) ([]byte, error) { return nil, nil },
|
ReadFile: func(string) ([]byte, error) { return nil, nil },
|
||||||
|
|
@ -603,7 +603,7 @@ func TestSessionStoreRename(t *testing.T) {
|
||||||
defer sess.Cancel()
|
defer sess.Cancel()
|
||||||
s.AddSession(sess)
|
s.AddSession(sess)
|
||||||
|
|
||||||
if err := s.Rename("old", "new"); err != nil {
|
if err := s.Store().Rename("old", "new"); err != nil {
|
||||||
t.Fatalf("Rename: %v", err)
|
t.Fatalf("Rename: %v", err)
|
||||||
}
|
}
|
||||||
if s.Session("old") != nil {
|
if s.Session("old") != nil {
|
||||||
|
|
@ -617,30 +617,30 @@ func TestSessionStoreRename(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionStoreRenameErrors(t *testing.T) {
|
func TestSessionManagerRenameErrors(t *testing.T) {
|
||||||
s := newSessionStore(t)
|
s := newSessionManager(t)
|
||||||
// nonexistent
|
// nonexistent
|
||||||
if err := s.Rename("nope", "x"); err == nil {
|
if err := s.Store().Rename("nope", "x"); err == nil {
|
||||||
t.Error("Rename(nonexistent) should error")
|
t.Error("Rename(nonexistent) should error")
|
||||||
}
|
}
|
||||||
// duplicate
|
// duplicate
|
||||||
s.AddSession(testSession("a"))
|
s.AddSession(testSession("a"))
|
||||||
s.AddSession(testSession("b"))
|
s.AddSession(testSession("b"))
|
||||||
if err := s.Rename("a", "b"); err == nil {
|
if err := s.Store().Rename("a", "b"); err == nil {
|
||||||
t.Error("Rename to existing should error")
|
t.Error("Rename to existing should error")
|
||||||
}
|
}
|
||||||
// running
|
// running
|
||||||
sess := testSession("r")
|
sess := testSession("r")
|
||||||
sess.Core.(*stubCore).running = true
|
sess.Core.(*stubCore).running = true
|
||||||
s.AddSession(sess)
|
s.AddSession(sess)
|
||||||
if err := s.Rename("r", "r2"); err == nil {
|
if err := s.Store().Rename("r", "r2"); err == nil {
|
||||||
t.Error("Rename while running should error")
|
t.Error("Rename while running should error")
|
||||||
}
|
}
|
||||||
// SetSessionID error
|
// SetSessionID error
|
||||||
sess2 := testSession("sid")
|
sess2 := testSession("sid")
|
||||||
sess2.Core.(*stubCore).setSessionIDErr = fmt.Errorf("id error")
|
sess2.Core.(*stubCore).setSessionIDErr = fmt.Errorf("id error")
|
||||||
s.AddSession(sess2)
|
s.AddSession(sess2)
|
||||||
if err := s.Rename("sid", "sid2"); err == nil {
|
if err := s.Store().Rename("sid", "sid2"); err == nil {
|
||||||
t.Error("Rename with SetSessionID error should error")
|
t.Error("Rename with SetSessionID error should error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1179,8 +1179,8 @@ func TestSessionFileStoreEntryStat(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionStoreOpenStore(t *testing.T) {
|
func TestSessionManagerOpenStore(t *testing.T) {
|
||||||
s := newSessionStore(t)
|
s := newSessionManager(t)
|
||||||
sess := testSession("s1")
|
sess := testSession("s1")
|
||||||
defer sess.Cancel()
|
defer sess.Cancel()
|
||||||
s.AddSession(sess)
|
s.AddSession(sess)
|
||||||
|
|
@ -1352,11 +1352,11 @@ func TestSessionInterrupt(t *testing.T) {
|
||||||
// stubCore.Interrupt is a no-op; just ensure it doesn't panic
|
// stubCore.Interrupt is a no-op; just ensure it doesn't panic
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== SessionStore.createSession =====
|
// ===== SessionManager.createSession =====
|
||||||
|
|
||||||
func TestSessionStoreCreateSessionViaWrite(t *testing.T) {
|
func TestSessionManagerCreateSessionViaWrite(t *testing.T) {
|
||||||
s := newSessionStoreWithCore(t)
|
s := newSessionManagerWithCore(t)
|
||||||
e, err := s.Open("new")
|
e, err := s.Store().Open("new")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Open(new): %v", err)
|
t.Fatalf("Open(new): %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -1369,17 +1369,17 @@ func TestSessionStoreCreateSessionViaWrite(t *testing.T) {
|
||||||
s.KillSession("testsess")
|
s.KillSession("testsess")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionStoreCreateSessionNoCwd(t *testing.T) {
|
func TestSessionManagerCreateSessionNoCwd(t *testing.T) {
|
||||||
s := newSessionStoreWithCore(t)
|
s := newSessionManagerWithCore(t)
|
||||||
e, _ := s.Open("new")
|
e, _ := s.Store().Open("new")
|
||||||
if err := e.Write([]byte("name=nocwd")); err == nil {
|
if err := e.Write([]byte("name=nocwd")); err == nil {
|
||||||
t.Error("expected error for missing cwd")
|
t.Error("expected error for missing cwd")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionStoreCreateSessionDuplicate(t *testing.T) {
|
func TestSessionManagerCreateSessionDuplicate(t *testing.T) {
|
||||||
s := newSessionStoreWithCore(t)
|
s := newSessionManagerWithCore(t)
|
||||||
e, _ := s.Open("new")
|
e, _ := s.Store().Open("new")
|
||||||
e.Write([]byte("name=dup cwd=/tmp")) //nolint:errcheck
|
e.Write([]byte("name=dup cwd=/tmp")) //nolint:errcheck
|
||||||
if err := e.Write([]byte("name=dup cwd=/tmp")); err == nil {
|
if err := e.Write([]byte("name=dup cwd=/tmp")); err == nil {
|
||||||
t.Error("expected error for duplicate session name")
|
t.Error("expected error for duplicate session name")
|
||||||
|
|
@ -1387,23 +1387,23 @@ func TestSessionStoreCreateSessionDuplicate(t *testing.T) {
|
||||||
s.KillSession("dup")
|
s.KillSession("dup")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionStoreCreateSessionBadOption(t *testing.T) {
|
func TestSessionManagerCreateSessionBadOption(t *testing.T) {
|
||||||
s := newSessionStoreWithCore(t)
|
s := newSessionManagerWithCore(t)
|
||||||
e, _ := s.Open("new")
|
e, _ := s.Store().Open("new")
|
||||||
if err := e.Write([]byte("bogus cwd=/tmp")); err == nil {
|
if err := e.Write([]byte("bogus cwd=/tmp")); err == nil {
|
||||||
t.Error("expected error for invalid option")
|
t.Error("expected error for invalid option")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionStoreCreateSessionUnknownKey(t *testing.T) {
|
func TestSessionManagerCreateSessionUnknownKey(t *testing.T) {
|
||||||
s := newSessionStoreWithCore(t)
|
s := newSessionManagerWithCore(t)
|
||||||
e, _ := s.Open("new")
|
e, _ := s.Store().Open("new")
|
||||||
if err := e.Write([]byte("unknown=x cwd=/tmp")); err == nil {
|
if err := e.Write([]byte("unknown=x cwd=/tmp")); err == nil {
|
||||||
t.Error("expected error for unknown key")
|
t.Error("expected error for unknown key")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionStoreCreateSessionEnvExpansion(t *testing.T) {
|
func TestSessionManagerCreateSessionEnvExpansion(t *testing.T) {
|
||||||
home, err := os.UserHomeDir()
|
home, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Skip("no home dir")
|
t.Skip("no home dir")
|
||||||
|
|
@ -1411,7 +1411,7 @@ func TestSessionStoreCreateSessionEnvExpansion(t *testing.T) {
|
||||||
t.Setenv("HOME", home)
|
t.Setenv("HOME", home)
|
||||||
var gotCwd string
|
var gotCwd string
|
||||||
sink := testSink()
|
sink := testSink()
|
||||||
s := store.NewSessionStore(store.SessionStoreConfig{
|
s := store.NewSessionManager(store.SessionManagerConfig{
|
||||||
Log: sink.NewLogger("test"),
|
Log: sink.NewLogger("test"),
|
||||||
Sink: sink,
|
Sink: sink,
|
||||||
ReadFile: func(string) ([]byte, error) { return []byte("#!/bin/sh\n"), nil },
|
ReadFile: func(string) ([]byte, error) { return []byte("#!/bin/sh\n"), nil },
|
||||||
|
|
@ -1421,7 +1421,7 @@ func TestSessionStoreCreateSessionEnvExpansion(t *testing.T) {
|
||||||
return &stubCore{state: "idle", backend: "stub", model: "m", agentName: agentName, cwd: cwd}, nil
|
return &stubCore{state: "idle", backend: "stub", model: "m", agentName: agentName, cwd: cwd}, nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
e, _ := s.Open("new")
|
e, _ := s.Store().Open("new")
|
||||||
if err := e.Write([]byte("name=envtest cwd=$HOME/")); err != nil {
|
if err := e.Write([]byte("name=envtest cwd=$HOME/")); err != nil {
|
||||||
t.Fatalf("Write(new): %v", err)
|
t.Fatalf("Write(new): %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -1431,14 +1431,14 @@ func TestSessionStoreCreateSessionEnvExpansion(t *testing.T) {
|
||||||
s.KillSession("envtest")
|
s.KillSession("envtest")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionStoreCreateSessionTildeExpansion(t *testing.T) {
|
func TestSessionManagerCreateSessionTildeExpansion(t *testing.T) {
|
||||||
home, err := os.UserHomeDir()
|
home, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Skip("no home dir")
|
t.Skip("no home dir")
|
||||||
}
|
}
|
||||||
var gotCwd string
|
var gotCwd string
|
||||||
sink := testSink()
|
sink := testSink()
|
||||||
s := store.NewSessionStore(store.SessionStoreConfig{
|
s := store.NewSessionManager(store.SessionManagerConfig{
|
||||||
Log: sink.NewLogger("test"),
|
Log: sink.NewLogger("test"),
|
||||||
Sink: sink,
|
Sink: sink,
|
||||||
ReadFile: func(string) ([]byte, error) { return []byte("#!/bin/sh\n"), nil },
|
ReadFile: func(string) ([]byte, error) { return []byte("#!/bin/sh\n"), nil },
|
||||||
|
|
@ -1448,7 +1448,7 @@ func TestSessionStoreCreateSessionTildeExpansion(t *testing.T) {
|
||||||
return &stubCore{state: "idle", backend: "stub", model: "m", agentName: agentName, cwd: cwd}, nil
|
return &stubCore{state: "idle", backend: "stub", model: "m", agentName: agentName, cwd: cwd}, nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
e, _ := s.Open("new")
|
e, _ := s.Store().Open("new")
|
||||||
if err := e.Write([]byte("name=tildetest cwd=~/")); err != nil {
|
if err := e.Write([]byte("name=tildetest cwd=~/")); err != nil {
|
||||||
t.Fatalf("Write(new): %v", err)
|
t.Fatalf("Write(new): %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -1460,17 +1460,17 @@ func TestSessionStoreCreateSessionTildeExpansion(t *testing.T) {
|
||||||
|
|
||||||
// ===== openEntry not-found =====
|
// ===== openEntry not-found =====
|
||||||
|
|
||||||
func TestSessionStoreOpenEntryNotFound(t *testing.T) {
|
func TestSessionManagerOpenEntryNotFound(t *testing.T) {
|
||||||
s := newSessionStore(t)
|
s := newSessionManager(t)
|
||||||
if _, err := s.Open("__nonexistent__"); err == nil {
|
if _, err := s.Store().Open("__nonexistent__"); err == nil {
|
||||||
t.Error("Open(nonexistent) should error")
|
t.Error("Open(nonexistent) should error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== OpenStore not-found =====
|
// ===== OpenStore not-found =====
|
||||||
|
|
||||||
func TestSessionStoreOpenStoreNotFound(t *testing.T) {
|
func TestSessionManagerOpenStoreNotFound(t *testing.T) {
|
||||||
s := newSessionStore(t)
|
s := newSessionManager(t)
|
||||||
if _, err := s.OpenStore("__missing__"); err == nil {
|
if _, err := s.OpenStore("__missing__"); err == nil {
|
||||||
t.Error("OpenStore(missing) should error")
|
t.Error("OpenStore(missing) should error")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Reference in New Issue