Recently learned some information about go failpiont, and now want to share it.
FailPiont is used to simulate a fault in the automatic test
Automated testing, often need to simulate some fault conditions, and then to test our procedures in this case whether a failure to perform properly in accordance with our ideas. Some failures are easier to test code simulation, but there are some relatively Kun Dan, such as a broken network interface traffic overrun the like. Then there are the great God to engage in out FailPiont.
Etcd made gofail https://github.com/etcd-io/gofail
gofail is used
func someFunc() string { // gofail: var SomeFuncString string // // this is SomeFuncStringcalled when the failpoint is triggered // return SomeFuncString return "default" }
To write code for a simulated failure, then the code commented out. When we test execution gofail enable command, then the code has become so
func someFunc() string { if vSomeFuncString, __fpErr := __fp_SomeFuncString.Acquire(); __fpErr == nil { defer __fp_SomeFuncString.Release(); SomeFuncString, __fpTypeOK := vSomeFuncString.(string); if !__fpTypeOK { goto __badTypeSomeFuncString} // this is SomeFuncStringcalled when the failpoint is triggered return SomeFuncString; __badTypeSomeFuncString: __fp_SomeFuncString.BadType(vSomeFuncString, "string"); }; return "default" }
There is also a generated file
// GENERATED BY GOFAIL. DO NOT EDIT. package fail_point import "github.com/etcd-io/gofail/runtime" var __fp_SomeFuncString *runtime.Failpoint = runtime.NewFailpoint("fail_point", "SomeFuncString")
Then we write a test code
func Test_someFunc(t *testing.T) { // /Users/edz/Documents/dev/metrics/src/fail_point/ gofail.Enable("fail_point/SomeFuncString", `return("SomeFuncString")`) defer gofail.Disable("fail_point/SomeFuncString") tests := []struct { name string want string }{ {name: "pass", want: "SomeFuncString"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := someFunc(); got != tt.want { t.Errorf("someFunc() = %v, want %v", got, tt.want) } }) } }
Run some tests, passed.
But this gofail also has some drawbacks, such as the presence of the comment is not easy to find errors; and generated code readability is poor.
To solve these problems, pingcap company made a upgraded version of gofail == "failpiont
Such code is failpiont
func pingCapFail() (string, failpoint.Value) { failpoint.Inject("failpoint-name", func(val failpoint.Value) { failpoint.Return("unit-test", val) }) return "success", nil } func pingCapFail1() string { failpoint.Inject("failpoint-name1", func() { failpoint.Return("unit-test") }) return "success" } func pingCapFail2(ctx context.Context) (string, failpoint.Value) { failpoint.InjectContext(ctx, "failpoint-name2", func(val failpoint.Value) { failpoint.Return("unit-test", val) }) return "success", nil }
We can see, failpiont avoid the drawbacks of using annotations to achieve, but also inject some code to affect the execution of the code.
This allows multiple simultaneous test cases in the test, different use cases and some will be triggered fault, some will ignore the fault.
Execution failpoint-ctl enable commands to enable
This time the code has become such
func pingCapFail() (string, failpoint.Value) { if val, ok := failpoint.Eval(_curpkg_("failpoint-name")); ok { return "unit-test", val } return "success", nil } func pingCapFail1() string { if _, ok := failpoint.Eval(_curpkg_("failpoint-name1")); ok { return "unit-test" } return "success" } func pingCapFail2(ctx context.Context) (string, failpoint.Value) { if val, ok := failpoint.EvalContext(ctx, _curpkg_("failpoint-name2")); ok { return "unit-test", val } return "success", nil }
This can be seen when the readability of the code is also high.
Then we write some test code
func Test_pingCapFail(t *testing.T) { tests := []struct { name string want string want1 failpoint.Value }{ {name: "pingCapFail", want: "unit-test", want1: 5}, } failpoint.Enable("fail_point/failpoint-name", "return(5)") for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, got1 := pingCapFail() if got != tt.want { t.Errorf("pingCapFail() got = %v, want %v", got, tt.want) } if !reflect.DeepEqual(got1, tt.want1) { t.Errorf("pingCapFail() got1 = %v, want %v", got1, tt.want1) } }) } } func Test_pingCapFail1(t *testing.T) { tests := []struct { name string want string }{ {name: "pingCapFail1", want: "unit-test"}, } failpoint.Enable("fail_point/failpoint-name1", "return(5)") for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := pingCapFail1(); got != tt.want { t.Errorf("pingCapFail1() = %v, want %v", got, tt.want) } }) } } func Test_pingCapFail2(t *testing.T) { type args struct { ctx context.Context } c := &gin.Context{} failPoints := map[string]struct{}{ "a": {}, "b": {}, // "fail_point/failpoint-name2": {}, } ctx := failpoint.WithHook(c, func(ctx context.Context, fpname string) bool { _, found := failPoints[fpname] // Only enables some failpoints. return found }) tests := []struct { name string args args want string want1 failpoint.Value }{ {"pingCapFail2", args{ctx}, "success", nil}, } failpoint.Enable("fail_point/failpoint-name2", "return(5)") for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, got1 := pingCapFail2(tt.args.ctx) if got != tt.want { t.Errorf("pingCapFail2() got = %v, want %v", got, tt.want) } if !reflect.DeepEqual(got1, tt.want1) { t.Errorf("pingCapFail2() got1 = %v, want %v", got1, tt.want1) } }) } }
Run it, test.
I want to be useful, thank you.