Skip to main content
Glama
helpers.go7.19 kB
package testutil import ( "context" "fmt" "io" "net" "net/http" "os" "os/exec" "path/filepath" "strings" "testing" "time" ) // BrummerTest provides a test harness for running brummer type BrummerTest struct { t *testing.T binaryPath string workDir string Cmd *exec.Cmd // Exported for tests to check process state ctx context.Context cancel context.CancelFunc output strings.Builder } // NewBrummerTest creates a new test harness func NewBrummerTest(t *testing.T) *BrummerTest { t.Helper() // Find brummer binary binaryPath := os.Getenv("BRUMMER_BINARY") projectRoot := "" if binaryPath == "" { // Try to find it in project root var err error projectRoot, err = filepath.Abs(filepath.Join(filepath.Dir(os.Args[0]), "../..")) if err != nil { t.Fatal(err) } binaryPath = filepath.Join(projectRoot, "brum") if _, err := os.Stat(binaryPath); os.IsNotExist(err) { t.Fatalf("brummer binary not found at %s. Set BRUMMER_BINARY env var or build with 'make build'", binaryPath) } } else { // Extract project root from binary path projectRoot = filepath.Dir(binaryPath) } // Create temp work directory workDir := t.TempDir() // Find fixtures directory // Try multiple paths to handle different execution contexts possiblePaths := []string{ filepath.Join(projectRoot, "test", "fixtures"), filepath.Join(filepath.Dir(t.Name()), "fixtures"), "fixtures", "./fixtures", "../fixtures", } var fixturesDir string for _, path := range possiblePaths { if info, err := os.Stat(path); err == nil && info.IsDir() { fixturesDir = path break } } if fixturesDir == "" { t.Fatalf("fixtures directory not found, tried: %v", possiblePaths) } // Copy fixtures if err := copyFixtures(fixturesDir, workDir); err != nil { t.Fatalf("failed to copy fixtures from %s: %v", fixturesDir, err) } ctx, cancel := context.WithCancel(context.Background()) return &BrummerTest{ t: t, binaryPath: binaryPath, workDir: workDir, ctx: ctx, cancel: cancel, } } // Start runs brummer with the given arguments func (bt *BrummerTest) Start(args ...string) error { bt.t.Helper() // Build command fullArgs := append([]string{"-d", bt.workDir}, args...) bt.Cmd = exec.CommandContext(bt.ctx, bt.binaryPath, fullArgs...) // Capture output bt.Cmd.Stdout = &bt.output bt.Cmd.Stderr = &bt.output // Start command if err := bt.Cmd.Start(); err != nil { return fmt.Errorf("failed to start brummer: %w", err) } // Give it time to start up time.Sleep(500 * time.Millisecond) return nil } // Stop gracefully stops brummer func (bt *BrummerTest) Stop() { bt.t.Helper() if bt.Cmd != nil && bt.Cmd.Process != nil { // Try graceful shutdown first bt.Cmd.Process.Signal(os.Interrupt) // Wait for graceful shutdown done := make(chan error, 1) go func() { done <- bt.Cmd.Wait() }() select { case <-done: // Graceful shutdown successful case <-time.After(2 * time.Second): // Force kill bt.cancel() bt.Cmd.Process.Kill() <-done } } } // Output returns the captured output func (bt *BrummerTest) Output() string { return bt.output.String() } // WaitForOutput waits for specific output to appear func (bt *BrummerTest) WaitForOutput(substr string, timeout time.Duration) error { deadline := time.Now().Add(timeout) for time.Now().Before(deadline) { if strings.Contains(bt.output.String(), substr) { return nil } time.Sleep(100 * time.Millisecond) } return fmt.Errorf("timeout waiting for output containing %q\nGot: %s", substr, bt.output.String()) } // WaitForMCP waits for MCP server to be ready and returns the port func (bt *BrummerTest) WaitForMCP(timeout time.Duration) (int, error) { err := bt.WaitForOutput("MCP server started on http://localhost:", timeout) if err != nil { return 0, err } // Extract port from output output := bt.output.String() start := strings.Index(output, "MCP server started on http://localhost:") if start == -1 { return 0, fmt.Errorf("MCP server URL not found in output") } start += len("MCP server started on http://localhost:") end := strings.IndexAny(output[start:], "/ \n") if end == -1 { end = len(output) - start } var port int _, err = fmt.Sscanf(output[start:start+end], "%d", &port) if err != nil { return 0, fmt.Errorf("failed to parse MCP port: %w", err) } return port, nil } // WaitForProxy waits for proxy server to be ready and returns the port func (bt *BrummerTest) WaitForProxy(timeout time.Duration) (int, error) { err := bt.WaitForOutput("Started HTTP proxy server on port", timeout) if err != nil { return 0, err } // Extract port from output output := bt.output.String() start := strings.Index(output, "Started HTTP proxy server on port ") if start == -1 { return 0, fmt.Errorf("proxy server port not found in output") } start += len("Started HTTP proxy server on port ") end := strings.IndexAny(output[start:], " \n") if end == -1 { end = len(output) - start } var port int _, err = fmt.Sscanf(output[start:start+end], "%d", &port) if err != nil { return 0, fmt.Errorf("failed to parse proxy port: %w", err) } return port, nil } // Cleanup should be called with defer to ensure proper cleanup func (bt *BrummerTest) Cleanup() { bt.Stop() } // copyFixtures copies test fixtures to the work directory func copyFixtures(src, dst string) error { return filepath.Walk(src, func(path string, info os.FileInfo, err error) error { if err != nil { return err } relPath, err := filepath.Rel(src, path) if err != nil { return err } dstPath := filepath.Join(dst, relPath) if info.IsDir() { return os.MkdirAll(dstPath, info.Mode()) } srcFile, err := os.Open(path) if err != nil { return err } defer srcFile.Close() dstFile, err := os.Create(dstPath) if err != nil { return err } defer dstFile.Close() _, err = io.Copy(dstFile, srcFile) return err }) } // GetFreePort returns a free port func GetFreePort() (int, error) { listener, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { return 0, err } defer listener.Close() addr := listener.Addr().(*net.TCPAddr) return addr.Port, nil } // WaitForHTTP waits for an HTTP server to be ready func WaitForHTTP(url string, timeout time.Duration) error { client := &http.Client{Timeout: 1 * time.Second} deadline := time.Now().Add(timeout) for time.Now().Before(deadline) { resp, err := client.Get(url) if err == nil { resp.Body.Close() return nil } time.Sleep(100 * time.Millisecond) } return fmt.Errorf("timeout waiting for HTTP server at %s", url) } // AssertContains checks if a string contains a substring func AssertContains(t *testing.T, haystack, needle string) { t.Helper() if !strings.Contains(haystack, needle) { t.Errorf("expected output to contain %q\nGot: %s", needle, haystack) } } // AssertNotContains checks if a string does not contain a substring func AssertNotContains(t *testing.T, haystack, needle string) { t.Helper() if strings.Contains(haystack, needle) { t.Errorf("expected output to NOT contain %q\nGot: %s", needle, haystack) } }

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