package testutil import ( "context" "fmt" "net" "net/http" "os" "os/exec" "path/filepath" "time" ) const SeaweedMiniStartupTimeout = 45 * time.Second func HasDocker() bool { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() cmd := exec.CommandContext(ctx, "docker", "version") return cmd.Run() == nil } func FindBindIP() string { addrs, err := net.InterfaceAddrs() if err != nil { return "127.0.0.1" } for _, addr := range addrs { ipNet, ok := addr.(*net.IPNet) if !ok || ipNet.IP == nil { continue } ip := ipNet.IP.To4() if ip == nil || ip.IsLoopback() || ip.IsLinkLocalUnicast() { continue } return ip.String() } return "127.0.0.1" } func WriteIAMConfig(dir, accessKey, secretKey string) (string, error) { iamConfigPath := filepath.Join(dir, "iam_config.json") iamConfig := fmt.Sprintf(`{ "identities": [ { "name": "admin", "credentials": [ { "accessKey": "%s", "secretKey": "%s" } ], "actions": [ "Admin", "Read", "List", "Tagging", "Write" ] } ] }`, accessKey, secretKey) if err := os.WriteFile(iamConfigPath, []byte(iamConfig), 0644); err != nil { return "", err } return iamConfigPath, nil } func WaitForService(url string, timeout time.Duration) bool { client := &http.Client{Timeout: 2 * time.Second} ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() ticker := time.NewTicker(500 * time.Millisecond) defer ticker.Stop() for { select { case <-ctx.Done(): return false case <-ticker.C: resp, err := client.Get(url) if err == nil { resp.Body.Close() return true } } } } func WaitForPort(port int, timeout time.Duration) bool { deadline := time.Now().Add(timeout) address := fmt.Sprintf("127.0.0.1:%d", port) for time.Now().Before(deadline) { conn, err := net.DialTimeout("tcp", address, 500*time.Millisecond) if err == nil { _ = conn.Close() return true } time.Sleep(100 * time.Millisecond) } return false }