Golang 单元测试暂停时间

本文介绍了在Golang中进行单元测试时如何冻结时间,以便控制测试中的时间影响。探讨了两种方案:一是通过类似动态语言的方式修改机器码,但此方法不安全且平台兼容性待考察;二是利用Golang的`time_fake.go`,官方提供了伪造时间的功能,但在编译时需添加特定参数,适用场景有限。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Golang 冻结时间

部分单元测试的时候,需要考虑时间的影响,有时需要我们冻结时间,或者整个测试过程中设定时间的返回数据。
因此需要考虑,如何在不修改原有逻辑的情况下冻结时间,或者替换时间函数。

update list

2022年05月12日12:57:50: add REPO

已经在这里实现了,注意 方法非常不安全。
https://github.com/MXuDong/test-utils-go

效果

在这里插入图片描述
大家可以看到已经可以直接停止掉时间。

原理

方案1 类似动态语言的操作,直接修改机器码

另外编译的时候需要禁用内联:-gcflags=-l

当前方案有一定问题,对不同平台的支持性有待考察。
根据 golang plan9 汇编的描述,我们可以得知,调用函数的实际过程中本质上是对函数执行了 call 的操作。
在这里插入图片描述
那么理论上来讲,在执行 0x108b9e0 的逻辑地址时,将 main.a(SB) 更换为 其它函数,操作是存在可行性的。我们可以直接 jmp 到我们的对应函数上。
具体实现方案,等确定平台兼容性后再做处理。
另外,本质上这个操作是直接控制内存,因此,不要再线上模式中使用!

甚至我认为直接覆盖原来的方法来达到某些奇怪的目的好像也是可以得了。

代码:

// stopTime will replace the time function, and always return yeas first time.
func stopTime(year int) {
	timeFunc := time.Now

	// custom function
	value = year // value is a 问题。。。。。
	f := func() time.Time {
		return time.Date(value, 1, 1, 00, 00, 00, 00, time.UTC)
	}

	timeFuncLocation := **(**uintptr)(unsafe.Pointer(&timeFunc))
	timeFuncWindow := (*(*[0xFF]byte)(unsafe.Pointer(timeFuncLocation)))[:]

	// disable in-line and given permission to operator the memory
	err := syscall.Mprotect(
		(*(*[0xFFFFFF]byte)(unsafe.Pointer(timeFuncLocation & ^uintptr(syscall.Getpagesize()-1))))[:syscall.Getpagesize()],
		syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC)
	if err != nil {
		fmt.Println(err)
	}

	newTimeJumpAssemble := *(*uintptr)(unsafe.Pointer(&f))
	funcJumpAssemble := []byte{
		0x48, 0xC7, 0xC2,
		byte(newTimeJumpAssemble >> 0),
		byte(newTimeJumpAssemble >> 8),
		byte(newTimeJumpAssemble >> 16),
		byte(newTimeJumpAssemble >> 24), // MOV rdx, fn address byte (use to 32 bytes)
		0xFF, 0x22,                      // JMP [rdx]
	}

	copy(timeFuncWindow, funcJumpAssemble)
}

// value: the default year value, and define it as global value.
var value = 200

func main() {

	// 未冻结时间
	println(time.Now().String())
	time.Sleep(1 * time.Second)
	println(time.Now().String())
	//time.Sleep(10 * time.Second)
	println(time.Now().String())

	//冻结时间
	stopTime(2030)
	println(time.Now().String())
	time.Sleep(1 * time.Second)
	println(time.Now().String())
	//time.Sleep(10 * time.Second)
	println(time.Now().String())
}

方案2 使用原生的 time_fake.go

在这里插入图片描述
golang 的 runtime 包下的 time.go 提供了最基础的获取当前时间的函数,大部分获取时间的操作本质上都是调用了该文件的该方法执行的。
官方已经提供了一个用于 fake 时间的文件,在编译的时候加上参数即可 ‘faketime’。
但是这种方式使用场景有限。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值