From e9d1794a8a58be3b5fa4b737d9e4c1a02e74ba1b Mon Sep 17 00:00:00 2001 From: Kaloyan Tanev Date: Mon, 29 Apr 2024 19:10:42 +0300 Subject: [PATCH 1/7] Add alpha test beacon tests --- cmd/testbeacon.go | 240 ++++++++++++++++++++++++++++++-- cmd/testbeacon_internal_test.go | 77 +++++++--- cmd/testpeers_internal_test.go | 2 +- 3 files changed, 288 insertions(+), 31 deletions(-) diff --git a/cmd/testbeacon.go b/cmd/testbeacon.go index 372ae1ee4..11550f967 100644 --- a/cmd/testbeacon.go +++ b/cmd/testbeacon.go @@ -4,13 +4,19 @@ package cmd import ( "context" + "encoding/json" + "fmt" "io" + "net/http" + "net/http/httptrace" + "strconv" "time" "github.com/spf13/cobra" "golang.org/x/exp/maps" "github.com/obolnetwork/charon/app/errors" + "github.com/obolnetwork/charon/app/z" ) type testBeaconConfig struct { @@ -18,6 +24,11 @@ type testBeaconConfig struct { Endpoints []string } +const ( + thresholdBeaconPeersAvg = 20 + thresholdBeaconPeersBad = 5 +) + func newTestBeaconCmd(runFunc func(context.Context, io.Writer, testBeaconConfig) error) *cobra.Command { var config testBeaconConfig @@ -48,7 +59,10 @@ func bindTestBeaconFlags(cmd *cobra.Command, config *testBeaconConfig) { func supportedBeaconTestCases() map[testCaseName]func(context.Context, *testBeaconConfig, string) testResult { return map[testCaseName]func(context.Context, *testBeaconConfig, string) testResult{ - {name: "ping", order: 1}: beaconPing, + {name: "ping", order: 1}: beaconPingTest, + {name: "pingMeasure", order: 2}: beaconPingMeasureTest, + {name: "isSynced", order: 3}: beaconIsSyncedTest, + {name: "peerCount", order: 4}: beaconPeerCountTest, } } @@ -181,15 +195,219 @@ func runBeaconTest(ctx context.Context, queuedTestCases []testCaseName, allTestC } } -func beaconPing(ctx context.Context, _ *testBeaconConfig, _ string) testResult { - // TODO(kalo): implement real ping - select { - case <-ctx.Done(): - return testResult{Verdict: testVerdictFail} - default: - return testResult{ - Verdict: testVerdictFail, - Error: errNotImplemented, - } +func beaconPingTest(ctx context.Context, _ *testBeaconConfig, target string) testResult { + testRes := testResult{Name: "Ping"} + + client := http.Client{} + targetEndpoint := fmt.Sprintf("%v/eth/v1/node/health", target) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, targetEndpoint, nil) + if err != nil { + testRes.Verdict = testVerdictFail + testRes.Error = testResultError{err} + + return testRes + } + resp, err := client.Do(req) + if err != nil { + testRes.Verdict = testVerdictFail + testRes.Error = testResultError{err} + + return testRes + } + defer resp.Body.Close() + + if resp.StatusCode > 399 { + testRes.Verdict = testVerdictFail + testRes.Error = testResultError{errors.New("status code %v", z.Int("status_code", resp.StatusCode))} + + return testRes + } + + testRes.Verdict = testVerdictOk + + return testRes +} + +func beaconPingMeasureTest(ctx context.Context, _ *testBeaconConfig, target string) testResult { + testRes := testResult{Name: "PingMeasure"} + + var start time.Time + var firstByte time.Duration + + trace := &httptrace.ClientTrace{ + GotFirstResponseByte: func() { + firstByte = time.Since(start) + }, + } + + start = time.Now() + targetEndpoint := fmt.Sprintf("%v/eth/v1/node/health", target) + req, err := http.NewRequestWithContext(httptrace.WithClientTrace(ctx, trace), http.MethodGet, targetEndpoint, nil) + if err != nil { + testRes.Verdict = testVerdictFail + testRes.Error = testResultError{err} + + return testRes + } + + resp, err := http.DefaultTransport.RoundTrip(req) + if err != nil { + testRes.Verdict = testVerdictFail + testRes.Error = testResultError{err} + + return testRes + } + defer resp.Body.Close() + + if resp.StatusCode > 399 { + testRes.Verdict = testVerdictFail + testRes.Error = testResultError{errors.New("status code %v", z.Int("status_code", resp.StatusCode))} + + return testRes + } + + if firstByte > thresholdMeasureBad { + testRes.Verdict = testVerdictBad + } else if firstByte > thresholdMeasureAvg { + testRes.Verdict = testVerdictAvg + } else { + testRes.Verdict = testVerdictGood + } + testRes.Measurement = Duration{firstByte}.String() + + return testRes +} + +func beaconIsSyncedTest(ctx context.Context, _ *testBeaconConfig, target string) testResult { + testRes := testResult{Name: "isSynced"} + + type isSyncedResponseData struct { + HeadSlot string `json:"head_slot"` + SyncDistance string `json:"sync_distance"` + IsSyncing bool `json:"is_syncing"` + IsOptimistic bool `json:"is_optimistic"` + ElOffline bool `json:"el_offline"` + } + + type isSyncedResponse struct { + Data isSyncedResponseData `json:"data"` } + + client := http.Client{} + targetEndpoint := fmt.Sprintf("%v/eth/v1/node/syncing", target) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, targetEndpoint, nil) + if err != nil { + testRes.Verdict = testVerdictFail + testRes.Error = testResultError{err} + + return testRes + } + resp, err := client.Do(req) + if err != nil { + testRes.Verdict = testVerdictFail + testRes.Error = testResultError{err} + + return testRes + } + + if resp.StatusCode > 399 { + testRes.Verdict = testVerdictFail + testRes.Error = testResultError{errors.New("status code %v", z.Int("status_code", resp.StatusCode))} + + return testRes + } + + b, err := io.ReadAll(resp.Body) + if err != nil { + testRes.Verdict = testVerdictFail + testRes.Error = testResultError{err} + + return testRes + } + defer resp.Body.Close() + + var respUnmarshaled isSyncedResponse + err = json.Unmarshal(b, &respUnmarshaled) + if err != nil { + testRes.Verdict = testVerdictFail + testRes.Error = testResultError{err} + + return testRes + } + + if respUnmarshaled.Data.IsSyncing { + testRes.Verdict = testVerdictFail + + return testRes + } + + testRes.Verdict = testVerdictOk + + return testRes +} + +func beaconPeerCountTest(ctx context.Context, _ *testBeaconConfig, target string) testResult { + testRes := testResult{Name: "peerCount"} + + type peerCountResponseMeta struct { + Count int `json:"count"` + } + + type peerCountResponse struct { + Meta peerCountResponseMeta `json:"meta"` + } + + client := http.Client{} + targetEndpoint := fmt.Sprintf("%v/eth/v1/node/peers?state=connected", target) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, targetEndpoint, nil) + if err != nil { + testRes.Verdict = testVerdictFail + testRes.Error = testResultError{err} + + return testRes + } + resp, err := client.Do(req) + if err != nil { + testRes.Verdict = testVerdictFail + testRes.Error = testResultError{err} + + return testRes + } + + if resp.StatusCode > 399 { + testRes.Verdict = testVerdictFail + testRes.Error = testResultError{errors.New("status code %v", z.Int("status_code", resp.StatusCode))} + + return testRes + } + + b, err := io.ReadAll(resp.Body) + if err != nil { + testRes.Verdict = testVerdictFail + testRes.Error = testResultError{err} + + return testRes + } + defer resp.Body.Close() + + var respUnmarshaled peerCountResponse + err = json.Unmarshal(b, &respUnmarshaled) + if err != nil { + testRes.Verdict = testVerdictFail + testRes.Error = testResultError{err} + + return testRes + } + + testRes.Measurement = strconv.Itoa(respUnmarshaled.Meta.Count) + + if respUnmarshaled.Meta.Count < thresholdBeaconPeersBad { + testRes.Verdict = testVerdictBad + } else if respUnmarshaled.Meta.Count < thresholdBeaconPeersAvg { + testRes.Verdict = testVerdictAvg + } else { + testRes.Verdict = testVerdictGood + } + + return testRes } diff --git a/cmd/testbeacon_internal_test.go b/cmd/testbeacon_internal_test.go index 211239fe9..0bc13e389 100644 --- a/cmd/testbeacon_internal_test.go +++ b/cmd/testbeacon_internal_test.go @@ -11,6 +11,8 @@ import ( "time" "github.com/stretchr/testify/require" + + "github.com/obolnetwork/charon/app/errors" ) //go:generate go test . -run=TestBeaconTest -update @@ -33,12 +35,22 @@ func TestBeaconTest(t *testing.T) { TestCases: nil, Timeout: time.Minute, }, - Endpoints: []string{"beacon-endpoint-1", "beacon-endpoint-2"}, + Endpoints: []string{"http://localhost:8080", "http://localhost:8081"}, }, expected: testCategoryResult{ Targets: map[string][]testResult{ - "beacon-endpoint-1": {{Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: errNotImplemented}}, - "beacon-endpoint-2": {{Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: errNotImplemented}}, + "http://localhost:8080": { + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8080: connect: connection refused`)}}, + {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8080: connect: connection refused`)}}, + {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`Get "http://localhost:8080/eth/v1/node/syncing": dial tcp 127.0.0.1:8080: connect: connection refused`)}}, + {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`Get "http://localhost:8080/eth/v1/node/peers?state=connected": dial tcp 127.0.0.1:8080: connect: connection refused`)}}, + }, + "http://localhost:8081": { + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8081: connect: connection refused`)}}, + {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8081: connect: connection refused`)}}, + {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`Get "http://localhost:8081/eth/v1/node/syncing": dial tcp 127.0.0.1:8081: connect: connection refused`)}}, + {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`Get "http://localhost:8081/eth/v1/node/peers?state=connected": dial tcp 127.0.0.1:8081: connect: connection refused`)}}, + }, }, }, expectedErr: "", @@ -52,12 +64,16 @@ func TestBeaconTest(t *testing.T) { TestCases: nil, Timeout: time.Nanosecond, }, - Endpoints: []string{"beacon-endpoint-1", "beacon-endpoint-2"}, + Endpoints: []string{"http://localhost:8080", "http://localhost:8081"}, }, expected: testCategoryResult{ Targets: map[string][]testResult{ - "beacon-endpoint-1": {{Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: errTimeoutInterrupted}}, - "beacon-endpoint-2": {{Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: errTimeoutInterrupted}}, + "http://localhost:8080": { + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: errTimeoutInterrupted}, + }, + "http://localhost:8081": { + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: errTimeoutInterrupted}, + }, }, }, expectedErr: "", @@ -71,12 +87,22 @@ func TestBeaconTest(t *testing.T) { TestCases: nil, Timeout: time.Minute, }, - Endpoints: []string{"beacon-endpoint-1", "beacon-endpoint-2"}, + Endpoints: []string{"http://localhost:8080", "http://localhost:8081"}, }, expected: testCategoryResult{ Targets: map[string][]testResult{ - "beacon-endpoint-1": {{Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: errNotImplemented}}, - "beacon-endpoint-2": {{Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: errNotImplemented}}, + "http://localhost:8080": { + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8080: connect: connection refused`)}}, + {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8080: connect: connection refused`)}}, + {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`Get "http://localhost:8080/eth/v1/node/syncing": dial tcp 127.0.0.1:8080: connect: connection refused`)}}, + {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`Get "http://localhost:8080/eth/v1/node/peers?state=connected": dial tcp 127.0.0.1:8080: connect: connection refused`)}}, + }, + "http://localhost:8081": { + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8081: connect: connection refused`)}}, + {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8081: connect: connection refused`)}}, + {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`Get "http://localhost:8081/eth/v1/node/syncing": dial tcp 127.0.0.1:8081: connect: connection refused`)}}, + {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`Get "http://localhost:8081/eth/v1/node/peers?state=connected": dial tcp 127.0.0.1:8081: connect: connection refused`)}}, + }, }, }, expectedErr: "", @@ -90,7 +116,7 @@ func TestBeaconTest(t *testing.T) { TestCases: []string{"notSupportedTest"}, Timeout: time.Minute, }, - Endpoints: []string{"beacon-endpoint-1", "beacon-endpoint-2"}, + Endpoints: []string{"http://localhost:8080", "http://localhost:8081"}, }, expected: testCategoryResult{}, expectedErr: "test case not supported", @@ -104,17 +130,20 @@ func TestBeaconTest(t *testing.T) { TestCases: []string{"ping"}, Timeout: time.Minute, }, - Endpoints: []string{"beacon-endpoint-1", "beacon-endpoint-2"}, + Endpoints: []string{"http://localhost:8080", "http://localhost:8081"}, }, expected: testCategoryResult{ Targets: map[string][]testResult{ - "beacon-endpoint-1": {{Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: errNotImplemented}}, - "beacon-endpoint-2": {{Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: errNotImplemented}}, + "http://localhost:8080": { + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8080: connect: connection refused`)}}, + }, + "http://localhost:8081": { + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8081: connect: connection refused`)}}, + }, }, }, expectedErr: "", }, - { name: "write to file", config: testBeaconConfig{ @@ -124,15 +153,25 @@ func TestBeaconTest(t *testing.T) { TestCases: nil, Timeout: time.Minute, }, - Endpoints: []string{"beacon-endpoint-1", "beacon-endpoint-2"}, + Endpoints: []string{"http://localhost:8080", "http://localhost:8081"}, }, expected: testCategoryResult{ - CategoryName: "beacon", Targets: map[string][]testResult{ - "beacon-endpoint-1": {{Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: errNotImplemented}}, - "beacon-endpoint-2": {{Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: errNotImplemented}}, + "http://localhost:8080": { + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8080: connect: connection refused`)}}, + {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8080: connect: connection refused`)}}, + {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`Get "http://localhost:8080/eth/v1/node/syncing": dial tcp 127.0.0.1:8080: connect: connection refused`)}}, + {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`Get "http://localhost:8080/eth/v1/node/peers?state=connected": dial tcp 127.0.0.1:8080: connect: connection refused`)}}, + }, + "http://localhost:8081": { + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8081: connect: connection refused`)}}, + {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8081: connect: connection refused`)}}, + {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`Get "http://localhost:8081/eth/v1/node/syncing": dial tcp 127.0.0.1:8081: connect: connection refused`)}}, + {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`Get "http://localhost:8081/eth/v1/node/peers?state=connected": dial tcp 127.0.0.1:8081: connect: connection refused`)}}, + }, }, - Score: categoryScoreC, + Score: categoryScoreC, + CategoryName: "beacon", }, expectedErr: "", cleanup: func(t *testing.T, p string) { diff --git a/cmd/testpeers_internal_test.go b/cmd/testpeers_internal_test.go index 3d1482c71..dd91f9a66 100644 --- a/cmd/testpeers_internal_test.go +++ b/cmd/testpeers_internal_test.go @@ -318,7 +318,7 @@ func testWriteFile(t *testing.T, expectedRes testCategoryResult, path string) { require.Equal(t, expectedRes.Targets[targetName][idx].Measurement, testRes.Measurement) require.Equal(t, expectedRes.Targets[targetName][idx].Suggestion, testRes.Suggestion) if expectedRes.Targets[targetName][idx].Error.error != nil { - require.ErrorContains(t, expectedRes.Targets[targetName][idx].Error.error, testRes.Error.error.Error()) + require.ErrorContains(t, testRes.Error.error, expectedRes.Targets[targetName][idx].Error.error.Error()) } } } From 70704d8dc78abef00f4533b78f743fff8acf8f39 Mon Sep 17 00:00:00 2001 From: Kaloyan Tanev Date: Mon, 29 Apr 2024 20:22:37 +0300 Subject: [PATCH 2/7] Improve test beacon node testing --- cmd/testbeacon_internal_test.go | 139 ++++++++++++++++++++++---------- testutil/helpers.go | 14 ++++ 2 files changed, 110 insertions(+), 43 deletions(-) diff --git a/cmd/testbeacon_internal_test.go b/cmd/testbeacon_internal_test.go index 0bc13e389..9119236c6 100644 --- a/cmd/testbeacon_internal_test.go +++ b/cmd/testbeacon_internal_test.go @@ -5,7 +5,10 @@ package cmd import ( "bytes" "context" + "fmt" "io" + "net/http" + "net/http/httptest" "os" "testing" "time" @@ -13,12 +16,22 @@ import ( "github.com/stretchr/testify/require" "github.com/obolnetwork/charon/app/errors" + "github.com/obolnetwork/charon/testutil" ) //go:generate go test . -run=TestBeaconTest -update -//nolint:dupl // code is marked as duplicate currently, as we are testing the same test skeleton, ignore for now func TestBeaconTest(t *testing.T) { + port1 := testutil.GetFreePort(t) + endpoint1 := fmt.Sprintf("http://localhost:%v", port1) + ip1 := fmt.Sprintf("127.0.0.1:%v", port1) + port2 := testutil.GetFreePort(t) + endpoint2 := fmt.Sprintf("http://localhost:%v", port2) + ip2 := fmt.Sprintf("127.0.0.1:%v", port2) + + mockedBeaconNode := StartHealthyMockedBeaconNode(t) + defer mockedBeaconNode.Close() + tests := []struct { name string config testBeaconConfig @@ -35,21 +48,44 @@ func TestBeaconTest(t *testing.T) { TestCases: nil, Timeout: time.Minute, }, - Endpoints: []string{"http://localhost:8080", "http://localhost:8081"}, + Endpoints: []string{mockedBeaconNode.URL}, }, expected: testCategoryResult{ Targets: map[string][]testResult{ - "http://localhost:8080": { - {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8080: connect: connection refused`)}}, - {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8080: connect: connection refused`)}}, - {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`Get "http://localhost:8080/eth/v1/node/syncing": dial tcp 127.0.0.1:8080: connect: connection refused`)}}, - {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`Get "http://localhost:8080/eth/v1/node/peers?state=connected": dial tcp 127.0.0.1:8080: connect: connection refused`)}}, + mockedBeaconNode.URL: { + {Name: "ping", Verdict: testVerdictOk, Measurement: "", Suggestion: "", Error: testResultError{}}, + {Name: "pingMeasure", Verdict: testVerdictGood, Measurement: "", Suggestion: "", Error: testResultError{}}, + {Name: "isSynced", Verdict: testVerdictOk, Measurement: "", Suggestion: "", Error: testResultError{}}, + {Name: "peerCount", Verdict: testVerdictGood, Measurement: "", Suggestion: "", Error: testResultError{}}, }, - "http://localhost:8081": { - {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8081: connect: connection refused`)}}, - {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8081: connect: connection refused`)}}, - {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`Get "http://localhost:8081/eth/v1/node/syncing": dial tcp 127.0.0.1:8081: connect: connection refused`)}}, - {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`Get "http://localhost:8081/eth/v1/node/peers?state=connected": dial tcp 127.0.0.1:8081: connect: connection refused`)}}, + }, + }, + expectedErr: "", + }, + { + name: "connection refused", + config: testBeaconConfig{ + testConfig: testConfig{ + OutputToml: "", + Quiet: false, + TestCases: nil, + Timeout: time.Minute, + }, + Endpoints: []string{endpoint1, endpoint2}, + }, + expected: testCategoryResult{ + Targets: map[string][]testResult{ + endpoint1: { + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip1))}}, + {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip1))}}, + {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`Get "%v/eth/v1/node/syncing": dial tcp %v: connect: connection refused`, endpoint1, ip1))}}, + {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`Get "%v/eth/v1/node/peers?state=connected": dial tcp %v: connect: connection refused`, endpoint1, ip1))}}, + }, + endpoint2: { + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip2))}}, + {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip2))}}, + {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`Get "%v/eth/v1/node/syncing": dial tcp %v: connect: connection refused`, endpoint2, ip2))}}, + {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`Get "%v/eth/v1/node/peers?state=connected": dial tcp %v: connect: connection refused`, endpoint2, ip2))}}, }, }, }, @@ -64,14 +100,14 @@ func TestBeaconTest(t *testing.T) { TestCases: nil, Timeout: time.Nanosecond, }, - Endpoints: []string{"http://localhost:8080", "http://localhost:8081"}, + Endpoints: []string{endpoint1, endpoint2}, }, expected: testCategoryResult{ Targets: map[string][]testResult{ - "http://localhost:8080": { + endpoint1: { {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: errTimeoutInterrupted}, }, - "http://localhost:8081": { + endpoint2: { {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: errTimeoutInterrupted}, }, }, @@ -87,21 +123,21 @@ func TestBeaconTest(t *testing.T) { TestCases: nil, Timeout: time.Minute, }, - Endpoints: []string{"http://localhost:8080", "http://localhost:8081"}, + Endpoints: []string{endpoint1, endpoint2}, }, expected: testCategoryResult{ Targets: map[string][]testResult{ - "http://localhost:8080": { - {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8080: connect: connection refused`)}}, - {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8080: connect: connection refused`)}}, - {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`Get "http://localhost:8080/eth/v1/node/syncing": dial tcp 127.0.0.1:8080: connect: connection refused`)}}, - {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`Get "http://localhost:8080/eth/v1/node/peers?state=connected": dial tcp 127.0.0.1:8080: connect: connection refused`)}}, + endpoint1: { + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip1))}}, + {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip1))}}, + {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`Get "%v/eth/v1/node/syncing": dial tcp %v: connect: connection refused`, endpoint1, ip1))}}, + {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`Get "%v/eth/v1/node/peers?state=connected": dial tcp %v: connect: connection refused`, endpoint1, ip1))}}, }, - "http://localhost:8081": { - {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8081: connect: connection refused`)}}, - {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8081: connect: connection refused`)}}, - {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`Get "http://localhost:8081/eth/v1/node/syncing": dial tcp 127.0.0.1:8081: connect: connection refused`)}}, - {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`Get "http://localhost:8081/eth/v1/node/peers?state=connected": dial tcp 127.0.0.1:8081: connect: connection refused`)}}, + endpoint2: { + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip2))}}, + {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip2))}}, + {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`Get "%v/eth/v1/node/syncing": dial tcp %v: connect: connection refused`, endpoint2, ip2))}}, + {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`Get "%v/eth/v1/node/peers?state=connected": dial tcp %v: connect: connection refused`, endpoint2, ip2))}}, }, }, }, @@ -116,7 +152,7 @@ func TestBeaconTest(t *testing.T) { TestCases: []string{"notSupportedTest"}, Timeout: time.Minute, }, - Endpoints: []string{"http://localhost:8080", "http://localhost:8081"}, + Endpoints: []string{endpoint1, endpoint2}, }, expected: testCategoryResult{}, expectedErr: "test case not supported", @@ -130,15 +166,15 @@ func TestBeaconTest(t *testing.T) { TestCases: []string{"ping"}, Timeout: time.Minute, }, - Endpoints: []string{"http://localhost:8080", "http://localhost:8081"}, + Endpoints: []string{endpoint1, endpoint2}, }, expected: testCategoryResult{ Targets: map[string][]testResult{ - "http://localhost:8080": { - {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8080: connect: connection refused`)}}, + endpoint1: { + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip1))}}, }, - "http://localhost:8081": { - {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8081: connect: connection refused`)}}, + endpoint2: { + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip2))}}, }, }, }, @@ -153,21 +189,21 @@ func TestBeaconTest(t *testing.T) { TestCases: nil, Timeout: time.Minute, }, - Endpoints: []string{"http://localhost:8080", "http://localhost:8081"}, + Endpoints: []string{endpoint1, endpoint2}, }, expected: testCategoryResult{ Targets: map[string][]testResult{ - "http://localhost:8080": { - {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8080: connect: connection refused`)}}, - {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8080: connect: connection refused`)}}, - {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`Get "http://localhost:8080/eth/v1/node/syncing": dial tcp 127.0.0.1:8080: connect: connection refused`)}}, - {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`Get "http://localhost:8080/eth/v1/node/peers?state=connected": dial tcp 127.0.0.1:8080: connect: connection refused`)}}, + endpoint1: { + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip1))}}, + {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip1))}}, + {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`Get "%v/eth/v1/node/syncing": dial tcp %v: connect: connection refused`, endpoint1, ip1))}}, + {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`Get "%v/eth/v1/node/peers?state=connected": dial tcp %v: connect: connection refused`, endpoint1, ip1))}}, }, - "http://localhost:8081": { - {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8081: connect: connection refused`)}}, - {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`dial tcp 127.0.0.1:8081: connect: connection refused`)}}, - {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`Get "http://localhost:8081/eth/v1/node/syncing": dial tcp 127.0.0.1:8081: connect: connection refused`)}}, - {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(`Get "http://localhost:8081/eth/v1/node/peers?state=connected": dial tcp 127.0.0.1:8081: connect: connection refused`)}}, + endpoint2: { + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip2))}}, + {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip2))}}, + {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`Get "%v/eth/v1/node/syncing": dial tcp %v: connect: connection refused`, endpoint2, ip2))}}, + {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`Get "%v/eth/v1/node/peers?state=connected": dial tcp %v: connect: connection refused`, endpoint2, ip2))}}, }, }, Score: categoryScoreC, @@ -211,6 +247,23 @@ func TestBeaconTest(t *testing.T) { } } +func StartHealthyMockedBeaconNode(t *testing.T) *httptest.Server { + t.Helper() + + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/eth/v1/node/health": + case "/eth/v1/node/syncing": + _, err := w.Write([]byte(`{"data":{"is_syncing":false}}`)) + require.NoError(t, err) + case "/eth/v1/node/peers": + _, err := w.Write([]byte(`{"meta":{"count":500}}`)) + require.NoError(t, err) + } + w.WriteHeader(http.StatusOK) + })) +} + func TestBeaconTestFlags(t *testing.T) { tests := []struct { name string diff --git a/testutil/helpers.go b/testutil/helpers.go index 96c01ac61..553e05d4b 100644 --- a/testutil/helpers.go +++ b/testutil/helpers.go @@ -3,6 +3,7 @@ package testutil import ( + "net" "sync" "testing" @@ -41,3 +42,16 @@ func NewTCPNodeCallback(t *testing.T, protocols ...protocol.ID) func(host host.H tcpNodes = append(tcpNodes, tcpNode) } } + +// GetFreePort returns a free port on the machine on which the test is ran. +func GetFreePort(t *testing.T) int { + t.Helper() + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + require.NoError(t, err) + l, err := net.ListenTCP("tcp", addr) + require.NoError(t, err) + defer l.Close() + port := l.Addr().(*net.TCPAddr).Port + + return port +} From 38369ccd2261c6363fb7db2554a1d3c399f009b0 Mon Sep 17 00:00:00 2001 From: Kaloyan Tanev Date: Mon, 29 Apr 2024 23:34:02 +0300 Subject: [PATCH 3/7] Add failedTestResult util function --- cmd/test.go | 7 ++++ cmd/testbeacon.go | 81 ++++++++++------------------------------------- 2 files changed, 23 insertions(+), 65 deletions(-) diff --git a/cmd/test.go b/cmd/test.go index bd7d0e3d5..782674c08 100644 --- a/cmd/test.go +++ b/cmd/test.go @@ -94,6 +94,13 @@ type testResult struct { Error testResultError } +func failedTestResult(testRes testResult, err error) testResult { + testRes.Verdict = testVerdictFail + testRes.Error = testResultError{err} + + return testRes +} + func (s *testResultError) UnmarshalText(data []byte) error { s.error = errors.New(string(data)) return nil diff --git a/cmd/testbeacon.go b/cmd/testbeacon.go index 11550f967..4d3c37365 100644 --- a/cmd/testbeacon.go +++ b/cmd/testbeacon.go @@ -202,25 +202,16 @@ func beaconPingTest(ctx context.Context, _ *testBeaconConfig, target string) tes targetEndpoint := fmt.Sprintf("%v/eth/v1/node/health", target) req, err := http.NewRequestWithContext(ctx, http.MethodGet, targetEndpoint, nil) if err != nil { - testRes.Verdict = testVerdictFail - testRes.Error = testResultError{err} - - return testRes + return failedTestResult(testRes, err) } resp, err := client.Do(req) if err != nil { - testRes.Verdict = testVerdictFail - testRes.Error = testResultError{err} - - return testRes + return failedTestResult(testRes, err) } defer resp.Body.Close() if resp.StatusCode > 399 { - testRes.Verdict = testVerdictFail - testRes.Error = testResultError{errors.New("status code %v", z.Int("status_code", resp.StatusCode))} - - return testRes + return failedTestResult(testRes, errors.New("status code %v", z.Int("status_code", resp.StatusCode))) } testRes.Verdict = testVerdictOk @@ -244,26 +235,17 @@ func beaconPingMeasureTest(ctx context.Context, _ *testBeaconConfig, target stri targetEndpoint := fmt.Sprintf("%v/eth/v1/node/health", target) req, err := http.NewRequestWithContext(httptrace.WithClientTrace(ctx, trace), http.MethodGet, targetEndpoint, nil) if err != nil { - testRes.Verdict = testVerdictFail - testRes.Error = testResultError{err} - - return testRes + return failedTestResult(testRes, err) } resp, err := http.DefaultTransport.RoundTrip(req) if err != nil { - testRes.Verdict = testVerdictFail - testRes.Error = testResultError{err} - - return testRes + return failedTestResult(testRes, err) } defer resp.Body.Close() if resp.StatusCode > 399 { - testRes.Verdict = testVerdictFail - testRes.Error = testResultError{errors.New("status code %v", z.Int("status_code", resp.StatusCode))} - - return testRes + return failedTestResult(testRes, errors.New("status code %v", z.Int("status_code", resp.StatusCode))) } if firstByte > thresholdMeasureBad { @@ -297,47 +279,31 @@ func beaconIsSyncedTest(ctx context.Context, _ *testBeaconConfig, target string) targetEndpoint := fmt.Sprintf("%v/eth/v1/node/syncing", target) req, err := http.NewRequestWithContext(ctx, http.MethodGet, targetEndpoint, nil) if err != nil { - testRes.Verdict = testVerdictFail - testRes.Error = testResultError{err} - - return testRes + return failedTestResult(testRes, err) } resp, err := client.Do(req) if err != nil { - testRes.Verdict = testVerdictFail - testRes.Error = testResultError{err} - - return testRes + return failedTestResult(testRes, err) } if resp.StatusCode > 399 { - testRes.Verdict = testVerdictFail - testRes.Error = testResultError{errors.New("status code %v", z.Int("status_code", resp.StatusCode))} - - return testRes + return failedTestResult(testRes, errors.New("status code %v", z.Int("status_code", resp.StatusCode))) } b, err := io.ReadAll(resp.Body) if err != nil { - testRes.Verdict = testVerdictFail - testRes.Error = testResultError{err} - - return testRes + return failedTestResult(testRes, err) } defer resp.Body.Close() var respUnmarshaled isSyncedResponse err = json.Unmarshal(b, &respUnmarshaled) if err != nil { - testRes.Verdict = testVerdictFail - testRes.Error = testResultError{err} - - return testRes + return failedTestResult(testRes, err) } if respUnmarshaled.Data.IsSyncing { testRes.Verdict = testVerdictFail - return testRes } @@ -361,42 +327,27 @@ func beaconPeerCountTest(ctx context.Context, _ *testBeaconConfig, target string targetEndpoint := fmt.Sprintf("%v/eth/v1/node/peers?state=connected", target) req, err := http.NewRequestWithContext(ctx, http.MethodGet, targetEndpoint, nil) if err != nil { - testRes.Verdict = testVerdictFail - testRes.Error = testResultError{err} - - return testRes + return failedTestResult(testRes, err) } resp, err := client.Do(req) if err != nil { - testRes.Verdict = testVerdictFail - testRes.Error = testResultError{err} - - return testRes + return failedTestResult(testRes, err) } if resp.StatusCode > 399 { - testRes.Verdict = testVerdictFail - testRes.Error = testResultError{errors.New("status code %v", z.Int("status_code", resp.StatusCode))} - - return testRes + return failedTestResult(testRes, errors.New("status code %v", z.Int("status_code", resp.StatusCode))) } b, err := io.ReadAll(resp.Body) if err != nil { - testRes.Verdict = testVerdictFail - testRes.Error = testResultError{err} - - return testRes + return failedTestResult(testRes, err) } defer resp.Body.Close() var respUnmarshaled peerCountResponse err = json.Unmarshal(b, &respUnmarshaled) if err != nil { - testRes.Verdict = testVerdictFail - testRes.Error = testResultError{err} - - return testRes + return failedTestResult(testRes, err) } testRes.Measurement = strconv.Itoa(respUnmarshaled.Meta.Count) From dc6c55a3f34286f780a0173c8eb7d857eadc6d45 Mon Sep 17 00:00:00 2001 From: Kaloyan Tanev Date: Tue, 30 Apr 2024 00:18:56 +0300 Subject: [PATCH 4/7] Consistency with peers tests --- cmd/testbeacon.go | 96 ++++++++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 43 deletions(-) diff --git a/cmd/testbeacon.go b/cmd/testbeacon.go index 4d3c37365..21ee7cf59 100644 --- a/cmd/testbeacon.go +++ b/cmd/testbeacon.go @@ -14,6 +14,7 @@ import ( "github.com/spf13/cobra" "golang.org/x/exp/maps" + "golang.org/x/sync/errgroup" "github.com/obolnetwork/charon/app/errors" "github.com/obolnetwork/charon/app/z" @@ -24,6 +25,8 @@ type testBeaconConfig struct { Endpoints []string } +type testCaseBeacon func(context.Context, *testBeaconConfig, string) testResult + const ( thresholdBeaconPeersAvg = 20 thresholdBeaconPeersBad = 5 @@ -57,8 +60,8 @@ func bindTestBeaconFlags(cmd *cobra.Command, config *testBeaconConfig) { mustMarkFlagRequired(cmd, endpoints) } -func supportedBeaconTestCases() map[testCaseName]func(context.Context, *testBeaconConfig, string) testResult { - return map[testCaseName]func(context.Context, *testBeaconConfig, string) testResult{ +func supportedBeaconTestCases() map[testCaseName]testCaseBeacon { + return map[testCaseName]testCaseBeacon{ {name: "ping", order: 1}: beaconPingTest, {name: "pingMeasure", order: 2}: beaconPingMeasureTest, {name: "isSynced", order: 3}: beaconIsSyncedTest, @@ -74,21 +77,20 @@ func runTestBeacon(ctx context.Context, w io.Writer, cfg testBeaconConfig) (err } sortTests(queuedTests) - parentCtx := ctx - if parentCtx == nil { - parentCtx = context.Background() + if ctx == nil { + ctx = context.Background() } - timeoutCtx, cancel := context.WithTimeout(parentCtx, cfg.Timeout) + timeoutCtx, cancel := context.WithTimeout(ctx, cfg.Timeout) defer cancel() - resultsCh := make(chan map[string][]testResult) + testResultsChan := make(chan map[string][]testResult) testResults := make(map[string][]testResult) startTime := time.Now() // run test suite for all beacon nodes - go testAllBeacons(timeoutCtx, queuedTests, testCases, cfg, resultsCh) + go testAllBeacons(timeoutCtx, queuedTests, testCases, cfg, testResultsChan) - for result := range resultsCh { + for result := range testResultsChan { maps.Copy(testResults, result) } @@ -127,63 +129,71 @@ func runTestBeacon(ctx context.Context, w io.Writer, cfg testBeaconConfig) (err return nil } -func testAllBeacons(ctx context.Context, queuedTestCases []testCaseName, allTestCases map[testCaseName]func(context.Context, *testBeaconConfig, string) testResult, cfg testBeaconConfig, resCh chan map[string][]testResult) { - defer close(resCh) +func testAllBeacons(ctx context.Context, queuedTestCases []testCaseName, allTestCases map[testCaseName]testCaseBeacon, conf testBeaconConfig, allBeaconsResCh chan map[string][]testResult) { + defer close(allBeaconsResCh) // run tests for all beacon nodes - res := make(map[string][]testResult) - chs := []chan map[string][]testResult{} - for _, enr := range cfg.Endpoints { - ch := make(chan map[string][]testResult) - chs = append(chs, ch) - go testSingleBeacon(ctx, queuedTestCases, allTestCases, cfg, enr, ch) - } - - for _, ch := range chs { - for { - // we are checking for context done (timeout) inside the go routine - result, ok := <-ch - if !ok { - break - } - maps.Copy(res, result) + allBeaconsRes := make(map[string][]testResult) + singleBeaconResCh := make(chan map[string][]testResult) + group, _ := errgroup.WithContext(ctx) + + for _, endpoint := range conf.Endpoints { + currEndpoint := endpoint // TODO: can be removed after go1.22 version bump + group.Go(func() error { + return testSingleBeacon(ctx, queuedTestCases, allTestCases, conf, currEndpoint, singleBeaconResCh) + }) + } + + doneReading := make(chan bool) + go func() { + for singlePeerRes := range singleBeaconResCh { + maps.Copy(allBeaconsRes, singlePeerRes) } + doneReading <- true + }() + + err := group.Wait() + if err != nil { + return } + close(singleBeaconResCh) + <-doneReading - resCh <- res + allBeaconsResCh <- allBeaconsRes } -func testSingleBeacon(ctx context.Context, queuedTestCases []testCaseName, allTestCases map[testCaseName]func(context.Context, *testBeaconConfig, string) testResult, cfg testBeaconConfig, target string, resCh chan map[string][]testResult) { - defer close(resCh) - ch := make(chan testResult) - res := []testResult{} - // run all beacon tests for a beacon node, pushing each completed test to the channel until all are complete or timeout occurs - go runBeaconTest(ctx, queuedTestCases, allTestCases, cfg, target, ch) +func testSingleBeacon(ctx context.Context, queuedTestCases []testCaseName, allTestCases map[testCaseName]testCaseBeacon, cfg testBeaconConfig, target string, resCh chan map[string][]testResult) error { + singkeTestResCh := make(chan testResult) + allTestRes := []testResult{} + // run all beacon tests for a beacon node, pushing each completed test to the channel until all are complete or timeout occurs + go runBeaconTest(ctx, queuedTestCases, allTestCases, cfg, target, singkeTestResCh) testCounter := 0 finished := false for !finished { - var name string + var testName string select { case <-ctx.Done(): - name = queuedTestCases[testCounter].name - res = append(res, testResult{Name: name, Verdict: testVerdictFail, Error: errTimeoutInterrupted}) + testName = queuedTestCases[testCounter].name + allTestRes = append(allTestRes, testResult{Name: testName, Verdict: testVerdictFail, Error: errTimeoutInterrupted}) finished = true - case result, ok := <-ch: + case result, ok := <-singkeTestResCh: if !ok { finished = true break } - name = queuedTestCases[testCounter].name + testName = queuedTestCases[testCounter].name testCounter++ - result.Name = name - res = append(res, result) + result.Name = testName + allTestRes = append(allTestRes, result) } } - resCh <- map[string][]testResult{target: res} + resCh <- map[string][]testResult{target: allTestRes} + + return nil } -func runBeaconTest(ctx context.Context, queuedTestCases []testCaseName, allTestCases map[testCaseName]func(context.Context, *testBeaconConfig, string) testResult, cfg testBeaconConfig, target string, ch chan testResult) { +func runBeaconTest(ctx context.Context, queuedTestCases []testCaseName, allTestCases map[testCaseName]testCaseBeacon, cfg testBeaconConfig, target string, ch chan testResult) { defer close(ch) for _, t := range queuedTestCases { select { From fcb01fd5e06384304d3fef6682f15ca100e224b0 Mon Sep 17 00:00:00 2001 From: Kaloyan Tanev Date: Tue, 30 Apr 2024 00:28:02 +0300 Subject: [PATCH 5/7] Fix beacon test localhost resolving --- cmd/testbeacon_internal_test.go | 54 ++++++++++++++++----------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/cmd/testbeacon_internal_test.go b/cmd/testbeacon_internal_test.go index 9119236c6..6ad3fe919 100644 --- a/cmd/testbeacon_internal_test.go +++ b/cmd/testbeacon_internal_test.go @@ -24,10 +24,8 @@ import ( func TestBeaconTest(t *testing.T) { port1 := testutil.GetFreePort(t) endpoint1 := fmt.Sprintf("http://localhost:%v", port1) - ip1 := fmt.Sprintf("127.0.0.1:%v", port1) port2 := testutil.GetFreePort(t) endpoint2 := fmt.Sprintf("http://localhost:%v", port2) - ip2 := fmt.Sprintf("127.0.0.1:%v", port2) mockedBeaconNode := StartHealthyMockedBeaconNode(t) defer mockedBeaconNode.Close() @@ -76,16 +74,16 @@ func TestBeaconTest(t *testing.T) { expected: testCategoryResult{ Targets: map[string][]testResult{ endpoint1: { - {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip1))}}, - {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip1))}}, - {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`Get "%v/eth/v1/node/syncing": dial tcp %v: connect: connection refused`, endpoint1, ip1))}}, - {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`Get "%v/eth/v1/node/peers?state=connected": dial tcp %v: connect: connection refused`, endpoint1, ip1))}}, + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port1))}}, + {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port1))}}, + {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port1))}}, + {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port1))}}, }, endpoint2: { - {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip2))}}, - {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip2))}}, - {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`Get "%v/eth/v1/node/syncing": dial tcp %v: connect: connection refused`, endpoint2, ip2))}}, - {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`Get "%v/eth/v1/node/peers?state=connected": dial tcp %v: connect: connection refused`, endpoint2, ip2))}}, + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port2))}}, + {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port2))}}, + {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port2))}}, + {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port2))}}, }, }, }, @@ -128,16 +126,16 @@ func TestBeaconTest(t *testing.T) { expected: testCategoryResult{ Targets: map[string][]testResult{ endpoint1: { - {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip1))}}, - {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip1))}}, - {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`Get "%v/eth/v1/node/syncing": dial tcp %v: connect: connection refused`, endpoint1, ip1))}}, - {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`Get "%v/eth/v1/node/peers?state=connected": dial tcp %v: connect: connection refused`, endpoint1, ip1))}}, + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port1))}}, + {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port1))}}, + {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port1))}}, + {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port1))}}, }, endpoint2: { - {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip2))}}, - {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip2))}}, - {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`Get "%v/eth/v1/node/syncing": dial tcp %v: connect: connection refused`, endpoint2, ip2))}}, - {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`Get "%v/eth/v1/node/peers?state=connected": dial tcp %v: connect: connection refused`, endpoint2, ip2))}}, + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port2))}}, + {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port2))}}, + {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port2))}}, + {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port2))}}, }, }, }, @@ -171,10 +169,10 @@ func TestBeaconTest(t *testing.T) { expected: testCategoryResult{ Targets: map[string][]testResult{ endpoint1: { - {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip1))}}, + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port1))}}, }, endpoint2: { - {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip2))}}, + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port2))}}, }, }, }, @@ -194,16 +192,16 @@ func TestBeaconTest(t *testing.T) { expected: testCategoryResult{ Targets: map[string][]testResult{ endpoint1: { - {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip1))}}, - {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip1))}}, - {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`Get "%v/eth/v1/node/syncing": dial tcp %v: connect: connection refused`, endpoint1, ip1))}}, - {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`Get "%v/eth/v1/node/peers?state=connected": dial tcp %v: connect: connection refused`, endpoint1, ip1))}}, + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port1))}}, + {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port1))}}, + {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port1))}}, + {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port1))}}, }, endpoint2: { - {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip2))}}, - {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`dial tcp %v: connect: connection refused`, ip2))}}, - {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`Get "%v/eth/v1/node/syncing": dial tcp %v: connect: connection refused`, endpoint2, ip2))}}, - {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`Get "%v/eth/v1/node/peers?state=connected": dial tcp %v: connect: connection refused`, endpoint2, ip2))}}, + {Name: "ping", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port2))}}, + {Name: "pingMeasure", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port2))}}, + {Name: "isSynced", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port2))}}, + {Name: "peerCount", Verdict: testVerdictFail, Measurement: "", Suggestion: "", Error: testResultError{errors.New(fmt.Sprintf(`%v: connect: connection refused`, port2))}}, }, }, Score: categoryScoreC, From 5dbfc379c1585fed64d1bfdf4648a0bfe2a6b818 Mon Sep 17 00:00:00 2001 From: Kaloyan Tanev Date: Tue, 7 May 2024 18:38:21 +0300 Subject: [PATCH 6/7] Fix typo --- cmd/testbeacon.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/testbeacon.go b/cmd/testbeacon.go index 21ee7cf59..0463a286c 100644 --- a/cmd/testbeacon.go +++ b/cmd/testbeacon.go @@ -162,11 +162,11 @@ func testAllBeacons(ctx context.Context, queuedTestCases []testCaseName, allTest } func testSingleBeacon(ctx context.Context, queuedTestCases []testCaseName, allTestCases map[testCaseName]testCaseBeacon, cfg testBeaconConfig, target string, resCh chan map[string][]testResult) error { - singkeTestResCh := make(chan testResult) + singleTestResCh := make(chan testResult) allTestRes := []testResult{} // run all beacon tests for a beacon node, pushing each completed test to the channel until all are complete or timeout occurs - go runBeaconTest(ctx, queuedTestCases, allTestCases, cfg, target, singkeTestResCh) + go runBeaconTest(ctx, queuedTestCases, allTestCases, cfg, target, singleTestResCh) testCounter := 0 finished := false for !finished { @@ -176,7 +176,7 @@ func testSingleBeacon(ctx context.Context, queuedTestCases []testCaseName, allTe testName = queuedTestCases[testCounter].name allTestRes = append(allTestRes, testResult{Name: testName, Verdict: testVerdictFail, Error: errTimeoutInterrupted}) finished = true - case result, ok := <-singkeTestResCh: + case result, ok := <-singleTestResCh: if !ok { finished = true break From f4548e3cbd2f7cc9d16dcbd7ddb1cfb432b30ee8 Mon Sep 17 00:00:00 2001 From: Kaloyan Tanev Date: Wed, 8 May 2024 10:17:21 +0300 Subject: [PATCH 7/7] Use eth2v1 library structs --- cmd/testbeacon.go | 11 ++--------- cmd/testbeacon_internal_test.go | 2 +- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/cmd/testbeacon.go b/cmd/testbeacon.go index 0463a286c..1139cd7a5 100644 --- a/cmd/testbeacon.go +++ b/cmd/testbeacon.go @@ -12,6 +12,7 @@ import ( "strconv" "time" + eth2v1 "github.com/attestantio/go-eth2-client/api/v1" "github.com/spf13/cobra" "golang.org/x/exp/maps" "golang.org/x/sync/errgroup" @@ -273,16 +274,8 @@ func beaconPingMeasureTest(ctx context.Context, _ *testBeaconConfig, target stri func beaconIsSyncedTest(ctx context.Context, _ *testBeaconConfig, target string) testResult { testRes := testResult{Name: "isSynced"} - type isSyncedResponseData struct { - HeadSlot string `json:"head_slot"` - SyncDistance string `json:"sync_distance"` - IsSyncing bool `json:"is_syncing"` - IsOptimistic bool `json:"is_optimistic"` - ElOffline bool `json:"el_offline"` - } - type isSyncedResponse struct { - Data isSyncedResponseData `json:"data"` + Data eth2v1.SyncState `json:"data"` } client := http.Client{} diff --git a/cmd/testbeacon_internal_test.go b/cmd/testbeacon_internal_test.go index 6ad3fe919..17af27096 100644 --- a/cmd/testbeacon_internal_test.go +++ b/cmd/testbeacon_internal_test.go @@ -252,7 +252,7 @@ func StartHealthyMockedBeaconNode(t *testing.T) *httptest.Server { switch r.URL.Path { case "/eth/v1/node/health": case "/eth/v1/node/syncing": - _, err := w.Write([]byte(`{"data":{"is_syncing":false}}`)) + _, err := w.Write([]byte(`{"data":{"head_slot":"0","sync_distance":"0","is_optimistic":false,"is_syncing":false}}`)) require.NoError(t, err) case "/eth/v1/node/peers": _, err := w.Write([]byte(`{"meta":{"count":500}}`))