
Biblioteca de injeção de falhas para Go que adiciona failpoints controláveis em tempo de execução para disparar panics, sleeps, returns e caminhos de erro condicionais via variáveis de ambiente.
Uma implementação de failpoints para Golang. Fail points são usados para adicionar pontos de código onde erros podem ser injetados de forma controlada pelo usuário. Um fail point é um trecho de código que só é executado quando o failpoint correspondente está ativo.
failpoint-ctl)Compile failpoint-ctl a partir do código-fonte
git clone https://github.com/pingcap/failpoint.git
cd failpoint
make
ls bin/failpoint-ctl
Injete failpoints no seu programa, por exemplo:
package main
import "github.com/pingcap/failpoint"
func main() {
failpoint.Inject("testPanic", func() {
panic("failpoint triggerd")
})
}
Transforme o seu código com failpoint-ctl enable
Compile com go build
Ative failpoints com a variável de ambiente GO_FAILPOINTS
GO_FAILPOINTS="main/testPanic=return(true)" ./your-program
Nota: GO_FAILPOINTS não funciona com marcadores do tipo InjectCall.
Se você usar go run para executar o teste, não se esqueça de adicionar o binding__failpoint_binding__.go gerado ao seu comando, por exemplo:
GO_FAILPOINTS="main/testPanic=return(true)" go run your-program.go binding__failpoint_binding__.go
failpoint-toolexec)Compile failpoint-toolexec a partir do código-fonte
git clone https://github.com/pingcap/failpoint.git
cd failpoint
make
ls bin/failpoint-toolexec
Injete failpoints no seu programa, por exemplo:
package main
import "github.com/pingcap/failpoint"
func main() {
failpoint.Inject("testPanic", func() {
panic("failpoint triggerd")
})
}
Use um cache de build separado para evitar misturar caches sem o failpoint-toolexec e compile
GOCACHE=/tmp/failpoint-cache go build -toolexec path/to/failpoint-toolexec
Ative failpoints com a variável de ambiente GO_FAILPOINTS
GO_FAILPOINTS="main/testPanic=return(true)" ./your-program
Você também pode usar go run ou go test, por exemplo:
GOCACHE=/tmp/failpoint-cache GO_FAILPOINTS="main/testPanic=return(true)" go run -toolexec path/to/failpoint-toolexec your-program.go
Defina failpoint em código Golang válido, não em comentários ou qualquer outra coisa
Failpoint não tem nenhum custo extra
A rotina do failpoint é escrevível/legível e deve ser verificada por um compilador
O código gerado pela definição de failpoint é fácil de ler
Mantenha os números de linha iguais aos dos códigos de injeção (mais fácil de depurar)
Suporte a testes paralelos com context.Context
Failpoint
Um failpoint é um trecho de código que só é executado quando o failpoint correspondente está ativo.
A closure nunca será executada se failpoint.Disable("failpoint-name-for-demo") for executado.
var outerVar = "declare in outer scope"
failpoint.Inject("failpoint-name-for-demo", func(val failpoint.Value) {
fmt.Println("unit-test", val, outerVar)
})
Funções de marcador
É apenas uma função vazia
É fácil de escrever/ler
Introduz uma verificação do compilador para failpoints que não podem compilar no modo normal se o código do failpoint for inválido
Lista de funções de marcador
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) {}Variável de ambiente suportada para failpoint
failpoint pode ser ativado exportando variáveis de ambiente com o seguinte padrão, o que é bem semelhante a variáveis SYSCTL do failpoint do FreeBSD
Nota: InjectCall não pode ser ativado por variáveis de ambiente.
[<percent>%][<count>*]<type>[(args...)][-><more terms>]
O argumento especifica qual ação tomar; pode ser um dos seguintes:
Você pode chamar failpoint.Inject para injetar um failpoint no local da chamada, onde failpoint-name é usado para acionar o failpoint e failpoint-closure será expandido como o corpo da instrução IF.
failpoint.Inject("failpoint-name", func(val failpoint.Value) {
failpoint.Return("unit-test", val)
})
O código convertido fica assim:
if val, _err_ := failpoint.Eval(_curpkg_("failpoint-name")); _err_ == nil {
return "unit-test", val
}
failpoint.Value é o valor passado por failpoint.Enable("failpoint-name", "return(5)"), que pode ser ignorado.
failpoint.Inject("failpoint-name", func(_ failpoint.Value) {
fmt.Println("unit-test")
})
OU
failpoint.Inject("failpoint-name", func() {
fmt.Println("unit-test")
})
E o código convertido fica assim:
if _, _err_ := failpoint.Eval(_curpkg_("failpoint-name")); _err_ == nil {
fmt.Println("unit-test")
}
Além disso, a closure do failpoint pode ser uma função que recebe context.Context. Você pode fazer algumas coisas personalizadas com context.Context, como controlar se um failpoint está ativo em testes paralelos ou em outros casos. Por exemplo,
failpoint.InjectContext(ctx, "failpoint-name", func(val failpoint.Value) {
fmt.Println("unit-test", val)
})
O código convertido fica assim: