package accept import ( "bytes" "fmt" "os" "os/exec" "path/filepath" "strings" "time" "git.asio.asia/nixevol/NixMsg/test/harness" ) // LoggedServer 在捕获 stdout/stderr 的情况下启动 serve(用于断言密码不进日志)。 type LoggedServer struct { *harness.Server LogBuf *bytes.Buffer cmd *exec.Cmd } // StartWithLogCapture 执行 admin init 后启动 serve,并把进程日志写入 LogBuf。 func StartWithLogCapture() (*LoggedServer, error) { bin, err := harness.Binary() if err != nil { return nil, err } dataDir, err := os.MkdirTemp("", "nixmsg-accept-*") if err != nil { return nil, err } cfgPath := filepath.Join(dataDir, "config.yaml") cfg := fmt.Sprintf("listen: %q\ndata_dir: %q\n", "127.0.0.1:0", filepath.ToSlash(dataDir)) if err = os.WriteFile(cfgPath, []byte(cfg), 0o644); err != nil { _ = os.RemoveAll(dataDir) return nil, err } initCmd := exec.Command(bin, "admin", "init") initCmd.Env = append(os.Environ(), "NIXMSG_CONFIG="+cfgPath) initOut, initErr := initCmd.CombinedOutput() if initErr != nil { _ = os.RemoveAll(dataDir) return nil, fmt.Errorf("admin init: %w\n%s", initErr, initOut) } password := parsePassword(string(initOut)) if password == "" { _ = os.RemoveAll(dataDir) return nil, fmt.Errorf("admin init password not found:\n%s", initOut) } logBuf := &bytes.Buffer{} cmd := exec.Command(bin, "serve") cmd.Env = append(os.Environ(), "NIXMSG_CONFIG="+cfgPath) cmd.Stdout = logBuf cmd.Stderr = logBuf if err = cmd.Start(); err != nil { _ = os.RemoveAll(dataDir) return nil, fmt.Errorf("start serve: %w", err) } s := &harness.Server{ BinPath: bin, ConfigPath: cfgPath, DataDir: dataDir, AdminPassword: password, } addr, err := waitListenAddr(filepath.Join(dataDir, "listen.addr"), 15*time.Second) if err != nil { _ = cmd.Process.Kill() _, _ = cmd.Process.Wait() _ = os.RemoveAll(dataDir) return nil, fmt.Errorf("wait listen.addr: %w\nlogs:\n%s", err, logBuf.String()) } s.Addr = addr s.HTTPBase = "http://" + addr s.AdminHTTPBase = s.HTTPBase return &LoggedServer{Server: s, LogBuf: logBuf, cmd: cmd}, nil } // Stop 结束进程并删除临时目录。 func (s *LoggedServer) Stop() error { if s == nil { return nil } if s.cmd != nil && s.cmd.Process != nil { _ = s.cmd.Process.Kill() _, _ = s.cmd.Process.Wait() } if s.DataDir != "" { return os.RemoveAll(s.DataDir) } return nil } func parsePassword(out string) string { for _, line := range strings.Split(out, "\n") { line = strings.TrimSpace(line) lower := strings.ToLower(line) if strings.HasPrefix(lower, "admin password:") { return strings.TrimSpace(line[len("admin password:"):]) } if strings.HasPrefix(lower, "password:") { return strings.TrimSpace(line[len("password:"):]) } } return "" } func waitListenAddr(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 }