Go:类似于C的宏,用于编写测试代码

When writing test code, I do a lot of this

if (!cond) {
    t.Fatal("error message")
}

It's a bit tedious. So I'd like to achieve the following

CHECK(cond, "error message")

So I attempted this

func CHECK(t *testing.T, cond bool, fmt string, a ...interface{}) {
  if !cond {
    t.Fatal(fmt, a)
  }
}

If it were a C macro it would've worked perfectly. But in Go, the line number where the failure is is wrong.

Is there a fix for this?

Sadly you can't do that.

A workaround would be to get the line / function yourself, something like the trace function from https://stackoverflow.com/a/25954534/145587.

You could possibly make use of runtime.Callers()+runtime.Caller(): the first one gives you the call stack while the second allows to extract the debug info about any arbitrary stack frame (obtained from that list).

Your CHECK() function is always one function call down the place the check should have happened at if it was a macro, so you can inspect the stack frame just above.


Update: the only functon which is really needed is runtime.Caller(). Here's your case, simplified:

package main

import (
    "runtime"
    "testing"
)

func CHECK(t *testing.T, cond bool) {
    if !cond {
        _, fname, lineno, ok := runtime.Caller(1)
        if !ok {
            fname, lineno = "<UNKNOWN>", -1
        }
        t.Fatalf("FAIL: %s:%d", fname, lineno)
    }
}

func TestFoo(t *testing.T) {
    CHECK(t, 12 == 13)
}

When saved as check_test.go and run via go test, it produces:

$ go test
--- FAIL: TestFoo (0.00 seconds)
        check_test.go:14: FAIL: /home/kostix/devel/go/src/check/check_test.go:19
FAIL
exit status 1
FAIL    check   0.001s

where line 19 is the line a call to CHECK() is located inside TestFoo().

While the above answer to use CHECK() function will work, I think that the actual answer is code readibility. Much of Go has been designed as a compromise to increase readibility among the community as a whole. See gofmt for example. Most people will agree that it's format is not best for every case. But having a convention agreed to by all is a huge plus for Go. The same answer is to your question. Go is for writing code for your peers, not for yourself. So don't think "I prefer this." Think "what will people reading my code understand."

Your original code should be like this, without parenthesis.

if !cond {
    t.Fatal("error message")
}

This is idiomatic and every Go coder will recognize it instantly. That is the point.