Firebase emulator does not stop when run with Golang exec command

800 Views Asked by At

I'm using the information in this article to build tests for Firestore using the firebase emulator.

The emulator starts correctly, tests run, but the despite the SIGKILL (and I tried other signals) the emulator is not cleaned up after the tests finish.

This is how my main_test.go looks like:

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")
}

The output of go test . -v is looking ok:

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]

But firebase emulator is not properly cleaned up (if I start the same command again, it fails complaining that port 4000 is already in use). The following processes are found lingering:

### 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

Any ideas what's wrong? ... or other ideas on how to test my main function (that uses Firestore as a backend)?

2

There are 2 best solutions below

0
On BEST ANSWER

Short answer: Go exec does not kill the child processes. More details here: Why won't Go kill a child process correctly?

Per the recommendations in here I decided to use:

firebase emulators:exec "go test"

which clears the emulator correctly after the tests finish.

0
On

I had the same problem, but changing from SIGKILL to SIGINT solved it for me. That is, I changed:

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

to

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

I was glad this worked, because using the firebase emulators:exec "go test" method didn't let me keep my existing CI setup.