使用 Golang exec 命令 运行 时,Firebase 模拟器不会停止

Firebase emulator does not stop when run with Golang exec command

我正在使用此 article 中的信息来使用 firebase 模拟器为 Firestore 构建测试。

模拟器正确启动,测试 运行,但是尽管 SIGKILL(我尝试了其他信号)模拟器在测试完成后仍未清理。

这是我的 main_test.go 的样子:

package main

import (
    "fmt"
    "io"
    "log"
    "os"
    "os/exec"
    "strings"
    "syscall"
    "testing"
    "time"
)

func TestMain(m *testing.M) {
    // command to start firestore emulator
    cmd := exec.Command("firebase", "emulators:start")

    // this makes it killable
    cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

    // we need to capture it's output to know when it's started
    stdout, err := cmd.StdoutPipe()
    if err != nil {
        log.Fatal(err)
    }
    defer stdout.Close()

    if err := cmd.Start(); err != nil {
        log.Fatal(err)
    }

    var result int
    defer func() {
        syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
        os.Exit(result)
    }()

    started := make(chan bool)
    go func() {
        buf := make([]byte, 512, 512)
        for {
            n, err := stdout.Read(buf[:])
            if err != nil {
                if err == io.EOF {
                    break
                }
                log.Fatalf("reading stdout %v", err)
            }

            if n > 0 {
                d := string(buf[:n])

                // only required if we want to see the emulator output
                fmt.Printf("%s", d)

                // checking for the message that it's started
                if strings.Contains(d, "All emulators ready") {
                    started <- true
                    break
                }
            }
        }
    }()

    done := make(chan error, 1)
    go func() {
        done <- cmd.Wait()
    }()

    select {
    case <-time.After(10 * time.Second):
        log.Fatal("Failed to start the command for 10 seconds")
    case err := <-done:
        log.Fatalf("------\nCommand has finished unexpectedly with error: %v", err)
    case <-started:
        fmt.Println("--------")
        log.Print("Command started successully... running tests")
    }

    log.Print("BEFORE running tests")
    result = m.Run()
    time.Sleep(time.Minute) // to simulate that it take some times to run tests
    log.Print("AFTER running tests")
}

go test . -v 的输出看起来不错:

i  emulators: Starting emulators: firestore
i  firestore: Firestore Emulator logging to firestore-debug.log
i  ui: Emulator UI logging to ui-debug.log

┌───────────────────────────────────────────────────────────────────────┐
│ ✔  All emulators ready! View status and logs at http://localhost:4000 │
└────────────────────────────────────────────────────────────--------
2020/10/13 16:20:29 Command started successully... running tests
2020/10/13 16:20:29 BEFORE running tests
testing: warning: no tests to run
PASS
2020/10/13 16:20:29 AFTER running tests
ok      go/src/test (cached) [no tests to run]

但是 firebase 模拟器没有正确清理(如果我再次启动相同的命令,它会失败并抱怨端口 4000 已被使用)。发现以下进程挥之不去:

### snippet from netstat -anp:
tcp        0      0 127.0.0.1:4000          0.0.0.0:*               LISTEN      25187/firebase
tcp6       0      0 127.0.0.1:8080          :::*                    LISTEN      25160/cloud-firesto

### snippet from ps axu:
user 25187 /usr/local/bin/firebase /home/.cache/firebase/emulators/ui-v1.1.1/server.bundle.js
...
user 25160 /home/.cache/firebase/emulators/cloud-firestore-emulator-v1.11.7.jar --launcher_javabase=/usr/local/buildtools/java/jdk11 -Duser.language=en run --host localhost --port 8080 --rules /home/firestore.rules

有什么问题吗? ...或关于如何测试我的主要功能(使用 Firestore 作为后端)的其他想法?

简短回答:Go exec 不会终止子进程。此处有更多详细信息:Why won't Go kill a child process correctly?

根据 here 中的建议,我决定使用:

firebase emulators:exec "go test"

测试完成后正确清除模拟器。