Go
golang

os.Exit()はDefer functionの実行を待たない

More than 1 year has passed since last update.

testingパッケージを利用してテストを記述している場合、初期化や終了後の後処理を記述するにはTestMain関数を利用する。

package test

import (
  "log"
  "os"
  "testing"
)

func TestMain(m *testing.M) {
  setup()
  code := m.Run()
  teardown()
  os.Exit(code)
}

func setup() {
  // 何らかの初期化処理...
  log.Println("setupped here.")
}

func teardown() {
  // 何らかの終了処理...
  log.Println("cleaned up here.")
}

func TestCase(t *testing.T) {
  t.Errorf("the test always fails")
}

この時、いつもの癖でteadown()deferで呼ぶコードを書いてしまうとする。こんなかんじだ。

func TestMain(m *testing.M) {
  setup()
  defer teardown()
  code := m.Run()
  os.Exit(code)
}

しかし、この書き方では問題が起こる。

deferを使ってしまうとこうなる
2015/06/13 00:06:04 setupped here.
=== RUN TestCase
--- FAIL: TestCase (0.00s)
    test_test.go:29: the test always fails
FAIL
exit status 1   <--- teardown()が実行されていない!!
FAIL    github.com/harukasan/test/test  0.008s

os.Exit(code)は全てのdeferを破棄して終了する。そのため、deferが正常に実行されずに、後処理が行われないのである。

で、どうするかなんだけど、まず考えるのはdeferの中でos.Exit(code int)を呼ぶ方法である。うまいこと終了コードを渡さないといけないので、あまり綺麗じゃないけどポインタ渡しにした。

func TestMain(m *testing.M) {
  setup()
  var code int
  defer func(int *code) {
    teardown()
    os.Exit(*code)
  }(&code)

  code = m.Run()
}

他の方法としては、codeをグローバル変数にする方法がある。

var code = 0

func TestMain(m *testing.M) {
  setup()
  var code int
  defer func() {
    teardown()
    os.Exit(code)
  }()

  code = m.Run()
}

どっちもどっちっぽいし、そもそもdeferを使うのをやめた方が綺麗なんじゃないかと思う。