init mock
Motivation
The init function is a powerful but dangerous language feature in golang.
Some annoying logics hidden in init functions and make it difficult to do unit tests.
So I make a lightweight library to skip them.
Replacing the init function is a little bit tricky. At first I wanted to find a way to mock init function with monkey-patching,
but it's not easy to control the package initialization order. You have to init a special package before the package that you
want to mock. It's also difficult to hack the go compiler. I think the easiest way to do so is to do it after compiling and
before the execution. Luckily go test have the exec flag to pass the compiled executable file in commandline to a specific program.
Fundamentals
-
How a golang program compiles and calls init function
-
The executable file (elf) layout
-
How do we locate the inittask in executable files?
- symbol name => VMA
- data Section VMA + data Section File Offset - VMA => symbol file offset
- symbol file offset -> init done flag + init function pointer
-
How to skip package init?
-
How to replace init function?
- locate the both init task;
- skip the source init function;
- swap the source and destination init function pointer in each task;
Usage
- install initmock:
go install github.com/huiscool/initmock
- run go test with
-exec initmock build flag and addition binary flags:
-skippkg=[package name]: skip the package initialization
-skipinit=[init func name]: skip the init function execution
-replaceinit=[src init func name]:[dst init func name]: replace init function with another one
NOTICE 1:
IMHO build/test flags are passed to go and binary flags are passed to test binary. initmock read the
skipinit and replaceinit from the binary flags, which settles after the package arguments. Unkwown
build/test flags passed to go-test will prevent running tests.
NOTICE 2:
To skip/replace multiple init functions, use multiple skipinit/replaceinit flag;
initmock will extract the skipinit/replaceinit flag with below regular expression:
-?-(skipinit|replaceinit)[= ](\S+)?
NOTICE 3:
go test may omit debug info when compile flag is not set. Use flags below to keep test binary:
-blockprofile -cpuprofile -memprofile -mutexprofile -c -o
NOTICE 4:
Initmock assumes the executable file's arch and os is the same with itself. Initmock doesn't work with cross-compiling.
Currently we only support windows/amd64 , darwin/amd64, linux/amd64
example:
go test ./testmain -exec initmock -v -skippkg github.com/huiscool/initmock/testmain/panic
go test ./testmain -exec initmock -v -skipinit github.com/huiscool/initmock/testmain/panic.init.0
go test ./testmain -exec initmock -v -replaceinit github.com/huiscool/initmock/testmain/panic.init.0:github.com/huiscool/initmock/testmain.init.0
Reference
[1] 【Go】一文带你深入了解初始化函数