From a8f43a56c845c0e9d356472958c9103a96067aad Mon Sep 17 00:00:00 2001 From: Dimitris Date: Thu, 14 Dec 2023 15:27:22 +0200 Subject: [PATCH 01/11] Add hex package --- pkg/utils/hex/hex.go | 55 +++++++++++++++++++++++++++++++ pkg/utils/hex/hex_test.go | 68 +++++++++++++++++++++++++++++++++++++++ pkg/utils/utils.go | 9 ------ 3 files changed, 123 insertions(+), 9 deletions(-) create mode 100644 pkg/utils/hex/hex.go create mode 100644 pkg/utils/hex/hex_test.go diff --git a/pkg/utils/hex/hex.go b/pkg/utils/hex/hex.go new file mode 100644 index 000000000..818356772 --- /dev/null +++ b/pkg/utils/hex/hex.go @@ -0,0 +1,55 @@ +package hex + +import ( + "encoding/hex" + "errors" + "fmt" + "math/big" + "strings" +) + +// EnsurePrefix adds the prefix (0x) to a given hex string. +func EnsurePrefix(str string) string { + if !strings.HasPrefix(str, "0x") { + str = "0x" + str + } + return str +} + +// ToBig parses the given hex string or panics if it is invalid. +func ToBig(s string) *big.Int { + n, ok := new(big.Int).SetString(s, 16) + if !ok { + panic(fmt.Errorf(`failed to convert "%s" as hex to big.Int`, s)) + } + return n +} + +// RemovePrefix removes the prefix (0x) of a given hex string. +func RemovePrefix(str string) string { + if HasPrefix(str) { + return str[2:] + } + return str +} + +// HasPrefix returns true if the string starts with 0x. +func HasPrefix(str string) bool { + return len(str) >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X') +} + +// TryParse parses the given hex string to bytes, +// it can return error if the hex string is invalid. +// Follows the semantic of ethereum's FromHex. +func TryParse(s string) (b []byte, err error) { + if !HasPrefix(s) { + err = errors.New("hex string must have 0x prefix") + } else { + s = s[2:] + if len(s)%2 == 1 { + s = "0" + s + } + b, err = hex.DecodeString(s) + } + return +} diff --git a/pkg/utils/hex/hex_test.go b/pkg/utils/hex/hex_test.go new file mode 100644 index 000000000..6e1827559 --- /dev/null +++ b/pkg/utils/hex/hex_test.go @@ -0,0 +1,68 @@ +package hex_test + +import ( + "testing" + + "github.com/smartcontractkit/chainlink-common/pkg/utils/hex" + "github.com/stretchr/testify/assert" +) + +func TestHasPrefix(t *testing.T) { + t.Parallel() + + t.Run("has prefix", func(t *testing.T) { + t.Parallel() + + r := hex.HasPrefix("0xabc") + assert.True(t, r) + }) + + t.Run("doesn't have prefix", func(t *testing.T) { + t.Parallel() + + r := hex.HasPrefix("abc") + assert.False(t, r) + }) + + t.Run("has 0x suffix", func(t *testing.T) { + t.Parallel() + + r := hex.HasPrefix("abc0x") + assert.False(t, r) + }) + +} + +func TestTryParse(t *testing.T) { + t.Parallel() + + t.Run("0x prefix missing", func(t *testing.T) { + t.Parallel() + + _, err := hex.TryParse("abcd") + assert.Error(t, err) + }) + + t.Run("wrong hex characters", func(t *testing.T) { + t.Parallel() + + _, err := hex.TryParse("0xabcdzzz") + assert.Error(t, err) + }) + + t.Run("valid hex string", func(t *testing.T) { + t.Parallel() + + b, err := hex.TryParse("0x1234") + assert.NoError(t, err) + assert.Equal(t, []byte{0x12, 0x34}, b) + }) + + t.Run("prepend odd length with zero", func(t *testing.T) { + t.Parallel() + + b, err := hex.TryParse("0x123") + assert.NoError(t, err) + assert.Equal(t, []byte{0x1, 0x23}, b) + }) +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 9adff93e7..57ea147c3 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -4,7 +4,6 @@ import ( "context" "math" mrand "math/rand" - "strings" "time" "github.com/smartcontractkit/chainlink-common/pkg/services" @@ -50,11 +49,3 @@ func IsZero[C comparable](val C) bool { var zero C return zero == val } - -// EnsureHexPrefix adds the prefix (0x) to a given hex string. -func EnsureHexPrefix(str string) string { - if !strings.HasPrefix(str, "0x") { - str = "0x" + str - } - return str -} From c6cac32516f8b181340e6c417a8d60e1fe753be1 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Thu, 14 Dec 2023 16:06:34 +0200 Subject: [PATCH 02/11] Improvements --- pkg/utils/hex/hex.go | 14 +++++++------- pkg/utils/hex/hex_test.go | 29 +++++++++++++++++++++++------ 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/pkg/utils/hex/hex.go b/pkg/utils/hex/hex.go index 818356772..abf207c65 100644 --- a/pkg/utils/hex/hex.go +++ b/pkg/utils/hex/hex.go @@ -16,17 +16,17 @@ func EnsurePrefix(str string) string { return str } -// ToBig parses the given hex string or panics if it is invalid. -func ToBig(s string) *big.Int { +// ToBig parses the given hex string and returns error if it is invalid. +func ToBig(s string) (*big.Int, error) { n, ok := new(big.Int).SetString(s, 16) if !ok { - panic(fmt.Errorf(`failed to convert "%s" as hex to big.Int`, s)) + return nil, fmt.Errorf(`failed to convert "%s" as hex to big.Int`, s) } - return n + return n, nil } -// RemovePrefix removes the prefix (0x) of a given hex string. -func RemovePrefix(str string) string { +// TrimPrefix removes the prefix (0x) of a given hex string. +func TrimPrefix(str string) string { if HasPrefix(str) { return str[2:] } @@ -41,7 +41,7 @@ func HasPrefix(str string) bool { // TryParse parses the given hex string to bytes, // it can return error if the hex string is invalid. // Follows the semantic of ethereum's FromHex. -func TryParse(s string) (b []byte, err error) { +func DecodeString(s string) (b []byte, err error) { if !HasPrefix(s) { err = errors.New("hex string must have 0x prefix") } else { diff --git a/pkg/utils/hex/hex_test.go b/pkg/utils/hex/hex_test.go index 6e1827559..f1eb4c89a 100644 --- a/pkg/utils/hex/hex_test.go +++ b/pkg/utils/hex/hex_test.go @@ -7,6 +7,24 @@ import ( "github.com/stretchr/testify/assert" ) +func TestTrimPrefix(t *testing.T) { + t.Parallel() + + t.Run("trims prefix", func(t *testing.T) { + t.Parallel() + + s := hex.TrimPrefix("0xabc") + assert.Equal(t, "abc", s) + }) + + t.Run("returns the same string if it doesn't have prefix", func(t *testing.T) { + t.Parallel() + + s := hex.TrimPrefix("defg") + assert.Equal(t, "defg", s) + }) +} + func TestHasPrefix(t *testing.T) { t.Parallel() @@ -30,30 +48,29 @@ func TestHasPrefix(t *testing.T) { r := hex.HasPrefix("abc0x") assert.False(t, r) }) - } -func TestTryParse(t *testing.T) { +func TestDecodeString(t *testing.T) { t.Parallel() t.Run("0x prefix missing", func(t *testing.T) { t.Parallel() - _, err := hex.TryParse("abcd") + _, err := hex.DecodeString("abcd") assert.Error(t, err) }) t.Run("wrong hex characters", func(t *testing.T) { t.Parallel() - _, err := hex.TryParse("0xabcdzzz") + _, err := hex.DecodeString("0xabcdzzz") assert.Error(t, err) }) t.Run("valid hex string", func(t *testing.T) { t.Parallel() - b, err := hex.TryParse("0x1234") + b, err := hex.DecodeString("0x1234") assert.NoError(t, err) assert.Equal(t, []byte{0x12, 0x34}, b) }) @@ -61,7 +78,7 @@ func TestTryParse(t *testing.T) { t.Run("prepend odd length with zero", func(t *testing.T) { t.Parallel() - b, err := hex.TryParse("0x123") + b, err := hex.DecodeString("0x123") assert.NoError(t, err) assert.Equal(t, []byte{0x1, 0x23}, b) }) From b20725f93888002aa0fb602bbb7a51bb1bcfe0a2 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Thu, 14 Dec 2023 16:43:19 +0200 Subject: [PATCH 03/11] Minor fixes --- pkg/utils/hex/hex.go | 6 +++--- pkg/utils/hex/hex_test.go | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/pkg/utils/hex/hex.go b/pkg/utils/hex/hex.go index abf207c65..926af75da 100644 --- a/pkg/utils/hex/hex.go +++ b/pkg/utils/hex/hex.go @@ -16,8 +16,8 @@ func EnsurePrefix(str string) string { return str } -// ToBig parses the given hex string and returns error if it is invalid. -func ToBig(s string) (*big.Int, error) { +// ParseBig parses the given hex string and returns error if it is invalid. +func ParseBig(s string) (*big.Int, error) { n, ok := new(big.Int).SetString(s, 16) if !ok { return nil, fmt.Errorf(`failed to convert "%s" as hex to big.Int`, s) @@ -38,7 +38,7 @@ func HasPrefix(str string) bool { return len(str) >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X') } -// TryParse parses the given hex string to bytes, +// DecodeString parses the given hex string to bytes, // it can return error if the hex string is invalid. // Follows the semantic of ethereum's FromHex. func DecodeString(s string) (b []byte, err error) { diff --git a/pkg/utils/hex/hex_test.go b/pkg/utils/hex/hex_test.go index f1eb4c89a..d09cb9b98 100644 --- a/pkg/utils/hex/hex_test.go +++ b/pkg/utils/hex/hex_test.go @@ -7,6 +7,24 @@ import ( "github.com/stretchr/testify/assert" ) +func TestParseBig(t *testing.T) { + t.Parallel() + + t.Run("parses successfully", func(t *testing.T) { + t.Parallel() + + _, err := hex.ParseBig("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F") + assert.NoError(t, err) + }) + + t.Run("returns error", func(t *testing.T) { + t.Parallel() + + _, err := hex.ParseBig("0xabc") + assert.Error(t, err) + }) +} + func TestTrimPrefix(t *testing.T) { t.Parallel() From b106ff6d58f6faf4be4362974cdec0b38731a714 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Mon, 18 Dec 2023 17:19:59 +0200 Subject: [PATCH 04/11] Add util methods --- pkg/utils/bytes/bytes.go | 10 ++++++++++ pkg/utils/utils.go | 21 +++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/pkg/utils/bytes/bytes.go b/pkg/utils/bytes/bytes.go index a29e25dda..01586ec3a 100644 --- a/pkg/utils/bytes/bytes.go +++ b/pkg/utils/bytes/bytes.go @@ -15,3 +15,13 @@ func TrimQuotes(input []byte) []byte { } return input } + +// IsEmpty returns true if bytes contains only zero values, or has len 0. +func IsEmpty(bytes []byte) bool { + for _, b := range bytes { + if b != 0 { + return false + } + } + return true +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 57ea147c3..25d3521bd 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -6,6 +6,8 @@ import ( mrand "math/rand" "time" + "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink-common/pkg/services" ) @@ -49,3 +51,22 @@ func IsZero[C comparable](val C) bool { var zero C return zero == val } + +// JustError takes a tuple and returns the last entry, the error. +func JustError(_ interface{}, err error) error { + return err +} + +// WrapIfError decorates an error with the given message. It is intended to +// be used with `defer` statements, like so: +// +// func SomeFunction() (err error) { +// defer WrapIfError(&err, "error in SomeFunction:") +// +// ... +// } +func WrapIfError(err *error, msg string) { + if *err != nil { + *err = errors.Wrap(*err, msg) + } +} From e77cfff12df206c9abd0dd0332f7aef7509b916f Mon Sep 17 00:00:00 2001 From: Dimitris Date: Mon, 18 Dec 2023 17:36:19 +0200 Subject: [PATCH 05/11] Add AllEqual --- pkg/utils/utils.go | 10 ++++++++++ pkg/utils/utils_test.go | 17 +++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 pkg/utils/utils_test.go diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 25d3521bd..dc2b834fc 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -70,3 +70,13 @@ func WrapIfError(err *error, msg string) { *err = errors.Wrap(*err, msg) } } + +// AllEqual returns true iff all the provided elements are equal to each other. +func AllEqual[T comparable](elems ...T) bool { + for i := 1; i < len(elems); i++ { + if elems[i] != elems[0] { + return false + } + } + return true +} diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go new file mode 100644 index 000000000..3e7ef4fcf --- /dev/null +++ b/pkg/utils/utils_test.go @@ -0,0 +1,17 @@ +package utils_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/utils" +) + +func TestAllEqual(t *testing.T) { + t.Parallel() + + require.False(t, utils.AllEqual(1, 2, 3, 4, 5)) + require.True(t, utils.AllEqual(1, 1, 1, 1, 1)) + require.False(t, utils.AllEqual(1, 1, 1, 2, 1, 1, 1)) +} From 77599830fd349d0a6e503f2db4224fc6b4314a5a Mon Sep 17 00:00:00 2001 From: Dimitris Date: Tue, 19 Dec 2023 17:49:03 +0200 Subject: [PATCH 06/11] Add SleeperTask and DependentAwaiter --- go.mod | 4 + go.sum | 28 +++++++ pkg/config/toml.go | 22 ++++++ pkg/utils/sleeper_task.go | 129 +++++++++++++++++++++++++++++++++ pkg/utils/sleeper_task_test.go | 118 ++++++++++++++++++++++++++++++ pkg/utils/utils.go | 45 ++++++++++++ pkg/utils/utils_test.go | 85 ++++++++++++++++++++++ 7 files changed, 431 insertions(+) create mode 100644 pkg/config/toml.go create mode 100644 pkg/utils/sleeper_task.go create mode 100644 pkg/utils/sleeper_task_test.go diff --git a/go.mod b/go.mod index 5f51b2b86..26144388e 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,8 @@ require ( github.com/linkedin/goavro/v2 v2.12.0 github.com/mitchellh/mapstructure v1.5.0 github.com/mwitkow/grpc-proxy v0.0.0-20230212185441-f345521cb9c9 + github.com/onsi/gomega v1.10.3 + github.com/pelletier/go-toml/v2 v2.1.1 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.17.0 github.com/riferrei/srclient v0.5.4 @@ -70,8 +72,10 @@ require ( golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 19224b433..927f42bb6 100644 --- a/go.sum +++ b/go.sum @@ -88,6 +88,9 @@ github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBF github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -179,6 +182,7 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= @@ -229,14 +233,25 @@ github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/nolag/mapstructure v1.5.1 h1:jEMB2AM8NXEosSMTPXlbycOpBsqttEBh5owT0gJs9/I= github.com/nolag/mapstructure v1.5.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4= +github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= +github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w= github.com/opencontainers/runc v1.1.3/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= +github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= +github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -362,6 +377,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -386,6 +402,7 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= @@ -413,6 +430,7 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -421,7 +439,9 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -509,6 +529,7 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -607,7 +628,14 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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/pkg/config/toml.go b/pkg/config/toml.go new file mode 100644 index 000000000..f51db7636 --- /dev/null +++ b/pkg/config/toml.go @@ -0,0 +1,22 @@ +package config + +import ( + "errors" + "io" + + "github.com/pelletier/go-toml/v2" +) + +// DecodeTOML decodes toml from r in to v. +// Requires strict field matches and returns full toml.StrictMissingError details. +func DecodeTOML(r io.Reader, v any) error { + d := toml.NewDecoder(r).DisallowUnknownFields() + if err := d.Decode(v); err != nil { + var strict *toml.StrictMissingError + if errors.As(err, &strict) { + return errors.New(strict.String()) + } + return err + } + return nil +} diff --git a/pkg/utils/sleeper_task.go b/pkg/utils/sleeper_task.go new file mode 100644 index 000000000..d84457e93 --- /dev/null +++ b/pkg/utils/sleeper_task.go @@ -0,0 +1,129 @@ +package utils + +import ( + "fmt" + "time" + + "github.com/smartcontractkit/chainlink-common/pkg/services" +) + +// SleeperTask represents a task that waits in the background to process some work. +type SleeperTask interface { + Stop() error + WakeUp() + WakeUpIfStarted() +} + +// Worker is a simple interface that represents some work to do repeatedly +type Worker interface { + Work() + Name() string +} + +type sleeperTask struct { + services.StateMachine + worker Worker + chQueue chan struct{} + chStop chan struct{} + chDone chan struct{} + chWorkDone chan struct{} +} + +// NewSleeperTask takes a worker and returns a SleeperTask. +// +// SleeperTask is guaranteed to call Work on the worker at least once for every +// WakeUp call. +// If the Worker is busy when WakeUp is called, the Worker will be called again +// immediately after it is finished. For this reason you should take care to +// make sure that Worker is idempotent. +// WakeUp does not block. +func NewSleeperTask(worker Worker) SleeperTask { + s := &sleeperTask{ + worker: worker, + chQueue: make(chan struct{}, 1), + chStop: make(chan struct{}), + chDone: make(chan struct{}), + chWorkDone: make(chan struct{}, 10), + } + + _ = s.StartOnce("SleeperTask-"+worker.Name(), func() error { + go s.workerLoop() + return nil + }) + + return s +} + +// Stop stops the SleeperTask +func (s *sleeperTask) Stop() error { + return s.StopOnce("SleeperTask-"+s.worker.Name(), func() error { + close(s.chStop) + select { + case <-s.chDone: + case <-time.After(15 * time.Second): + return fmt.Errorf("SleeperTask-%s took too long to stop", s.worker.Name()) + } + return nil + }) +} + +func (s *sleeperTask) WakeUpIfStarted() { + s.IfStarted(func() { + select { + case s.chQueue <- struct{}{}: + default: + } + }) +} + +// WakeUp wakes up the sleeper task, asking it to execute its Worker. +func (s *sleeperTask) WakeUp() { + if !s.IfStarted(func() { + select { + case s.chQueue <- struct{}{}: + default: + } + }) { + panic("cannot wake up stopped sleeper task") + } +} + +func (s *sleeperTask) workDone() { + select { + case s.chWorkDone <- struct{}{}: + default: + } +} + +// WorkDone isn't part of the SleeperTask interface, but can be +// useful in tests to assert that the work has been done. +func (s *sleeperTask) WorkDone() <-chan struct{} { + return s.chWorkDone +} + +func (s *sleeperTask) workerLoop() { + defer close(s.chDone) + + for { + select { + case <-s.chQueue: + s.worker.Work() + s.workDone() + case <-s.chStop: + return + } + } +} + +type sleeperTaskWorker struct { + name string + work func() +} + +// SleeperFuncTask returns a Worker to execute the given work function. +func SleeperFuncTask(work func(), name string) Worker { + return &sleeperTaskWorker{name: name, work: work} +} + +func (w *sleeperTaskWorker) Name() string { return w.name } +func (w *sleeperTaskWorker) Work() { w.work() } diff --git a/pkg/utils/sleeper_task_test.go b/pkg/utils/sleeper_task_test.go new file mode 100644 index 000000000..ab91a8e0d --- /dev/null +++ b/pkg/utils/sleeper_task_test.go @@ -0,0 +1,118 @@ +package utils_test + +import ( + "sync/atomic" + "testing" + "time" + + "github.com/smartcontractkit/chainlink-common/pkg/utils" + + "github.com/onsi/gomega" + "github.com/stretchr/testify/require" +) + +type countingWorker struct { + numJobsPerformed atomic.Int32 + delay time.Duration +} + +func (t *countingWorker) Name() string { + return "CountingWorker" +} + +func (t *countingWorker) Work() { + if t.delay != 0 { + time.Sleep(t.delay) + } + // Without an atomic, the race detector fails + t.numJobsPerformed.Add(1) +} + +func (t *countingWorker) getNumJobsPerformed() int { + return int(t.numJobsPerformed.Load()) +} + +func TestSleeperTask_WakeupAfterStopPanics(t *testing.T) { + t.Parallel() + + worker := &countingWorker{} + sleeper := utils.NewSleeperTask(worker) + + require.NoError(t, sleeper.Stop()) + + require.Panics(t, func() { + sleeper.WakeUp() + }) + gomega.NewWithT(t).Eventually(worker.getNumJobsPerformed).Should(gomega.Equal(0)) +} + +func TestSleeperTask_CallingStopTwiceFails(t *testing.T) { + t.Parallel() + + worker := &countingWorker{} + sleeper := utils.NewSleeperTask(worker) + require.NoError(t, sleeper.Stop()) + require.Error(t, sleeper.Stop()) +} + +func TestSleeperTask_WakeupPerformsWork(t *testing.T) { + t.Parallel() + + worker := &countingWorker{} + sleeper := utils.NewSleeperTask(worker) + + sleeper.WakeUp() + gomega.NewWithT(t).Eventually(worker.getNumJobsPerformed).Should(gomega.Equal(1)) + require.NoError(t, sleeper.Stop()) +} + +type controllableWorker struct { + countingWorker + awaitWorkStarted chan struct{} + allowResumeWork chan struct{} + ignoreSignals bool +} + +func (w *controllableWorker) Work() { + if !w.ignoreSignals { + w.awaitWorkStarted <- struct{}{} + <-w.allowResumeWork + } + w.countingWorker.Work() +} + +func TestSleeperTask_WakeupEnqueuesMaxTwice(t *testing.T) { + t.Parallel() + + worker := &controllableWorker{awaitWorkStarted: make(chan struct{}), allowResumeWork: make(chan struct{})} + sleeper := utils.NewSleeperTask(worker) + + sleeper.WakeUp() + <-worker.awaitWorkStarted + sleeper.WakeUp() + sleeper.WakeUp() + sleeper.WakeUp() + sleeper.WakeUp() + sleeper.WakeUp() + worker.ignoreSignals = true + worker.allowResumeWork <- struct{}{} + + gomega.NewWithT(t).Eventually(worker.getNumJobsPerformed).Should(gomega.Equal(2)) + gomega.NewWithT(t).Consistently(worker.getNumJobsPerformed).Should(gomega.BeNumerically("<", 3)) + require.NoError(t, sleeper.Stop()) +} + +func TestSleeperTask_StopWaitsUntilWorkFinishes(t *testing.T) { + t.Parallel() + + worker := &controllableWorker{awaitWorkStarted: make(chan struct{}), allowResumeWork: make(chan struct{})} + sleeper := utils.NewSleeperTask(worker) + + sleeper.WakeUp() + <-worker.awaitWorkStarted + require.Equal(t, 0, worker.getNumJobsPerformed()) + worker.allowResumeWork <- struct{}{} + + require.NoError(t, sleeper.Stop()) + require.Equal(t, worker.getNumJobsPerformed(), 1) +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index dc2b834fc..39dc61b3e 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -4,6 +4,7 @@ import ( "context" "math" mrand "math/rand" + "sync" "time" "github.com/pkg/errors" @@ -80,3 +81,47 @@ func AllEqual[T comparable](elems ...T) bool { } return true } + +// WaitGroupChan creates a channel that closes when the provided sync.WaitGroup is done. +func WaitGroupChan(wg *sync.WaitGroup) <-chan struct{} { + chAwait := make(chan struct{}) + go func() { + defer close(chAwait) + wg.Wait() + }() + return chAwait +} + +// DependentAwaiter contains Dependent funcs +type DependentAwaiter interface { + AwaitDependents() <-chan struct{} + AddDependents(n int) + DependentReady() +} + +type dependentAwaiter struct { + wg *sync.WaitGroup + ch <-chan struct{} +} + +// NewDependentAwaiter creates a new DependentAwaiter +func NewDependentAwaiter() DependentAwaiter { + return &dependentAwaiter{ + wg: &sync.WaitGroup{}, + } +} + +func (da *dependentAwaiter) AwaitDependents() <-chan struct{} { + if da.ch == nil { + da.ch = WaitGroupChan(da.wg) + } + return da.ch +} + +func (da *dependentAwaiter) AddDependents(n int) { + da.wg.Add(n) +} + +func (da *dependentAwaiter) DependentReady() { + da.wg.Done() +} diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index 3e7ef4fcf..346cfab47 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -1,7 +1,9 @@ package utils_test import ( + "sync" "testing" + "time" "github.com/stretchr/testify/require" @@ -15,3 +17,86 @@ func TestAllEqual(t *testing.T) { require.True(t, utils.AllEqual(1, 1, 1, 1, 1)) require.False(t, utils.AllEqual(1, 1, 1, 2, 1, 1, 1)) } + +func TestWaitGroupChan(t *testing.T) { + t.Parallel() + + wg := &sync.WaitGroup{} + wg.Add(2) + + ch := utils.WaitGroupChan(wg) + + select { + case <-ch: + t.Fatal("should not fire immediately") + default: + } + + wg.Done() + + select { + case <-ch: + t.Fatal("should not fire until finished") + default: + } + + go func() { + time.Sleep(2 * time.Second) + wg.Done() + }() + + callbackOrTimeout(t, "WaitGroupChan fires", func() { + <-ch + }, 5*time.Second) +} + +func TestDependentAwaiter(t *testing.T) { + t.Parallel() + + da := utils.NewDependentAwaiter() + da.AddDependents(2) + + select { + case <-da.AwaitDependents(): + t.Fatal("should not fire immediately") + default: + } + + da.DependentReady() + + select { + case <-da.AwaitDependents(): + t.Fatal("should not fire until finished") + default: + } + + go func() { + time.Sleep(2 * time.Second) + da.DependentReady() + }() + + callbackOrTimeout(t, "dependents are now ready", func() { + <-da.AwaitDependents() + }, 5*time.Second) +} + +func callbackOrTimeout(t testing.TB, msg string, callback func(), durationParams ...time.Duration) { + t.Helper() + + duration := 100 * time.Millisecond + if len(durationParams) > 0 { + duration = durationParams[0] + } + + done := make(chan struct{}) + go func() { + callback() + close(done) + }() + + select { + case <-done: + case <-time.After(duration): + t.Fatalf("CallbackOrTimeout: %s timed out", msg) + } +} From 9a9ca795f074d9f543807cc02779007ee0554b9b Mon Sep 17 00:00:00 2001 From: Dimitris Date: Wed, 20 Dec 2023 15:58:07 +0200 Subject: [PATCH 07/11] Fixes --- pkg/utils/bytes/bytes_test.go | 16 ++++++++++++++++ pkg/utils/sleeper_task.go | 26 ++++++++++---------------- pkg/utils/utils.go | 5 ++--- 3 files changed, 28 insertions(+), 19 deletions(-) create mode 100644 pkg/utils/bytes/bytes_test.go diff --git a/pkg/utils/bytes/bytes_test.go b/pkg/utils/bytes/bytes_test.go new file mode 100644 index 000000000..fa514493a --- /dev/null +++ b/pkg/utils/bytes/bytes_test.go @@ -0,0 +1,16 @@ +package bytes_test + +import ( + "testing" + + "github.com/smartcontractkit/chainlink-common/pkg/utils/bytes" + "github.com/stretchr/testify/require" +) + +func TestIsEmpty(t *testing.T) { + t.Parallel() + + require.True(t, bytes.IsEmpty([]byte{0, 0, 0})) + require.True(t, bytes.IsEmpty([]byte{})) + require.False(t, bytes.IsEmpty([]byte{1, 2, 3, 5})) +} diff --git a/pkg/utils/sleeper_task.go b/pkg/utils/sleeper_task.go index d84457e93..bbbcfb287 100644 --- a/pkg/utils/sleeper_task.go +++ b/pkg/utils/sleeper_task.go @@ -7,20 +7,14 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/services" ) -// SleeperTask represents a task that waits in the background to process some work. -type SleeperTask interface { - Stop() error - WakeUp() - WakeUpIfStarted() -} - // Worker is a simple interface that represents some work to do repeatedly type Worker interface { Work() Name() string } -type sleeperTask struct { +// SleeperTask represents a task that waits in the background to process some work. +type SleeperTask struct { services.StateMachine worker Worker chQueue chan struct{} @@ -37,8 +31,8 @@ type sleeperTask struct { // immediately after it is finished. For this reason you should take care to // make sure that Worker is idempotent. // WakeUp does not block. -func NewSleeperTask(worker Worker) SleeperTask { - s := &sleeperTask{ +func NewSleeperTask(worker Worker) *SleeperTask { + s := &SleeperTask{ worker: worker, chQueue: make(chan struct{}, 1), chStop: make(chan struct{}), @@ -55,7 +49,7 @@ func NewSleeperTask(worker Worker) SleeperTask { } // Stop stops the SleeperTask -func (s *sleeperTask) Stop() error { +func (s *SleeperTask) Stop() error { return s.StopOnce("SleeperTask-"+s.worker.Name(), func() error { close(s.chStop) select { @@ -67,7 +61,7 @@ func (s *sleeperTask) Stop() error { }) } -func (s *sleeperTask) WakeUpIfStarted() { +func (s *SleeperTask) WakeUpIfStarted() { s.IfStarted(func() { select { case s.chQueue <- struct{}{}: @@ -77,7 +71,7 @@ func (s *sleeperTask) WakeUpIfStarted() { } // WakeUp wakes up the sleeper task, asking it to execute its Worker. -func (s *sleeperTask) WakeUp() { +func (s *SleeperTask) WakeUp() { if !s.IfStarted(func() { select { case s.chQueue <- struct{}{}: @@ -88,7 +82,7 @@ func (s *sleeperTask) WakeUp() { } } -func (s *sleeperTask) workDone() { +func (s *SleeperTask) workDone() { select { case s.chWorkDone <- struct{}{}: default: @@ -97,11 +91,11 @@ func (s *sleeperTask) workDone() { // WorkDone isn't part of the SleeperTask interface, but can be // useful in tests to assert that the work has been done. -func (s *sleeperTask) WorkDone() <-chan struct{} { +func (s *SleeperTask) WorkDone() <-chan struct{} { return s.chWorkDone } -func (s *sleeperTask) workerLoop() { +func (s *SleeperTask) workerLoop() { defer close(s.chDone) for { diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 39dc61b3e..c08cd447c 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -2,13 +2,12 @@ package utils import ( "context" + "fmt" "math" mrand "math/rand" "sync" "time" - "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink-common/pkg/services" ) @@ -68,7 +67,7 @@ func JustError(_ interface{}, err error) error { // } func WrapIfError(err *error, msg string) { if *err != nil { - *err = errors.Wrap(*err, msg) + *err = fmt.Errorf("%s : %w", msg, *err) } } From 87231db5b74183bb0c1080920374cd52c55bca53 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Wed, 20 Dec 2023 17:43:49 +0200 Subject: [PATCH 08/11] Fix WrapIfError --- pkg/utils/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index c08cd447c..9a98ea4b9 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -67,7 +67,7 @@ func JustError(_ interface{}, err error) error { // } func WrapIfError(err *error, msg string) { if *err != nil { - *err = fmt.Errorf("%s : %w", msg, *err) + *err = fmt.Errorf("%s: %w", msg, *err) } } From 32aed04fb051a1a169bad45a2c7df0201cf087cb Mon Sep 17 00:00:00 2001 From: Dimitris Date: Wed, 20 Dec 2023 17:56:35 +0200 Subject: [PATCH 09/11] Add WrapIfError test --- pkg/utils/utils_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index 346cfab47..93601638a 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -1,15 +1,27 @@ package utils_test import ( + "errors" "sync" "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-common/pkg/utils" ) +func TestWrapIfError(t *testing.T) { + t.Parallel() + + t.Run("wraps error", func(t *testing.T) { + err := errors.New("this is an error") + utils.WrapIfError(&err, "wrapped message") + assert.Equal(t, "wrapped message: this is an error", err.Error()) + }) +} + func TestAllEqual(t *testing.T) { t.Parallel() From bfe59b5f93576783fc101d8a4da2e43005868b55 Mon Sep 17 00:00:00 2001 From: Jordan Krage Date: Thu, 21 Dec 2023 09:58:02 -0600 Subject: [PATCH 10/11] replace gomega with chans (#300) --- pkg/utils/sleeper_task_test.go | 99 ++++++++++++++++++++++++---------- 1 file changed, 70 insertions(+), 29 deletions(-) diff --git a/pkg/utils/sleeper_task_test.go b/pkg/utils/sleeper_task_test.go index ab91a8e0d..3cc233fbc 100644 --- a/pkg/utils/sleeper_task_test.go +++ b/pkg/utils/sleeper_task_test.go @@ -1,41 +1,36 @@ package utils_test import ( - "sync/atomic" "testing" "time" - "github.com/smartcontractkit/chainlink-common/pkg/utils" - - "github.com/onsi/gomega" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/utils" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" ) -type countingWorker struct { - numJobsPerformed atomic.Int32 - delay time.Duration +type chanWorker struct { + ch chan struct{} + delay time.Duration } -func (t *countingWorker) Name() string { - return "CountingWorker" +func (t *chanWorker) Name() string { + return "ChanWorker" } -func (t *countingWorker) Work() { +func (t *chanWorker) Work() { if t.delay != 0 { time.Sleep(t.delay) } - // Without an atomic, the race detector fails - t.numJobsPerformed.Add(1) -} - -func (t *countingWorker) getNumJobsPerformed() int { - return int(t.numJobsPerformed.Load()) + t.ch <- struct{}{} } func TestSleeperTask_WakeupAfterStopPanics(t *testing.T) { t.Parallel() - worker := &countingWorker{} + worker := &chanWorker{ch: make(chan struct{}, 1)} sleeper := utils.NewSleeperTask(worker) require.NoError(t, sleeper.Stop()) @@ -43,13 +38,18 @@ func TestSleeperTask_WakeupAfterStopPanics(t *testing.T) { require.Panics(t, func() { sleeper.WakeUp() }) - gomega.NewWithT(t).Eventually(worker.getNumJobsPerformed).Should(gomega.Equal(0)) + + select { + case <-worker.ch: + t.Fatal("work was performed when none was expected") + default: + } } func TestSleeperTask_CallingStopTwiceFails(t *testing.T) { t.Parallel() - worker := &countingWorker{} + worker := &chanWorker{} sleeper := utils.NewSleeperTask(worker) require.NoError(t, sleeper.Stop()) require.Error(t, sleeper.Stop()) @@ -57,17 +57,24 @@ func TestSleeperTask_CallingStopTwiceFails(t *testing.T) { func TestSleeperTask_WakeupPerformsWork(t *testing.T) { t.Parallel() + ctx := tests.Context(t) - worker := &countingWorker{} + worker := &chanWorker{ch: make(chan struct{}, 1)} sleeper := utils.NewSleeperTask(worker) sleeper.WakeUp() - gomega.NewWithT(t).Eventually(worker.getNumJobsPerformed).Should(gomega.Equal(1)) + + select { + case <-worker.ch: + case <-ctx.Done(): + t.Error("timed out waiting for work to be performed") + } + require.NoError(t, sleeper.Stop()) } type controllableWorker struct { - countingWorker + chanWorker awaitWorkStarted chan struct{} allowResumeWork chan struct{} ignoreSignals bool @@ -78,13 +85,14 @@ func (w *controllableWorker) Work() { w.awaitWorkStarted <- struct{}{} <-w.allowResumeWork } - w.countingWorker.Work() + w.chanWorker.Work() } func TestSleeperTask_WakeupEnqueuesMaxTwice(t *testing.T) { t.Parallel() + ctx := tests.Context(t) - worker := &controllableWorker{awaitWorkStarted: make(chan struct{}), allowResumeWork: make(chan struct{})} + worker := &controllableWorker{chanWorker: chanWorker{ch: make(chan struct{}, 1)}, awaitWorkStarted: make(chan struct{}), allowResumeWork: make(chan struct{})} sleeper := utils.NewSleeperTask(worker) sleeper.WakeUp() @@ -97,22 +105,55 @@ func TestSleeperTask_WakeupEnqueuesMaxTwice(t *testing.T) { worker.ignoreSignals = true worker.allowResumeWork <- struct{}{} - gomega.NewWithT(t).Eventually(worker.getNumJobsPerformed).Should(gomega.Equal(2)) - gomega.NewWithT(t).Consistently(worker.getNumJobsPerformed).Should(gomega.BeNumerically("<", 3)) + for i := 0; i < 2; i++ { + select { + case <-worker.ch: + case <-ctx.Done(): + t.Error("timed out waiting for work to be performed") + } + } + + if !t.Failed() { + select { + case <-worker.ch: + t.Errorf("unexpected work performed") + case <-time.After(time.Second): + } + } + require.NoError(t, sleeper.Stop()) } func TestSleeperTask_StopWaitsUntilWorkFinishes(t *testing.T) { t.Parallel() - worker := &controllableWorker{awaitWorkStarted: make(chan struct{}), allowResumeWork: make(chan struct{})} + worker := &controllableWorker{chanWorker: chanWorker{ch: make(chan struct{}, 1)}, awaitWorkStarted: make(chan struct{}), allowResumeWork: make(chan struct{})} sleeper := utils.NewSleeperTask(worker) sleeper.WakeUp() <-worker.awaitWorkStarted - require.Equal(t, 0, worker.getNumJobsPerformed()) + + select { + case <-worker.ch: + t.Error("work was performed when none was expected") + assert.NoError(t, sleeper.Stop()) + return + default: + } + worker.allowResumeWork <- struct{}{} require.NoError(t, sleeper.Stop()) - require.Equal(t, worker.getNumJobsPerformed(), 1) + + select { + case <-worker.ch: + default: + t.Fatal("work should have been performed") + } + + select { + case <-worker.ch: + t.Fatal("extra work was performed") + default: + } } From 27df2e1acc2f5c7dc0dd5465dfb876146678fbae Mon Sep 17 00:00:00 2001 From: Dimitris Date: Thu, 21 Dec 2023 18:05:59 +0200 Subject: [PATCH 11/11] Tidy --- go.mod | 3 --- go.sum | 26 -------------------------- 2 files changed, 29 deletions(-) diff --git a/go.mod b/go.mod index 26144388e..6123b652b 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,6 @@ require ( github.com/linkedin/goavro/v2 v2.12.0 github.com/mitchellh/mapstructure v1.5.0 github.com/mwitkow/grpc-proxy v0.0.0-20230212185441-f345521cb9c9 - github.com/onsi/gomega v1.10.3 github.com/pelletier/go-toml/v2 v2.1.1 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.17.0 @@ -72,10 +71,8 @@ require ( golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 927f42bb6..b6a482b19 100644 --- a/go.sum +++ b/go.sum @@ -88,9 +88,6 @@ github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBF github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -182,7 +179,6 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= @@ -233,17 +229,8 @@ github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/nolag/mapstructure v1.5.1 h1:jEMB2AM8NXEosSMTPXlbycOpBsqttEBh5owT0gJs9/I= github.com/nolag/mapstructure v1.5.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4= -github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= -github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= @@ -377,7 +364,6 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -402,7 +388,6 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= @@ -430,7 +415,6 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -439,9 +423,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -529,7 +511,6 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -628,14 +609,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=