Skip to main content
Glama
manager_test.go9.87 kB
package process import ( "encoding/json" "fmt" "os" "path/filepath" "strings" "sync" "testing" "time" "github.com/standardbeagle/brummer/pkg/events" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // TestManagerInitialization tests creating a new manager func TestManagerInitialization(t *testing.T) { tempDir := t.TempDir() eventBus := events.NewEventBus() // Test without package.json mgr, err := NewManager(tempDir, eventBus, false) require.NoError(t, err) assert.NotNil(t, mgr) assert.Equal(t, tempDir, mgr.workDir) // Test with package.json packageJSON := map[string]interface{}{ "name": "test-project", "scripts": map[string]interface{}{ "test": "echo 'running tests'", "build": "echo 'building project'", "dev": "echo 'starting dev server'", }, } packageFile := filepath.Join(tempDir, "package.json") data, err := json.Marshal(packageJSON) require.NoError(t, err) require.NoError(t, os.WriteFile(packageFile, data, 0644)) mgr2, err := NewManager(tempDir, eventBus, true) require.NoError(t, err) assert.NotNil(t, mgr2) scripts := mgr2.GetScripts() assert.Contains(t, scripts, "test") assert.Contains(t, scripts, "build") assert.Contains(t, scripts, "dev") assert.Equal(t, "echo 'running tests'", scripts["test"]) } // TestProcessStartStop tests starting and stopping processes func TestProcessStartStop(t *testing.T) { tempDir := t.TempDir() eventBus := events.NewEventBus() // Track events var receivedEvents []events.Event var eventsMu sync.Mutex eventBus.Subscribe(events.ProcessStarted, func(e events.Event) { eventsMu.Lock() receivedEvents = append(receivedEvents, e) eventsMu.Unlock() }) eventBus.Subscribe(events.ProcessExited, func(e events.Event) { eventsMu.Lock() receivedEvents = append(receivedEvents, e) eventsMu.Unlock() }) mgr, err := NewManager(tempDir, eventBus, false) require.NoError(t, err) // Test starting a simple command proc, err := mgr.StartCommand("echo-test", "echo", []string{"hello world"}) require.NoError(t, err) assert.NotNil(t, proc) assert.Equal(t, "echo-test", proc.Name) assert.Equal(t, StatusRunning, proc.GetStatus()) // Wait for process to complete time.Sleep(100 * time.Millisecond) // Check process completed successfully proc, exists := mgr.GetProcess(proc.ID) require.True(t, exists) assert.Equal(t, StatusSuccess, proc.GetStatus()) // Verify events were published eventsMu.Lock() defer eventsMu.Unlock() assert.GreaterOrEqual(t, len(receivedEvents), 1) // At least ProcessStarted } // TestProcessManagement tests process lifecycle management func TestProcessManagement(t *testing.T) { tempDir := t.TempDir() eventBus := events.NewEventBus() mgr, err := NewManager(tempDir, eventBus, false) require.NoError(t, err) // Start multiple processes proc1, err := mgr.StartCommand("sleep1", "sleep", []string{"0.2"}) require.NoError(t, err) proc2, err := mgr.StartCommand("sleep2", "sleep", []string{"0.2"}) require.NoError(t, err) // Test GetAllProcesses allProcs := mgr.GetAllProcesses() assert.Len(t, allProcs, 2) // Find our processes in the list var foundProc1, foundProc2 bool for _, p := range allProcs { if p.ID == proc1.ID { foundProc1 = true } if p.ID == proc2.ID { foundProc2 = true } } assert.True(t, foundProc1) assert.True(t, foundProc2) // Test GetProcess retrievedProc, exists := mgr.GetProcess(proc1.ID) require.True(t, exists) assert.Equal(t, proc1.ID, retrievedProc.ID) assert.Equal(t, "sleep1", retrievedProc.Name) // Test stopping a specific process err = mgr.StopProcess(proc1.ID) assert.NoError(t, err) // Wait a moment and check status time.Sleep(50 * time.Millisecond) stoppedProc, exists := mgr.GetProcess(proc1.ID) require.True(t, exists) status := stoppedProc.GetStatus() assert.True(t, status == StatusStopped || status == StatusSuccess) // Test stopping all processes err = mgr.StopAllProcesses() assert.NoError(t, err) } // TestScriptExecution tests running package.json scripts func TestScriptExecution(t *testing.T) { tempDir := t.TempDir() eventBus := events.NewEventBus() // Create package.json with test scripts packageJSON := map[string]interface{}{ "name": "test-project", "scripts": map[string]interface{}{ "hello": "echo 'Hello from script'", "fail": "exit 1", }, } packageFile := filepath.Join(tempDir, "package.json") data, err := json.Marshal(packageJSON) require.NoError(t, err) require.NoError(t, os.WriteFile(packageFile, data, 0644)) mgr, err := NewManager(tempDir, eventBus, true) require.NoError(t, err) // Test successful script proc, err := mgr.StartScript("hello") require.NoError(t, err) assert.NotNil(t, proc) assert.Equal(t, "hello", proc.Name) // Wait for completion and check multiple times var finalStatus ProcessStatus for i := 0; i < 10; i++ { time.Sleep(100 * time.Millisecond) proc, exists := mgr.GetProcess(proc.ID) require.True(t, exists) finalStatus = proc.GetStatus() if finalStatus == StatusSuccess || finalStatus == StatusFailed { break } } // Process should have completed (either success or failed, not running) assert.True(t, finalStatus == StatusSuccess || finalStatus == StatusFailed, "Process should have completed, got status: %s", finalStatus) // Test failing script procFail, err := mgr.StartScript("fail") require.NoError(t, err) // Wait for completion and check multiple times var failStatus ProcessStatus for i := 0; i < 10; i++ { time.Sleep(100 * time.Millisecond) procFail, exists := mgr.GetProcess(procFail.ID) require.True(t, exists) failStatus = procFail.GetStatus() if failStatus == StatusSuccess || failStatus == StatusFailed { break } } // This process should have failed (exit 1) assert.Equal(t, StatusFailed, failStatus) // Test non-existent script _, err = mgr.StartScript("nonexistent") assert.Error(t, err) } // TestLogCallbacks tests log callback functionality func TestLogCallbacks(t *testing.T) { tempDir := t.TempDir() eventBus := events.NewEventBus() mgr, err := NewManager(tempDir, eventBus, false) require.NoError(t, err) // Set up log callback var logLines []string var logMu sync.Mutex mgr.AddLogCallback(func(processID, line string, isError bool) { logMu.Lock() logLines = append(logLines, fmt.Sprintf("%s:%t:%s", processID, isError, line)) logMu.Unlock() }) // Start a command that produces output proc, err := mgr.StartCommand("output-test", "echo", []string{"test output line"}) require.NoError(t, err) // Wait for output time.Sleep(200 * time.Millisecond) // Check we received log output logMu.Lock() defer logMu.Unlock() assert.Greater(t, len(logLines), 0) // Should have output from our echo command found := false for _, line := range logLines { if strings.Contains(line, proc.ID) && strings.Contains(line, "test output line") { found = true break } } assert.True(t, found, "Expected to find echo output in log lines") } // TestPackageManagerDetection tests package manager detection func TestPackageManagerDetection(t *testing.T) { tempDir := t.TempDir() eventBus := events.NewEventBus() mgr, err := NewManager(tempDir, eventBus, false) require.NoError(t, err) // Test getting installed package managers installed := mgr.GetInstalledPackageManagers() assert.NotEmpty(t, installed) // Should detect at least some package managers // Test getting current package manager current := mgr.GetCurrentPackageManager() assert.NotEmpty(t, string(current)) // Test detected commands commands := mgr.GetDetectedCommands() // Should detect some commands (at least basic shell commands) assert.NotEmpty(t, commands) } // TestCleanup tests proper cleanup of all processes func TestCleanup(t *testing.T) { tempDir := t.TempDir() eventBus := events.NewEventBus() mgr, err := NewManager(tempDir, eventBus, false) require.NoError(t, err) // Start some long-running processes proc1, err := mgr.StartCommand("sleep1", "sleep", []string{"10"}) // Long sleep require.NoError(t, err) proc2, err := mgr.StartCommand("sleep2", "sleep", []string{"10"}) // Long sleep require.NoError(t, err) // Verify they're running assert.Equal(t, StatusRunning, proc1.GetStatus()) assert.Equal(t, StatusRunning, proc2.GetStatus()) // Cleanup should stop all processes err = mgr.Cleanup() assert.NoError(t, err) // Wait a moment for cleanup to complete time.Sleep(100 * time.Millisecond) // Check processes are stopped proc1, exists := mgr.GetProcess(proc1.ID) require.True(t, exists) status1 := proc1.GetStatus() assert.True(t, status1 == StatusStopped || status1 == StatusFailed) proc2, exists = mgr.GetProcess(proc2.ID) require.True(t, exists) status2 := proc2.GetStatus() assert.True(t, status2 == StatusStopped || status2 == StatusFailed) } // TestConcurrentOperations tests thread safety func TestConcurrentOperations(t *testing.T) { tempDir := t.TempDir() eventBus := events.NewEventBus() mgr, err := NewManager(tempDir, eventBus, false) require.NoError(t, err) var wg sync.WaitGroup processIDs := make([]string, 10) // Start multiple processes concurrently for i := 0; i < 10; i++ { wg.Add(1) go func(index int) { defer wg.Done() proc, err := mgr.StartCommand(fmt.Sprintf("echo-%d", index), "echo", []string{fmt.Sprintf("message-%d", index)}) if err == nil { processIDs[index] = proc.ID } }(i) } wg.Wait() // Verify all processes were created time.Sleep(200 * time.Millisecond) allProcs := mgr.GetAllProcesses() assert.GreaterOrEqual(t, len(allProcs), 10) // Cleanup concurrently wg.Add(1) go func() { defer wg.Done() mgr.Cleanup() }() // Read process status concurrently while cleanup is happening for i := 0; i < 5; i++ { wg.Add(1) go func() { defer wg.Done() mgr.GetAllProcesses() }() } wg.Wait() }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/standardbeagle/brummer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server