diff --git a/README.md b/README.md index 42a17b8..18db42f 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,12 @@ List the failpoints, $ curl http://127.0.0.1:1234/SomeFuncString=return("hello") ``` +Retrieve the execution count of a failpoint, + +```sh +$curl http://127.0.0.1:1234/SomeFuncString/execution-count -XGET +``` + Deactivate a failpoint, ```sh diff --git a/doc/design.md b/doc/design.md index a4e9e81..247112e 100644 --- a/doc/design.md +++ b/doc/design.md @@ -69,6 +69,16 @@ Similarly, you can set multiple failpoints using endpoint `/failpoints`, curl http://127.0.0.1:22381/failpoints -X PUT -d'failpoint1=return("hello");failpoint2=sleep(10)' ``` +You can get the execution count of the failpoint in the dynamic way, +``` +$curl http://127.0.0.1:1234/SomeFuncString/execution-count -XGET +``` + +To deactivate a failpoint, +``` +$ curl http://127.0.0.1:1234/SomeFuncString -XDELETE +``` + #### 3.2 Unit test Assuming there is a function with a failpoint something like below, ``` diff --git a/runtime/terms.go b/runtime/terms.go index 4d73a25..f94d22b 100644 --- a/runtime/terms.go +++ b/runtime/terms.go @@ -105,7 +105,7 @@ func (t *terms) eval() interface{} { for _, term := range t.chain { if term.mods.allow() { t.counter++ - return term.do(), nil + return term.do() } } return nil diff --git a/runtime/terms_test.go b/runtime/terms_test.go index 79da404..cffef6e 100644 --- a/runtime/terms_test.go +++ b/runtime/terms_test.go @@ -71,3 +71,45 @@ func TestTermsTypes(t *testing.T) { } } } + +func TestTermsCounter(t *testing.T) { + tests := []struct { + failpointTerm string + runAfterEnabling int + wantCount int + }{ + { + failpointTerm: `10*sleep(10)->1*return("abc")`, + runAfterEnabling: 12, + // This example tests mods which allows users to restrict the + // number of failpoint actions as against their callsite executions. + // This is the reason why wantCount < runAfterEnabling + // In a real world example you can hit a code spot a million times but + // using mods restrict the associated fallpoint actions to run twice. + wantCount: 11, + }, + { + failpointTerm: `10*sleep(10)->1*return("abc")`, + runAfterEnabling: 3, + wantCount: 3, + }, + { + failpointTerm: `10*sleep(10)->1*return("abc")`, + runAfterEnabling: 0, + wantCount: 0, + }, + } + for _, tt := range tests { + ter, err := newTerms("test", tt.failpointTerm) + if err != nil { + t.Fatal(err) + } + for i := 0; i < tt.runAfterEnabling; i++ { + _ = ter.eval() + } + + if ter.counter != tt.wantCount { + t.Fatalf("counter is not properly incremented") + } + } +} diff --git a/runtime/termscounter_test.go b/runtime/termscounter_test.go deleted file mode 100644 index 876f209..0000000 --- a/runtime/termscounter_test.go +++ /dev/null @@ -1,158 +0,0 @@ -package runtime_test - -import ( - "strings" - "testing" - - "go.etcd.io/gofail/runtime" -) - -// This variable mimics the code generated by gofail code package. -// This works in tandem with exampleFunc function. -var __fp_ExampleString *runtime.Failpoint //nolint:stylecheck - -// check if failpoint is initialized as gofail -// tests can clear global variables of runtime packages -func initFP() { - fps := runtime.List() - if fps != nil { - s_fps := strings.Join(fps, " ") - if strings.Contains(s_fps, "ExampleString") { - return - } - } - __fp_ExampleString = runtime.NewFailpoint("ExampleString") //nolint:stylecheck -} - -func TestTermsCounter(t *testing.T) { - testcases := []struct { - name string - fp string - failpointTerm string - runBeforeEnabling int - runAfterEnabling int - wantCount int - }{ - { - name: "Terms limit Failpoint", - fp: "ExampleString", - failpointTerm: `10*sleep(10)->1*return("abc")`, - runAfterEnabling: 12, - // This example tests mods which allows users to restrict the - // number of failpoint actions as against their callsite executions. - // This is the reason why wantCount < runAfterEnabling - // In a real world example you can hit a code spot a million times but - // using mods restrict the associated fallpoint actions to run twice. - wantCount: 11, - }, - { - name: "Inbetween Enabling Failpoint", - fp: "ExampleString", - failpointTerm: `10*sleep(10)->1*return("abc")`, - runBeforeEnabling: 2, - runAfterEnabling: 3, - wantCount: 3, - }, - { - name: "Before Enabling Failpoint", - fp: "ExampleString", - failpointTerm: `10*sleep(10)->1*return("abc")`, - runBeforeEnabling: 2, - runAfterEnabling: 0, - wantCount: 0, - }, - } - - initFP() - for _, tc := range testcases { - t.Run(tc.name, func(t *testing.T) { - - for i := 0; i < tc.runBeforeEnabling; i++ { - exampleFunc() - } - - err := runtime.Enable(tc.fp, tc.failpointTerm) - if err != nil { - t.Fatal(err) - } - defer runtime.Disable(tc.fp) - for i := 0; i < tc.runAfterEnabling; i++ { - exampleFunc() - } - _, count, err := runtime.Status(tc.fp) - if err != nil { - t.Fatal(err) - } - if tc.wantCount != count { - t.Fatal("counter is not properly incremented") - } - }) - } -} - -func TestEnablingNewTermResetsCount(t *testing.T) { - testcases := []struct { - name string - fp string - oldTerm string - newTerm string - runOldTerm int - runNewTerm int - wantCount int - }{ - { - name: "Change and Reset Counter", - fp: "ExampleString", - oldTerm: `10*sleep(10)->1*return("abc")`, - newTerm: "sleep(10)", - runOldTerm: 2, - runNewTerm: 3, - wantCount: 3, - }, - } - - initFP() - for _, tc := range testcases { - t.Run(tc.name, func(t *testing.T) { - err := runtime.Enable(tc.fp, tc.oldTerm) - if err != nil { - t.Fatal(err) - } - - for i := 0; i < tc.runOldTerm; i++ { - exampleFunc() - } - err = runtime.Enable(tc.fp, tc.newTerm) - if err != nil { - t.Fatal(err) - } - defer runtime.Disable(tc.fp) - - for i := 0; i < tc.runNewTerm; i++ { - exampleFunc() - } - _, count, err := runtime.Status(tc.fp) - if err != nil { - t.Fatal(err) - } - if tc.wantCount != count { - t.Fatal("counter is not properly incremented") - } - }) - } - -} - -// This function mimics a customized code that is generated by gofail code package. -func exampleFunc() string { - if vExampleString, __fpErr := __fp_ExampleString.Acquire(); __fpErr == nil { //nolint:stylecheck - ExampleString, __fpTypeOK := vExampleString.(string) //nolint:stylecheck - if !__fpTypeOK { //nolint:stylecheck - goto __badTypeExampleString //nolint:stylecheck - } - return ExampleString - __badTypeExampleString: //nolint:stylecheck - __fp_ExampleString.BadType(vExampleString, "string") //nolint:stylecheck - } - return "example" -}