You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

230 lines
5.2 KiB

package testutil
import (
"fmt"
"net"
"net/http"
"os"
"os/exec"
"path/filepath"
"time"
)
// TestServer represents a running weed mini server for testing
type TestServer struct {
cmd *exec.Cmd
config ServerConfig
done chan error
}
// ServerConfig holds configuration for starting a weed mini server
type ServerConfig struct {
DataDir string
S3Port int
S3Config string
AccessKey string
SecretKey string
LogFile string
PIDFile string
StartupWait time.Duration
WeedBinary string
}
// DefaultServerConfig creates a default server configuration
func DefaultServerConfig(dataDir *string) ServerConfig {
if dataDir == nil {
dataDir = &[]string{filepath.Join(os.TempDir(), "weed-test-data")}[0]
}
return ServerConfig{
DataDir: *dataDir,
S3Port: 8333,
AccessKey: "test",
SecretKey: "test",
LogFile: "weed-test.log",
PIDFile: "weed-test.pid",
StartupWait: 30 * time.Second,
}
}
// StartServer starts a weed mini server with the given configuration
func StartServer(config ServerConfig) (*TestServer, error) {
// Find weed binary
weedBinary, err := findWeedBinary()
if err != nil {
return nil, fmt.Errorf("failed to find weed binary: %w", err)
}
config.WeedBinary = weedBinary
// Create data directory
os.MkdirAll(config.DataDir, 0755)
// Build command arguments
args := []string{
"server",
"-debug",
"-s3",
fmt.Sprintf("-s3.port=%d", config.S3Port),
"-s3.allowDeleteBucketNotEmpty=true",
"-filer",
"-filer.maxMB=64",
"-master.volumeSizeLimitMB=50",
"-master.peers=none",
"-volume.max=100",
fmt.Sprintf("-dir=%s", config.DataDir),
"-volume.preStopSeconds=1",
}
// choose free ports for master, volume, filer and metrics to avoid collisions
pick := func() int {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return 0
}
p := l.Addr().(*net.TCPAddr).Port
l.Close()
return p
}
masterPort := pick()
volumePort := pick()
filerPort := pick()
metricsPort := pick()
if masterPort != 0 {
args = append(args, fmt.Sprintf("-master.port=%d", masterPort))
}
if volumePort != 0 {
args = append(args, fmt.Sprintf("-volume.port=%d", volumePort))
}
if filerPort != 0 {
args = append(args, fmt.Sprintf("-filer.port=%d", filerPort))
}
if metricsPort != 0 {
args = append(args, fmt.Sprintf("-metricsPort=%d", metricsPort))
}
if config.S3Config != "" {
args = append(args, fmt.Sprintf("-s3.config=%s", config.S3Config))
}
// Start server process
cmd := exec.Command(weedBinary, args...)
logFile, err := os.Create(config.LogFile)
if err != nil {
return nil, fmt.Errorf("failed to create log file: %w", err)
}
cmd.Stdout = logFile
cmd.Stderr = logFile
if err := cmd.Start(); err != nil {
logFile.Close()
return nil, fmt.Errorf("failed to start server: %w", err)
}
// Save PID
os.WriteFile(config.PIDFile, []byte(fmt.Sprintf("%d", cmd.Process.Pid)), 0644)
server := &TestServer{
cmd: cmd,
config: config,
done: make(chan error, 1),
}
// Wait for server to be ready
if err := server.WaitForReady(); err != nil {
server.Stop()
return nil, fmt.Errorf("server failed to start: %w", err)
}
return server, nil
}
// WaitForReady polls the server health endpoint until it responds
func (s *TestServer) WaitForReady() error {
deadline := time.Now().Add(s.config.StartupWait)
for {
if time.Now().After(deadline) {
return fmt.Errorf("server startup timeout after %v", s.config.StartupWait)
}
resp, err := http.Get(fmt.Sprintf("http://localhost:%d", s.config.S3Port))
if err == nil {
resp.Body.Close()
return nil
}
time.Sleep(100 * time.Millisecond)
}
}
// Stop stops the server and cleans up resources
func (s *TestServer) Stop() error {
if s.cmd.Process == nil {
return nil
}
// Try graceful shutdown
s.cmd.Process.Signal(os.Interrupt)
done := make(chan error, 1)
go func() {
done <- s.cmd.Wait()
}()
select {
case <-time.After(5 * time.Second):
// Force kill if graceful shutdown takes too long
s.cmd.Process.Kill()
<-done
case <-done:
}
// Clean up files
os.Remove(s.config.PIDFile)
os.RemoveAll(s.config.DataDir)
return nil
}
// findWeedBinary searches for the weed binary in common locations
func findWeedBinary() (string, error) {
// Try various locations for weed binary
paths := []string{
"../../../weed",
"../../../../weed",
"../weed",
"./weed",
"/usr/local/bin/weed",
"/usr/bin/weed",
}
// Add GOPATH/bin/weed
if gopath := os.Getenv("GOPATH"); gopath != "" {
paths = append(paths, filepath.Join(gopath, "bin", "weed"))
}
// Add GOROOT/bin/weed
if goroot := os.Getenv("GOROOT"); goroot != "" {
paths = append(paths, filepath.Join(goroot, "bin", "weed"))
}
// Add ~/go/bin/weed (common default GOPATH)
if home := os.Getenv("HOME"); home != "" {
paths = append(paths, filepath.Join(home, "go", "bin", "weed"))
}
for _, p := range paths {
if info, err := os.Stat(p); err == nil {
// Check if it's a regular file and executable
if info.Mode().IsRegular() && (info.Mode().Perm()&0111) != 0 {
return p, nil
}
}
}
// Try PATH
if path, err := exec.LookPath("weed"); err == nil {
return path, nil
}
return "", fmt.Errorf("weed binary not found in PATH or common locations")
}