
Fault-injection library for Go that adds runtime-controllable failpoints to trigger panics, sleeps, returns, and conditional error paths via environment variables.
An implementation of failpoints for Golang. Fail points are used to add code points where errors may be injected in a user controlled fashion. Fail point is a code snippet that is only executed when the corresponding failpoint is active.
failpoint-ctl)Build failpoint-ctl from source
git clone https://github.com/pingcap/failpoint.git
cd failpoint
make
ls bin/failpoint-ctl
Inject failpoints to your program, eg:
package main
import "github.com/pingcap/failpoint"
func main() {
failpoint.Inject("testPanic", func() {
panic("failpoint triggerd")
})
}
Transfrom your code with failpoint-ctl enable
Build with go build
Enable failpoints with GO_FAILPOINTS environment variable
GO_FAILPOINTS="main/testPanic=return(true)" ./your-program
Note: GO_FAILPOINTS does not work with InjectCall type of marker.
If you use go run to run the test, don't forget to add the generated binding__failpoint_binding__.go in your command, like:
GO_FAILPOINTS="main/testPanic=return(true)" go run your-program.go binding__failpoint_binding__.go
failpoint-toolexec)Build failpoint-toolexec from source
git clone https://github.com/pingcap/failpoint.git
cd failpoint
make
ls bin/failpoint-toolexec
Inject failpoints to your program, eg:
package main
import "github.com/pingcap/failpoint"
func main() {
failpoint.Inject("testPanic", func() {
panic("failpoint triggerd")
})
}
Use a separate build cache to avoid mixing caches without failpoint-toolexec, and build
GOCACHE=/tmp/failpoint-cache go build -toolexec path/to/failpoint-toolexec
Enable failpoints with GO_FAILPOINTS environment variable
GO_FAILPOINTS="main/testPanic=return(true)" ./your-program
You can also use go run or go test, like:
GOCACHE=/tmp/failpoint-cache GO_FAILPOINTS="main/testPanic=return(true)" go run -toolexec path/to/failpoint-toolexec your-program.go
Define failpoint in valid Golang code, not comments or anything else
Failpoint does not have any extra cost
Failpoint routine is writable/readable and should be checked by a compiler
Generated code by failpoint definition is easy to read
Keep the line numbers same with the injecting codes(easier to debug)
Support parallel tests with context.Context
Failpoint
Faillpoint is a code snippet that is only executed when the corresponding failpoint is active.
The closure will never be executed if failpoint.Disable("failpoint-name-for-demo") is executed.
var outerVar = "declare in outer scope"
failpoint.Inject("failpoint-name-for-demo", func(val failpoint.Value) {
fmt.Println("unit-test", val, outerVar)
})
Marker functions
It is just an empty function
It is easy to write/read
Introduce a compiler check for failpoints which cannot compile in the regular mode if failpoint code is invalid
Marker funtion list
func Inject(fpname string, fpblock func(val Value)) {}func InjectContext(fpname string, ctx context.Context, fpblock func(val Value)) {}func InjectCall(fpname string, args ...any) {}func Break(label ...string) {}func Goto(label string) {}func Continue(label ...string) {}func Fallthrough() {}func Return(results ...interface{}) {}func Label(label string) {}Supported failpoint environment variable
failpoint can be enabled by export environment variables with the following patten, which is quite similar to freebsd failpoint SYSCTL VARIABLES
Note: InjectCall cannot be enabled by environment variables.
[<percent>%][<count>*]<type>[(args...)][-><more terms>]
The argument specifies which action to take; it can be one of:
You can call failpoint.Inject to inject a failpoint to the call site, where failpoint-name is
used to trigger the failpoint and failpoint-closure will be expanded as the body of the IF statement.
failpoint.Inject("failpoint-name", func(val failpoint.Value) {
failpoint.Return("unit-test", val)
})
The converted code looks like:
if val, _err_ := failpoint.Eval(_curpkg_("failpoint-name")); _err_ == nil {
return "unit-test", val
}
failpoint.Value is the value that passes by failpoint.Enable("failpoint-name", "return(5)")
which can be ignored.
failpoint.Inject("failpoint-name", func(_ failpoint.Value) {
fmt.Println("unit-test")
})
OR
failpoint.Inject("failpoint-name", func() {
fmt.Println("unit-test")
})
And the converted code looks like:
if _, _err_ := failpoint.Eval(_curpkg_("failpoint-name")); _err_ == nil {
fmt.Println("unit-test")
}
Also, the failpoint closure can be a function which takes context.Context. You can
do some customized things with context.Context like controlling whether a failpoint is
active in parallel tests or other cases. For example,
failpoint.InjectContext(ctx, "failpoint-name", func(val failpoint.Value) {
fmt.Println("unit-test", val)
})
The converted code looks like:
if val, _err_ := failpoint.EvalContext(ctx, _curpkg_("failpoint-name")); _err_ == nil {
fmt.Println("unit-test", val)
}
You can ignore context.Context, and this will generate the same code as above non-context version. For example,
failpoint.InjectContext(nil, "failpoint-name", func(val failpoint.Value) {
fmt.Println("unit-test", val)
})
Becomes