这个属于奇技淫巧,不建议项目中使用。如果要用一定要带上注释。
最近在搞coredns的开发,代码嘎嘎敲完之后开始啃单元测试。
在写测试的时候,需要测试的某个函数,我们这里假设是 func abc(r request.Request)
,它接收coredns的 request.Request
类型,在内部调用的 r.Name()
r.IP()
获取请求域名和请求方ip,但是对应单测文件的传入参数在我接手的时候写的还是字符串类型,估计好久没改过了。
这是request.Request的定义
// Request contains some connection state and is useful in plugin.
type Request struct {
Req *dns.Msg
W dns.ResponseWriter
// Optional lowercased zone of this query.
Zone string
// Cache size after first call to Size or Do. If size is zero nothing has been cached yet.
// Both Size and Do set these values (and cache them).
size uint16 // UDP buffer size, or 64K in case of TCP.
do bool // DNSSEC OK value
// Caches
family int8 // transport's family.
name string // lowercase qname.
ip string // client's ip.
port string // client's port.
localPort string // server's port.
localIP string // server's ip.
}
想要测试这个函数,就需要向其中传入有效的Request结构体,但是问题是name和ip在里面都是小写字母开头,属于私有变量,也没有一个方便的方法可以在新建Request时初始化name和ip。
为啥不用反射
写过java,看到这个问题,第一个想法自然是使用反射来获取对应的成员赋值。但是理想和现实总有点差距,查了才知道,golang中的反射限制有点多。
java中反射的作用很强大,又可以取值又可以赋值。golang中的反射只可以获取已知名称字段的值,如果想要给私有成员赋值就会抛出panic炸掉程序。(公有成员没事)
使用unsafe解决赋值问题
好在golang中还可以获取变量的指针,这样我们可以通过获取Request的unsafe指针来通过偏移量修改其成员的值。(奇技淫巧,如无必要,不宜使用)
获取偏移量
观察数据结构发现,name和ip和结构体的尾部仅相差几个字符串的偏移量。因此采用修改 基址+sizeof(Request)-n*sizeof(string)
的格式来定位对应地址。
测试
先写个demo测试一下,鉴于name和ip分别是结构体倒数第五和第四个成员,因此下面 (unsafe.Sizeof(*new(string)))
需要分别乘5和乘4
func GenTestRequest(name string) request.Request {
req_name := name
req_name_bytes := []byte(req_name)
req_ip := "127.0.0.1"
req_ip_bytes := []byte(req_ip)
test_req := request.Request{}
p := unsafe.Pointer(&test_req)
*(*[]byte)(unsafe.Pointer(uintptr(p) + uintptr(unsafe.Sizeof(request.Request{})) - uintptr(unsafe.Sizeof(*new(string)))*5)) = req_name_bytes
*(*[]byte)(unsafe.Pointer(uintptr(p) + uintptr(unsafe.Sizeof(request.Request{})) - uintptr(unsafe.Sizeof(*new(string)))*4)) = req_ip_bytes
return test_req
}
打上断点,直接开跑
可以看到,对应的私有变量已经被修改成我们想要的值