163 lines
3.9 KiB
Go
163 lines
3.9 KiB
Go
package harness
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Options 控制测试进程启动。
|
|
type Options struct {
|
|
// Listen 写入配置的 listen,默认 127.0.0.1:0。
|
|
Listen string
|
|
// AdminListen 可选;非空时写入 admin_listen,并读取 admin.addr。
|
|
AdminListen string
|
|
// AdminInit 可选;nil 时用 CLIAdminInit。不支持时忽略并继续启动。
|
|
AdminInit AdminInitializer
|
|
// KeepDir 为 true 时 Stop 不删除数据目录(命令行启动器在清理前可读)。
|
|
KeepDir bool
|
|
}
|
|
|
|
// Server 是一次集成测试用的真实 nixmsg 进程。
|
|
type Server struct {
|
|
BinPath string
|
|
ConfigPath string
|
|
DataDir string
|
|
Addr string // 来自 listen.addr,形如 127.0.0.1:12345
|
|
AdminAddr string // 来自 admin.addr(若有)
|
|
AdminPassword string
|
|
HTTPBase string
|
|
AdminHTTPBase string
|
|
|
|
cmd *exec.Cmd
|
|
keepDir bool
|
|
stopped bool
|
|
}
|
|
|
|
// Start 编译(如需)并启动服务:临时目录、listen 端口 0、读 listen.addr。
|
|
func Start(opts Options) (*Server, error) {
|
|
bin, err := Binary()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataDir, err := os.MkdirTemp("", "nixmsg-harness-*")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
listen := opts.Listen
|
|
if listen == "" {
|
|
listen = "127.0.0.1:0"
|
|
}
|
|
cfgPath := filepath.Join(dataDir, "config.yaml")
|
|
cfg := fmt.Sprintf("listen: %q\ndata_dir: %q\n", listen, filepath.ToSlash(dataDir))
|
|
if opts.AdminListen != "" {
|
|
cfg += fmt.Sprintf("admin_listen: %q\n", opts.AdminListen)
|
|
}
|
|
if err = os.WriteFile(cfgPath, []byte(cfg), 0o644); err != nil {
|
|
_ = os.RemoveAll(dataDir)
|
|
return nil, err
|
|
}
|
|
|
|
adminInit := opts.AdminInit
|
|
if adminInit == nil {
|
|
adminInit = CLIAdminInit{}
|
|
}
|
|
password, initErr := adminInit.Init(bin, cfgPath)
|
|
if initErr != nil && !errors.Is(initErr, ErrAdminInitUnsupported) {
|
|
_ = os.RemoveAll(dataDir)
|
|
return nil, initErr
|
|
}
|
|
if errors.Is(initErr, ErrAdminInitUnsupported) {
|
|
password = ""
|
|
}
|
|
|
|
cmd := exec.Command(bin, "serve")
|
|
cmd.Env = append(os.Environ(), "NIXMSG_CONFIG="+cfgPath)
|
|
cmd.Stdout = os.Stderr
|
|
cmd.Stderr = os.Stderr
|
|
if err = cmd.Start(); err != nil {
|
|
_ = os.RemoveAll(dataDir)
|
|
return nil, fmt.Errorf("start serve: %w", err)
|
|
}
|
|
|
|
s := &Server{
|
|
BinPath: bin,
|
|
ConfigPath: cfgPath,
|
|
DataDir: dataDir,
|
|
AdminPassword: password,
|
|
cmd: cmd,
|
|
keepDir: opts.KeepDir,
|
|
}
|
|
|
|
addr, err := waitAddrFile(filepath.Join(dataDir, "listen.addr"), 10*time.Second)
|
|
if err != nil {
|
|
_ = s.Stop()
|
|
return nil, fmt.Errorf("wait listen.addr: %w", err)
|
|
}
|
|
s.Addr = addr
|
|
s.HTTPBase = "http://" + addr
|
|
|
|
if opts.AdminListen != "" {
|
|
adminAddr, adminErr := waitAddrFile(filepath.Join(dataDir, "admin.addr"), 10*time.Second)
|
|
if adminErr == nil {
|
|
s.AdminAddr = adminAddr
|
|
s.AdminHTTPBase = "http://" + adminAddr
|
|
}
|
|
}
|
|
if s.AdminHTTPBase == "" {
|
|
s.AdminHTTPBase = s.HTTPBase
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
// AdminClient 返回指向管理接口的 HTTP 客户端。
|
|
func (s *Server) AdminClient() (*AdminClient, error) {
|
|
base := s.AdminHTTPBase
|
|
if base == "" {
|
|
base = s.HTTPBase
|
|
}
|
|
return NewAdminClient(base)
|
|
}
|
|
|
|
// Stop 结束进程并删除临时目录(除非 KeepDir)。
|
|
func (s *Server) Stop() error {
|
|
if s == nil || s.stopped {
|
|
return nil
|
|
}
|
|
s.stopped = true
|
|
var stopErr error
|
|
if s.cmd != nil && s.cmd.Process != nil {
|
|
_ = s.cmd.Process.Kill()
|
|
_, _ = s.cmd.Process.Wait()
|
|
}
|
|
if !s.keepDir && s.DataDir != "" {
|
|
if err := os.RemoveAll(s.DataDir); err != nil {
|
|
stopErr = err
|
|
}
|
|
}
|
|
return stopErr
|
|
}
|
|
|
|
func waitAddrFile(path string, timeout time.Duration) (string, error) {
|
|
deadline := time.Now().Add(timeout)
|
|
var lastErr error
|
|
for time.Now().Before(deadline) {
|
|
b, err := os.ReadFile(path)
|
|
if err == nil {
|
|
addr := strings.TrimSpace(string(b))
|
|
if addr != "" {
|
|
return addr, nil
|
|
}
|
|
lastErr = fmt.Errorf("empty addr file")
|
|
} else {
|
|
lastErr = err
|
|
}
|
|
time.Sleep(20 * time.Millisecond)
|
|
}
|
|
return "", lastErr
|
|
}
|