diff --git a/.github/workflows/govuln.yaml b/.github/workflows/govuln.yaml index 7d1ce95..904242a 100644 --- a/.github/workflows/govuln.yaml +++ b/.github/workflows/govuln.yaml @@ -3,7 +3,7 @@ name: Go Vulnerability Checker on: [push, pull_request] permissions: read-all jobs: - test: + govuln: runs-on: ubuntu-latest steps: - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml new file mode 100644 index 0000000..fee79d0 --- /dev/null +++ b/.github/workflows/integration_tests.yml @@ -0,0 +1,27 @@ +--- +name: Integration Tests +on: + push: + tags: + - v* + branches: + - master + - main + pull_request: +permissions: + contents: read + # Optional: allow read access to pull request. Use with `only-new-issues` option. + # pull-requests: read +jobs: + integration-test: + runs-on: ubuntu-latest + steps: + - id: goversion + run: echo "goversion=$(cat .go-version)" >> "$GITHUB_OUTPUT" + - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 + with: + go-version: ${{ steps.goversion.outputs.goversion }} + - uses: actions/checkout@v4 + - name: tests + run: | + make run-all-integration-tests diff --git a/Makefile b/Makefile index 7a64ced..fdbd0bf 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ SRCS=$(filter-out %_test.go, $(wildcard *.go */*.go)) +include integration/makefile.mk .PHONY: all all: gofail diff --git a/integration/README.md b/integration/README.md new file mode 100644 index 0000000..c07c185 --- /dev/null +++ b/integration/README.md @@ -0,0 +1,5 @@ +# Integration Tests + +Each directory contains a scenario + +- sleep: the enabling and disabling of a failpoint won't be delayed due to an ongoing sleep() action diff --git a/integration/makefile.mk b/integration/makefile.mk new file mode 100644 index 0000000..6998e84 --- /dev/null +++ b/integration/makefile.mk @@ -0,0 +1,50 @@ +GOFAIL_BINARY = $(shell pwd)/gofail + +.PHONY: run-all-integration-tests +run-all-integration-tests: + # we enable all failpoints + $(MAKE) gofail-enable + + # we compile and execute all integration tests + # add new integration test targets here + $(MAKE) run-integration-test-sleep + + # we disable all failpoints + $(MAKE) gofail-disable + $(MAKE) clean-gofail + +.PHONY: clean-all-integration-tests +clean-all-integration-tests: clean-integration-test-sleep gofail-disable + +.PHONY: gofail-enable +gofail-enable: build-gofail + $(GOFAIL_BINARY) enable ./integration/sleep/failpoints + +.PHONY: gofail-disable +gofail-disable: build-gofail + ${GOFAIL_BINARY} disable ./integration/sleep/failpoints + +# run integration test - sleep +.PHONY: run-integration-test-sleep +run-integration-test-sleep: build-integration-test-sleep execute-integration-test-sleep clean-integration-test-sleep + +.PHONY: build-integration-test-sleep +build-integration-test-sleep: + cd ./integration/sleep && go build -o integration_test_sleep . + +.PHONY: execute-integration-test-sleep +execute-integration-test-sleep: + cd ./integration/sleep && ./integration_test_sleep + +.PHONY: clean-integration-test-sleep +clean-integration-test-sleep: + cd ./integration/sleep && rm integration_test_sleep + +# helper: build/remove gofail binaries +.PHONY: build-gofail +build-gofail: + GO_BUILD_FLAGS="-v" ./build.sh + +.PHONY: clean-gofail +clean-gofail: + rm -f gofail diff --git a/integration/sleep/failpoints/failpoints.go b/integration/sleep/failpoints/failpoints.go new file mode 100644 index 0000000..d1e1407 --- /dev/null +++ b/integration/sleep/failpoints/failpoints.go @@ -0,0 +1,40 @@ +package failpoints + +import ( + "log" + "sync" + "time" +) + +func Worker1(wg *sync.WaitGroup) { + defer wg.Done() + + log.Println("worker1 in") + defer log.Println("worker1 out") + + // gofail: var worker1Failpoint struct{} + + time.Sleep(3 * time.Second) +} + +func Worker2(wg *sync.WaitGroup) { + defer wg.Done() + + log.Println("worker2 in") + defer log.Println("worker2 out") + + // gofail: var worker2Failpoint struct{} + + time.Sleep(3 * time.Second) +} + +func Worker3(wg *sync.WaitGroup) { + defer wg.Done() + + log.Println("worker3 in") + defer log.Println("worker3 out") + + // gofail: var worker3Failpoint struct{} + + time.Sleep(3 * time.Second) +} diff --git a/integration/sleep/go.mod b/integration/sleep/go.mod new file mode 100644 index 0000000..16f9891 --- /dev/null +++ b/integration/sleep/go.mod @@ -0,0 +1,9 @@ +module go.etcd.io/gofail/integration/sleep + +go 1.21 + +toolchain go1.21.11 + +require go.etcd.io/gofail v0.1.1-0.20240328162059-93c579a86c46 + +replace go.etcd.io/gofail => ./../../ diff --git a/integration/sleep/go.sum b/integration/sleep/go.sum new file mode 100644 index 0000000..5496456 --- /dev/null +++ b/integration/sleep/go.sum @@ -0,0 +1,8 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/integration/sleep/main.go b/integration/sleep/main.go new file mode 100644 index 0000000..b0d0cbf --- /dev/null +++ b/integration/sleep/main.go @@ -0,0 +1,81 @@ +package main + +import ( + "log" + "sync" + "time" + + "go.etcd.io/gofail/integration/sleep/failpoints" + gofail "go.etcd.io/gofail/runtime" +) + +func main() { + { + // expectation: this part of the code will take about 3s to execute, because all go routines will be executing concurrently + start := time.Now() + + log.Println("Stage 1: Run 3 workers under normal logic") + var wg sync.WaitGroup + wg.Add(1) + go failpoints.Worker1(&wg) + + wg.Add(1) + go failpoints.Worker2(&wg) + + wg.Add(1) + go failpoints.Worker3(&wg) + + wg.Wait() + + elapsed := time.Since(start) + if elapsed > (3*time.Second + 100*time.Millisecond) { + log.Fatalln("invalid execution time", elapsed) + } + + log.Println("Stage 1: Done") + } + + { + // expectation: this part of the code will take about 6s to execute only, + // because all go routines will be executing concurrently, with both the sleep + // from failpoint and the original sleep actions + // + // The gofail implementation up till commit 93c579a86c46 is executing the + // program sequentially, due to the failpoint action execution and enable/disable + // flows are under the same locking mechanism, only one of the actions can make + // progress at a given moment + log.Println("Stage 2: Run 3 workers under failpoint logic") + + start := time.Now() + + var wg sync.WaitGroup + gofail.Enable("worker1Failpoint", `sleep("3s")`) + wg.Add(1) + go failpoints.Worker1(&wg) + time.Sleep(10 * time.Millisecond) + + gofail.Enable("worker2Failpoint", `sleep("3s")`) + wg.Add(1) + go failpoints.Worker2(&wg) + time.Sleep(10 * time.Millisecond) + + gofail.Enable("worker3Failpoint", `sleep("3s")`) + wg.Add(1) + go failpoints.Worker3(&wg) + time.Sleep(10 * time.Millisecond) + + // the failpoint can be disabled during failpoint execution + gofail.Disable("worker1Failpoint") + gofail.Disable("worker2Failpoint") + gofail.Disable("worker3Failpoint") + + wg.Wait() + + elapsed := time.Since(start) + if elapsed > (6*time.Second + 100*time.Millisecond) { + log.Fatalln("invalid execution time", elapsed) + } + + log.Println("Stage 2: Done") + } +}