databricks-cli/libs/telemetry/logger_test.go

176 lines
4.9 KiB
Go
Raw Normal View History

2024-11-27 14:39:32 +00:00
package telemetry
import (
"context"
"fmt"
2024-11-27 22:29:04 +00:00
"io"
2024-11-27 14:39:32 +00:00
"net/http"
"testing"
2024-11-27 22:29:04 +00:00
"time"
2024-11-27 14:39:32 +00:00
"github.com/stretchr/testify/assert"
)
type mockDatabricksClient struct {
numCalls int
2024-11-27 22:29:04 +00:00
t *testing.T
2024-11-27 14:39:32 +00:00
}
func (m *mockDatabricksClient) Do(ctx context.Context, method, path string, headers map[string]string, request, response any, visitors ...func(*http.Request) error) error {
m.numCalls++
2024-11-27 22:29:04 +00:00
assertRequestPayload := func() {
expectedProtoLogs := []string{
"{\"databricks_cli_log\":{\"cli_test_event\":{\"name\":\"VALUE1\"}}}",
"{\"databricks_cli_log\":{\"cli_test_event\":{\"name\":\"VALUE2\"}}}",
"{\"databricks_cli_log\":{\"cli_test_event\":{\"name\":\"VALUE2\"}}}",
"{\"databricks_cli_log\":{\"cli_test_event\":{\"name\":\"VALUE3\"}}}",
}
// Assert payload matches the expected payload.
assert.Equal(m.t, expectedProtoLogs, request.(RequestBody).ProtoLogs)
}
2024-11-27 14:39:32 +00:00
switch m.numCalls {
case 1, 2:
2024-11-27 22:29:04 +00:00
// Assert that the request is of type *io.PipeReader, which implies that
// the request is not coming from the main thread.
assert.IsType(m.t, &io.PipeReader{}, request)
// For the first two calls, we want to return an error to simulate a server
// timeout.
2024-11-27 14:39:32 +00:00
return fmt.Errorf("server timeout")
case 3:
2024-11-27 22:29:04 +00:00
// Assert that the request is of type *io.PipeReader, which implies that
// the request is not coming from the main thread.
assert.IsType(m.t, &io.PipeReader{}, request)
// The call is successful but not all events are successfully logged.
2024-11-27 14:39:32 +00:00
*(response.(*ResponseBody)) = ResponseBody{
NumProtoSuccess: 2,
}
case 4:
2024-11-27 22:29:04 +00:00
// Assert that the request is of type RequestBody, which implies that the
// request is coming from the main thread.
assertRequestPayload()
2024-11-27 14:39:32 +00:00
return fmt.Errorf("some weird error")
case 5:
2024-11-27 22:29:04 +00:00
// The call is successful but not all events are successfully logged.
assertRequestPayload()
2024-11-27 14:39:32 +00:00
*(response.(*ResponseBody)) = ResponseBody{
NumProtoSuccess: 3,
}
case 6:
2024-11-27 22:29:04 +00:00
// The call is successful and all events are successfully logged.
assertRequestPayload()
2024-11-27 14:39:32 +00:00
*(response.(*ResponseBody)) = ResponseBody{
NumProtoSuccess: 4,
}
default:
panic("unexpected number of calls")
}
return nil
}
2024-11-27 22:29:04 +00:00
// We run each of the unit tests multiple times to root out any race conditions
// that may exist.
func TestTelemetryLogger(t *testing.T) {
for i := 0; i < 5000; i++ {
t.Run("testPersistentConnectionRetriesOnError", testPersistentConnectionRetriesOnError)
t.Run("testFlush", testFlush)
t.Run("testFlushRespectsTimeout", testFlushRespectsTimeout)
}
}
func testPersistentConnectionRetriesOnError(t *testing.T) {
mockClient := &mockDatabricksClient{
t: t,
}
2024-11-27 14:39:32 +00:00
2024-11-27 22:29:04 +00:00
ctx := context.Background()
2024-11-27 14:39:32 +00:00
l, err := NewLogger(ctx, mockClient)
assert.NoError(t, err)
// Wait for the persistent connection go-routine to exit.
resp := <-l.respChannel
// Assert that the .Do method was called 3 times. The goroutine should
// return on the first successful response.
assert.Equal(t, 3, mockClient.numCalls)
// Assert the value of the response body.
assert.Equal(t, int64(2), resp.NumProtoSuccess)
}
2024-11-27 22:29:04 +00:00
func testFlush(t *testing.T) {
mockClient := &mockDatabricksClient{
t: t,
}
2024-11-27 14:39:32 +00:00
2024-11-27 22:29:04 +00:00
ctx := context.Background()
2024-11-27 14:39:32 +00:00
l, err := NewLogger(ctx, mockClient)
assert.NoError(t, err)
2024-11-27 22:29:04 +00:00
// Set the maximum additional wait time to 1 hour to ensure that the
// the Flush method does not timeout in the test run.
MaxAdditionalWaitTime = 1 * time.Hour
t.Cleanup(func() {
MaxAdditionalWaitTime = 1 * time.Second
2024-11-27 14:39:32 +00:00
})
2024-11-27 22:29:04 +00:00
// Add four events to be tracked and flushed.
for _, v := range []DummyCliEnum{DummyCliEnumValue1, DummyCliEnumValue2, DummyCliEnumValue2, DummyCliEnumValue3} {
l.TrackEvent(FrontendLogEntry{
DatabricksCliLog: DatabricksCliLog{
CliTestEvent: CliTestEvent{Name: v},
},
})
}
2024-11-27 14:39:32 +00:00
// Flush the events.
l.Flush()
// Assert that the .Do method was called 6 times. The goroutine should
// keep on retrying until it sees `numProtoSuccess` equal to 4 since that's
// the number of events we added.
assert.Equal(t, 6, mockClient.numCalls)
}
2024-11-27 22:29:04 +00:00
func testFlushRespectsTimeout(t *testing.T) {
mockClient := &mockDatabricksClient{
t: t,
}
ctx := context.Background()
l, err := NewLogger(ctx, mockClient)
assert.NoError(t, err)
// Set the timer to 0 to ensure that the Flush method times out immediately.
MaxAdditionalWaitTime = 0 * time.Hour
t.Cleanup(func() {
MaxAdditionalWaitTime = 1 * time.Second
})
// Add four events to be tracked and flushed.
for _, v := range []DummyCliEnum{DummyCliEnumValue1, DummyCliEnumValue2, DummyCliEnumValue2, DummyCliEnumValue3} {
l.TrackEvent(FrontendLogEntry{
DatabricksCliLog: DatabricksCliLog{
CliTestEvent: CliTestEvent{Name: v},
},
})
}
// Flush the events.
l.Flush()
// Assert that the .Do method was called less than or equal to 3 times. Since
// the timeout is set to 0, only the calls from the parallel go-routine should
// be made. The main thread should not make any calls.
assert.LessOrEqual(t, mockClient.numCalls, 3)
}