vibetunnel/linux/pkg/session/stdin_watcher_test.go
Peter Steinberger 20395d6e09 fix: Fix CI test failures
- Fix hanging TestNewStdinWatcher by not calling Stop() without Start()
- Fix TestSession_Signal and TestSession_KillWithSignal by adding PID values
- Fix isProcessRunning to use syscall.Signal(0) instead of os.Signal(nil)
- Update websocket test to expect new 'Unknown WebSocket endpoint' error message
- Add timeout handling to websocket integration test
2025-06-20 16:21:27 +02:00

274 lines
5.7 KiB
Go

package session
import (
"io"
"os"
"path/filepath"
"testing"
"time"
)
func TestNewStdinWatcher(t *testing.T) {
// Create temporary directory for testing
tmpDir := t.TempDir()
// Create a named pipe
pipePath := filepath.Join(tmpDir, "stdin")
if err := os.MkdirAll(filepath.Dir(pipePath), 0755); err != nil {
t.Fatal(err)
}
// Create the pipe file (will be a regular file in tests)
if err := os.WriteFile(pipePath, []byte{}, 0644); err != nil {
t.Fatal(err)
}
// Create a mock PTY file
ptyFile, err := os.CreateTemp(tmpDir, "pty")
if err != nil {
t.Fatal(err)
}
defer ptyFile.Close()
// Test creating stdin watcher
watcher, err := NewStdinWatcher(pipePath, ptyFile)
if err != nil {
t.Fatalf("NewStdinWatcher() error = %v", err)
}
defer func() {
// Only stop if it was started
if watcher.watcher != nil {
watcher.watcher.Close()
}
if watcher.stdinFile != nil {
watcher.stdinFile.Close()
}
}()
if watcher.stdinPath != pipePath {
t.Errorf("stdinPath = %v, want %v", watcher.stdinPath, pipePath)
}
if watcher.ptyFile != ptyFile {
t.Errorf("ptyFile = %v, want %v", watcher.ptyFile, ptyFile)
}
if watcher.watcher == nil {
t.Error("watcher should not be nil")
}
if watcher.stdinFile == nil {
t.Error("stdinFile should not be nil")
}
}
func TestStdinWatcher_StartStop(t *testing.T) {
// Create temporary directory for testing
tmpDir := t.TempDir()
// Create a named pipe
pipePath := filepath.Join(tmpDir, "stdin")
if err := os.WriteFile(pipePath, []byte{}, 0644); err != nil {
t.Fatal(err)
}
// Create a mock PTY file
ptyFile, err := os.CreateTemp(tmpDir, "pty")
if err != nil {
t.Fatal(err)
}
defer ptyFile.Close()
watcher, err := NewStdinWatcher(pipePath, ptyFile)
if err != nil {
t.Fatal(err)
}
// Start the watcher
watcher.Start()
// Give it a moment to start
time.Sleep(10 * time.Millisecond)
// Stop the watcher
done := make(chan bool)
go func() {
watcher.Stop()
done <- true
}()
// Should stop quickly
select {
case <-done:
// Success
case <-time.After(1 * time.Second):
t.Error("Stop() took too long")
}
}
func TestStdinWatcher_HandleStdinData(t *testing.T) {
// Create temporary directory for testing
tmpDir := t.TempDir()
// Create a named pipe path
pipePath := filepath.Join(tmpDir, "stdin")
// Create PTY pipe for reading what's written
ptyReader, ptyWriter, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
defer ptyReader.Close()
defer ptyWriter.Close()
// Create stdin file
stdinFile, err := os.Create(pipePath)
if err != nil {
t.Fatal(err)
}
defer stdinFile.Close()
// Create watcher
watcher := &StdinWatcher{
stdinPath: pipePath,
ptyFile: ptyWriter,
stdinFile: stdinFile,
buffer: make([]byte, 4096),
stopChan: make(chan struct{}),
stoppedChan: make(chan struct{}),
}
// Write test data to stdin
testData := []byte("Hello, World!")
if _, err := stdinFile.Write(testData); err != nil {
t.Fatal(err)
}
if _, err := stdinFile.Seek(0, 0); err != nil {
t.Fatal(err)
}
// Handle the data
watcher.handleStdinData()
// Read from PTY to verify data was forwarded
result := make([]byte, len(testData))
if _, err := io.ReadFull(ptyReader, result); err != nil {
t.Fatalf("Failed to read forwarded data: %v", err)
}
if string(result) != string(testData) {
t.Errorf("Forwarded data = %q, want %q", result, testData)
}
}
func TestIsEAGAIN(t *testing.T) {
tests := []struct {
name string
err error
expected bool
}{
{
name: "nil error",
err: nil,
expected: false,
},
{
name: "EAGAIN error",
err: &os.PathError{Err: os.NewSyscallError("read", os.ErrDeadlineExceeded)},
expected: false, // Our simple implementation checks string
},
{
name: "other error",
err: io.EOF,
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isEAGAIN(tt.err)
if result != tt.expected {
t.Errorf("isEAGAIN() = %v, want %v", result, tt.expected)
}
})
}
}
func TestStdinWatcher_Cleanup(t *testing.T) {
// Create temporary directory for testing
tmpDir := t.TempDir()
// Create a named pipe
pipePath := filepath.Join(tmpDir, "stdin")
if err := os.WriteFile(pipePath, []byte{}, 0644); err != nil {
t.Fatal(err)
}
// Create a mock PTY file
ptyFile, err := os.CreateTemp(tmpDir, "pty")
if err != nil {
t.Fatal(err)
}
defer ptyFile.Close()
watcher, err := NewStdinWatcher(pipePath, ptyFile)
if err != nil {
t.Fatal(err)
}
// Store references to check if closed
stdinFile := watcher.stdinFile
fsWatcher := watcher.watcher
// Clean up
watcher.cleanup()
// Verify files are closed
if err := stdinFile.Close(); err == nil {
t.Error("stdinFile should have been closed")
}
// Verify watcher is closed by trying to add a path
if err := fsWatcher.Add("/tmp"); err == nil {
t.Error("fsnotify watcher should have been closed")
}
}
func BenchmarkStdinWatcher_HandleData(b *testing.B) {
// Create temporary directory for testing
tmpDir := b.TempDir()
// Create pipes
_, ptyWriter, err := os.Pipe()
if err != nil {
b.Fatal(err)
}
defer ptyWriter.Close()
// Create stdin file with data
stdinPath := filepath.Join(tmpDir, "stdin")
testData := []byte("This is test data for benchmarking stdin handling\n")
b.ResetTimer()
for i := 0; i < b.N; i++ {
// Create fresh stdin file each time
stdinFile, err := os.Create(stdinPath)
if err != nil {
b.Fatal(err)
}
if _, err := stdinFile.Write(testData); err != nil {
b.Fatal(err)
}
if _, err := stdinFile.Seek(0, 0); err != nil {
b.Fatal(err)
}
watcher := &StdinWatcher{
stdinPath: stdinPath,
ptyFile: ptyWriter,
stdinFile: stdinFile,
buffer: make([]byte, 4096),
}
watcher.handleStdinData()
stdinFile.Close()
}
}