如何在TestMain中为测试套件执行延迟功能?

时间:2019-04-03 02:57:23

标签: go

我已更新问题,以避免被指控发布XY问题。

以前的问题是:

  

我怎么知道何时在主goroutine上调用了runtime.Goexit?

我正在尝试编写一种方法,该方法通过调用runtime.Goexit()然后在生成的goroutine中调用os.Exit()来完成主goroutine上所有延迟的函数,然后再调用此退出方法。我的问题是我不知道runtime.Goexit()何时完成。我有什么办法可以知道主goroutine何时完成?

已更新:让我详细说明我的用例。为TestMain考虑以下模式:

func TestMain(m *testing.M) {
    db.Create()
    defer db.Drop()
    os.Exit(m.Run())
}

在这种情况下,因为os.Exit会停止程序,所以永远不会删除数据库。我的目标是提出一个替代方案 退出函数,该函数执行TestMain中所有延迟的函数。现在,我可以将所有内容移到另一个 这样的功能:

func realTestMain(m *testing.M) int {
    db.Create()
    defer db.Drop()
    return m.Run()
}

func TestMain(m *testing.M) {
    os.Exit(realTestMain(m))
}

但是,这种模式必须在我们的测试套件中强制执行,而且很难看且很难记住。我正在探索 是否可以使用一个帮助程序来编写我的设置/拆卸文件,如下所示:

func TestMain(m *testing.M) {
    db.Create()
    defer db.Drop()
    helpers.SafeExit(m.Run())
}

借助这样的助手,我可以编写一个简单的检查来确保os.Exit永远不会出现在我们的测试套件中。

3 个答案:

答案 0 :(得分:5)

如果要确保进行协调的清理不要,请使用runtime.Goexit。没有可靠/确定性的机制可以使用这种退出方法。

请改为使用context.Context并将其传递给您相关的例程。您可以显式取消Context,而例程可以监视此类事件的状态并进行清理。

如果有一些关键任务需要在市电退出之前执行,则可以使用sync.WaitGroup。为每个执行例程添加到等待组。并在每个执行例程完成时在WaitGroup上执行Done()。主例程最终可以在WaitGroup上Wait()上运行,并且在知道所有清理任务已经完成的情况下,程序将退出,


示例:

func main() {
    mainCtx, cancelFn := context.WithCancel(context.Background())
    var wg sync.WaitGroup

    wg.Add(1)
    go func() { taskA(mainCtx, &wg) }()
    wg.Add(1)
    go func() { manager(mainCtx, &wg, cancelFn) }()

    wg.Wait() // waiting for manager/task(s)
}


func taskA(ctx context.Context, wg *sync.WaitGroup) {
    defer func() {
        log.Println("defer(): taskA DONE!")
        wg.Done() // must be called last
    }()

    t := time.NewTicker(300 * time.Millisecond)

    for {
        select {
        case <-t.C:
            log.Println("taskA is working:", time.Now())
        case <-ctx.Done(): // are we done?
            log.Println("taskA was canceled. Reason:", ctx.Err())
            return
        }
    }
}

func manager(ctx context.Context, wg *sync.WaitGroup, cancelFn func()) {
    defer func() {
        log.Println("defer(): Manager DONE!")
        wg.Done() // must be called last
    }()

    time.Sleep(time.Second) // give worker(s) some time to work...

    cancelFn() // time to wrap up worker(s)!
}

工作playground version

答案 1 :(得分:2)

  

我有什么办法可以知道主goroutine何时完成?

是:程序停止后。

一旦主goroutine停止,您的程序即会终止,包括任何其他goroutine。因此,您将不得不重新设计,因为在主要goroutine退出后,什么也不会执行。

答案 2 :(得分:-2)

这是一个我可以在主goroutine的变量上使用终结器然后触发垃圾回收的解决方案:

package main

import (
  "fmt"
  "os"
  "runtime"
  "time"
)

type toBeCollected struct {
  dummy bool // need to have a non-empty struct
}

func main() {
  collectMe := new(toBeCollected)
  runtime.SetFinalizer(collectMe, func(*toBeCollected) {
    fmt.Println("now I'm really done")
    os.Exit(0)
  })
  go func() {
    for range time.NewTicker(time.Millisecond).C {
      runtime.GC()
    }
  }()
  runtime.Goexit()
}

奇怪的是,虽然我可以在本地运行它,但它在操场上却无法使用(请参见https://play.golang.org/p/bwf7fMu2w76)。