From f9bcfff75bf75d2523213ca2b45eb9fa26e95d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emir=20Ribi=C4=87?= Date: Sat, 21 Dec 2024 18:37:25 +0100 Subject: [PATCH 01/17] implement flush with context --- transport.go | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/transport.go b/transport.go index 417252f83..c343cfe41 100644 --- a/transport.go +++ b/transport.go @@ -418,14 +418,20 @@ func (t *HTTPTransport) SendEventWithContext(ctx context.Context, event *Event) // have the SDK send events over the network synchronously, configure it to use // the HTTPSyncTransport in the call to Init. func (t *HTTPTransport) Flush(timeout time.Duration) bool { - toolate := time.After(timeout) + timeoutCh := make(chan struct{}) + go func() { + time.Sleep(timeout) + close(timeoutCh) + }() + return t.flushInternal(timeoutCh) +} - // Wait until processing the current batch has started or the timeout. - // - // We must wait until the worker has seen the current batch, because it is - // the only way b.done will be closed. If we do not wait, there is a - // possible execution flow in which b.done is never closed, and the only way - // out of Flush would be waiting for the timeout, which is undesired. +// FlushWithContext works like Flush, but it accepts a context.Context instead of a timeout. +func (t *HTTPTransport) FlushWithContext(ctx context.Context) bool { + return t.flushInternal(ctx.Done()) +} + +func (t *HTTPTransport) flushInternal(timeout <-chan struct{}) bool { var b batch for { select { @@ -436,7 +442,7 @@ func (t *HTTPTransport) Flush(timeout time.Duration) bool { default: t.buffer <- b } - case <-toolate: + case <-timeout: goto fail } } @@ -457,12 +463,12 @@ started: case <-b.done: Logger.Println("Buffer flushed successfully.") return true - case <-toolate: + case <-timeout: goto fail } fail: - Logger.Println("Buffer flushing reached the timeout.") + Logger.Println("Buffer flushing was canceled or timed out.") return false } From cc9bea57016fe37012e9062ba0d32063e32f3a4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emir=20Ribi=C4=87?= Date: Sat, 21 Dec 2024 18:41:40 +0100 Subject: [PATCH 02/17] re-add comment --- transport.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/transport.go b/transport.go index c343cfe41..ed5fe8433 100644 --- a/transport.go +++ b/transport.go @@ -432,7 +432,14 @@ func (t *HTTPTransport) FlushWithContext(ctx context.Context) bool { } func (t *HTTPTransport) flushInternal(timeout <-chan struct{}) bool { + // Wait until processing the current batch has started or the timeout. + // + // We must wait until the worker has seen the current batch, because it is + // the only way b.done will be closed. If we do not wait, there is a + // possible execution flow in which b.done is never closed, and the only way + // out of Flush would be waiting for the timeout, which is undesired. var b batch + for { select { case b = <-t.buffer: From 72dd675c86f86172feeefa172e98e0649876647d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emir=20Ribi=C4=87?= Date: Sat, 21 Dec 2024 23:05:07 +0100 Subject: [PATCH 03/17] add flushwithcontext methods on hub, client and global --- client.go | 16 ++++++++++++++++ hub.go | 22 ++++++++++++++++++++++ mocks_test.go | 7 ++++++- sentry.go | 17 +++++++++++++++++ transport.go | 1 + 5 files changed, 62 insertions(+), 1 deletion(-) diff --git a/client.go b/client.go index 0d0869020..910def1f5 100644 --- a/client.go +++ b/client.go @@ -510,6 +510,22 @@ func (client *Client) Flush(timeout time.Duration) bool { return client.Transport.Flush(timeout) } +// FlushWithContext waits until the underlying Transport sends any buffered events +// to the Sentry server, blocking for at most the duration specified by the context. +// It returns false if the context is canceled before the events are sent. In such a case, +// some events may not be delivered. +// +// FlushWithContext should be called before terminating the program to ensure no +// events are unintentionally dropped. +// +// Avoid calling FlushWithContext indiscriminately after each call to CaptureEvent, +// CaptureException, or CaptureMessage. To send events synchronously over the network, +// configure the SDK to use HTTPSyncTransport during initialization with Init. + +func (client *Client) FlushWithContext(ctx context.Context) bool { + return client.Transport.FlushWithContext(ctx) +} + // Close clean up underlying Transport resources. // // Close should be called after Flush and before terminating the program diff --git a/hub.go b/hub.go index 04cebee3e..f87aa1cd9 100644 --- a/hub.go +++ b/hub.go @@ -365,6 +365,28 @@ func (hub *Hub) Flush(timeout time.Duration) bool { return client.Flush(timeout) } +// FlushWithContext waits until the underlying Transport sends any buffered events +// to the Sentry server, blocking for at most the duration specified by the context. +// It returns false if the context is canceled before the events are sent. In such a case, +// some events may not be delivered. +// +// FlushWithContext should be called before terminating the program to ensure no +// events are unintentionally dropped. +// +// Avoid calling FlushWithContext indiscriminately after each call to CaptureEvent, +// CaptureException, or CaptureMessage. To send events synchronously over the network, +// configure the SDK to use HTTPSyncTransport during initialization with Init. + +func (hub *Hub) FlushWithContext(ctx context.Context) bool { + client := hub.Client() + + if client == nil { + return false + } + + return client.FlushWithContext(ctx) +} + // GetTraceparent returns the current Sentry traceparent string, to be used as a HTTP header value // or HTML meta tag value. // This function is context aware, as in it either returns the traceparent based diff --git a/mocks_test.go b/mocks_test.go index 5cc127e1b..602f012aa 100644 --- a/mocks_test.go +++ b/mocks_test.go @@ -1,6 +1,7 @@ package sentry import ( + "context" "sync" "time" ) @@ -37,10 +38,14 @@ func (t *TransportMock) SendEvent(event *Event) { func (t *TransportMock) Flush(_ time.Duration) bool { return true } + +func (t *TransportMock) FlushWithContext(_ context.Context) bool { + return true +} + func (t *TransportMock) Events() []*Event { t.mu.Lock() defer t.mu.Unlock() return t.events } func (t *TransportMock) Close() {} - diff --git a/sentry.go b/sentry.go index 49c172318..98eaa354b 100644 --- a/sentry.go +++ b/sentry.go @@ -125,6 +125,23 @@ func Flush(timeout time.Duration) bool { return hub.Flush(timeout) } +// FlushWithContext waits until the underlying Transport sends any buffered events +// to the Sentry server, blocking for at most the duration specified by the context. +// It returns false if the context is canceled before the events are sent. In such a case, +// some events may not be delivered. +// +// FlushWithContext should be called before terminating the program to ensure no +// events are unintentionally dropped. +// +// Avoid calling FlushWithContext indiscriminately after each call to CaptureEvent, +// CaptureException, or CaptureMessage. To send events synchronously over the network, +// configure the SDK to use HTTPSyncTransport during initialization with Init. + +func FlushWithContext(ctx context.Context) bool { + hub := CurrentHub() + return hub.FlushWithContext(ctx) +} + // LastEventID returns an ID of last captured event. func LastEventID() EventID { hub := CurrentHub() diff --git a/transport.go b/transport.go index ed5fe8433..4cb16e8b8 100644 --- a/transport.go +++ b/transport.go @@ -33,6 +33,7 @@ const maxDrainResponseBytes = 16 << 10 // Transport is used by the Client to deliver events to remote server. type Transport interface { Flush(timeout time.Duration) bool + FlushWithContext(ctx context.Context) bool Configure(options ClientOptions) SendEvent(event *Event) Close() From cfd36cc5bcf66b4b58cc8d3d3002cd18c02ecd1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emir=20Ribi=C4=87?= Date: Sat, 21 Dec 2024 23:11:36 +0100 Subject: [PATCH 04/17] update readme --- CHANGELOG.md | 4 +++- transport.go | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25f6b7237..fe726616b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,9 @@ ### Features -Add ability to override `hub` in `context` for integrations that use custom context ([#931](https://github.com/getsentry/sentry-go/pull/931)) +- Introduced `FlushWithContext` method to support flushing events with a `context.Context` ([#935](https://github.com/getsentry/sentry-go/pull/935)) + +- Add ability to override `hub` in `context` for integrations that use custom context ([#931](https://github.com/getsentry/sentry-go/pull/931)) ## 0.30.0 diff --git a/transport.go b/transport.go index 4cb16e8b8..54282f3ad 100644 --- a/transport.go +++ b/transport.go @@ -720,4 +720,8 @@ func (noopTransport) Flush(time.Duration) bool { return true } +func (noopTransport) FlushWithContext(context.Context) bool { + return true +} + func (noopTransport) Close() {} From a88eb2ca252547870ad0ce49e5703217b51e2128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emir=20Ribi=C4=87?= Date: Sat, 21 Dec 2024 23:19:51 +0100 Subject: [PATCH 05/17] add FlushWithContext to SyncTransport --- transport.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/transport.go b/transport.go index 54282f3ad..9bab5966e 100644 --- a/transport.go +++ b/transport.go @@ -688,6 +688,11 @@ func (t *HTTPSyncTransport) Flush(_ time.Duration) bool { return true } +// FlushWithContext is a no-op for HTTPSyncTransport. It always returns true immediately. +func (t *HTTPSyncTransport) FlushWithContext(_ context.Context) bool { + return true +} + func (t *HTTPSyncTransport) disabled(c ratelimit.Category) bool { t.mu.Lock() defer t.mu.Unlock() From 43ad6738f603e289b20b4b1adc52b413aa3e50d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emir=20Ribi=C4=87?= Date: Sat, 21 Dec 2024 23:43:56 +0100 Subject: [PATCH 06/17] add FlushWithContext to logrus --- logrus/logrusentry.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/logrus/logrusentry.go b/logrus/logrusentry.go index d95358655..51fbe1d3f 100644 --- a/logrus/logrusentry.go +++ b/logrus/logrusentry.go @@ -2,6 +2,7 @@ package sentrylogrus import ( + "context" "errors" "net/http" "time" @@ -195,3 +196,11 @@ func (h *Hook) entryToEvent(l *logrus.Entry) *sentry.Event { func (h *Hook) Flush(timeout time.Duration) bool { return h.hub.Client().Flush(timeout) } + +// FlushWithContext waits for the underlying Sentry transport to send any buffered +// events, blocking until the context's deadline is reached or the context is canceled. +// It returns false if the context is canceled or its deadline expires before the events +// are sent, meaning some events may not have been sent. +func (h *Hook) FlushWithContext(ctx context.Context) bool { + return h.hub.Client().FlushWithContext(ctx) +} From c35e98ebc9fb79ac5bc1a5e2c69fac8a77f5da62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emir=20Ribi=C4=87?= Date: Sun, 22 Dec 2024 00:00:06 +0100 Subject: [PATCH 07/17] fix tests --- otel/helpers_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/otel/helpers_test.go b/otel/helpers_test.go index 227f1a804..831bab00e 100644 --- a/otel/helpers_test.go +++ b/otel/helpers_test.go @@ -1,6 +1,7 @@ package sentryotel import ( + "context" "encoding/hex" "sort" "sync" @@ -137,12 +138,16 @@ func (t *TransportMock) SendEvent(event *sentry.Event) { func (t *TransportMock) Flush(timeout time.Duration) bool { return true } + +func (t *TransportMock) FlushWithContext(ctx context.Context) bool { + return true +} func (t *TransportMock) Events() []*sentry.Event { t.mu.Lock() defer t.mu.Unlock() return t.events } -func (t *TransportMock) Close() {} +func (t *TransportMock) Close() {} // From 3f8bf08227295fd81d07adde95ff47a38ba1bc69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emir=20Ribi=C4=87?= Date: Sun, 22 Dec 2024 12:04:55 +0100 Subject: [PATCH 08/17] simplify flush --- transport.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/transport.go b/transport.go index 9bab5966e..28e07b2d2 100644 --- a/transport.go +++ b/transport.go @@ -420,10 +420,9 @@ func (t *HTTPTransport) SendEventWithContext(ctx context.Context, event *Event) // the HTTPSyncTransport in the call to Init. func (t *HTTPTransport) Flush(timeout time.Duration) bool { timeoutCh := make(chan struct{}) - go func() { - time.Sleep(timeout) + time.AfterFunc(timeout, func() { close(timeoutCh) - }() + }) return t.flushInternal(timeoutCh) } From c3b4932911457304fa97178df2ba6aa9c2bf79fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emir=20Ribi=C4=87?= Date: Mon, 13 Jan 2025 00:15:34 +0100 Subject: [PATCH 09/17] fix logrus --- logrus/logrusentry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logrus/logrusentry.go b/logrus/logrusentry.go index ed55db072..7f1bb91dd 100644 --- a/logrus/logrusentry.go +++ b/logrus/logrusentry.go @@ -222,5 +222,5 @@ func (h *Hook) Flush(timeout time.Duration) bool { // It returns false if the context is canceled or its deadline expires before the events // are sent, meaning some events may not have been sent. func (h *Hook) FlushWithContext(ctx context.Context) bool { - return h.hub.Client().FlushWithContext(ctx) + return h.hubProvider().Client().FlushWithContext(ctx) } From d8eb25f413c5216c14b84072e28d78bd6b3d6406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emir=20Ribi=C4=87?= Date: Mon, 13 Jan 2025 00:19:06 +0100 Subject: [PATCH 10/17] add example --- logrus/README.md | 2 +- slog/README.MD | 2 +- zerolog/README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/logrus/README.md b/logrus/README.md index b4444530d..9c0768ce8 100644 --- a/logrus/README.md +++ b/logrus/README.md @@ -87,5 +87,5 @@ sentryHook.AddTags(map[string]string{ ## Notes -- Always call Flush to ensure all events are sent to Sentry before program termination +- Always call `Flush` or `FlushWithContext` to ensure all events are sent to Sentry before program termination diff --git a/slog/README.MD b/slog/README.MD index 494fda8ad..a6a44c078 100644 --- a/slog/README.MD +++ b/slog/README.MD @@ -90,4 +90,4 @@ handler := slogSentry.Option{ ## Notes -- Always call Flush to ensure all events are sent to Sentry before program termination +- Always call `Flush` or `FlushWithContext` to ensure all events are sent to Sentry before program termination diff --git a/zerolog/README.md b/zerolog/README.md index 18e4b0d08..e27fcbfc0 100644 --- a/zerolog/README.md +++ b/zerolog/README.md @@ -85,4 +85,4 @@ The `sentryzerolog.Options` struct allows you to configure the following: ## Notes -- Always call Flush to ensure all events are sent to Sentry before program termination \ No newline at end of file +- Always call `Flush` or `FlushWithContext` to ensure all events are sent to Sentry before program termination \ No newline at end of file From a94845b9cb590b5c06acae86da059563e775d698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emir=20Ribi=C4=87?= Date: Mon, 13 Jan 2025 00:19:08 +0100 Subject: [PATCH 11/17] add example --- _examples/flush-with-context/main.go | 36 ++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 _examples/flush-with-context/main.go diff --git a/_examples/flush-with-context/main.go b/_examples/flush-with-context/main.go new file mode 100644 index 000000000..3f6d3c728 --- /dev/null +++ b/_examples/flush-with-context/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "context" + "fmt" + "time" + + "github.com/getsentry/sentry-go" +) + +func main() { + _ = sentry.Init(sentry.ClientOptions{ + Dsn: "https://hello@example.com/1337", + Debug: true, + }) + + sentry.CaptureMessage("Event #1") + sentry.CaptureMessage("Event #2") + sentry.CaptureMessage("Event #3") + + go func() { + sentry.CaptureMessage("Event #4") + sentry.CaptureMessage("Event #5") + }() + + fmt.Println("=> Flushing transport buffer") + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) + defer cancel() + + if sentry.FlushWithContext(ctx) { + fmt.Println("=> All queued events delivered!") + } else { + fmt.Println("=> Flush timeout reached") + } +} From 9330f8cd64259147ef7b3178abaed60b2ecdf122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emir=20Ribi=C4=87?= Date: Mon, 13 Jan 2025 11:25:02 +0100 Subject: [PATCH 12/17] Update CHANGELOG.md Co-authored-by: Michi Hoffmann --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac2f825c2..85689ce75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,7 @@ The Sentry SDK team is happy to announce the immediate availability of Sentry Go ### Features -- Add ability to override `hub` in `context` for integrations that use custom context ([#931](https://github.com/getsentry/sentry-go/pull/931)) +- Add the ability to override `hub` in `context` for integrations that use custom context. ([#931](https://github.com/getsentry/sentry-go/pull/931)) - Add `HubProvider` Hook for `sentrylogrus`, enabling dynamic Sentry hub allocation for each log entry or goroutine. ([#936](https://github.com/getsentry/sentry-go/pull/936)) From f8611dba93fd91d42e2424dbd8c6588e9480b4ce Mon Sep 17 00:00:00 2001 From: Giannis Gkiortzis Date: Tue, 13 May 2025 15:08:22 +0200 Subject: [PATCH 13/17] add FlushWithContext tests --- hub_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ log_test.go | 15 +++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/hub_test.go b/hub_test.go index 7aade5593..254289789 100644 --- a/hub_test.go +++ b/hub_test.go @@ -7,6 +7,7 @@ import ( "strings" "sync" "testing" + "time" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" @@ -493,3 +494,42 @@ func TestConcurrentHubClone(t *testing.T) { t.Errorf("Events mismatch (-want +got):\n%s", diff) } } + +func TestHub_Flush(t *testing.T) { + hub, client, _ := setupHubTest() + transport := &MockTransport{} + client.Transport = transport + + wantEvent := Event{Message: "something"} + hub.CaptureEvent(&wantEvent) + hub.Flush(20 * time.Millisecond) + + gotEvents := transport.Events() + if len(gotEvents) != 1 { + t.Fatalf("expected 1 event, got %d", len(gotEvents)) + } + if gotEvents[0].Message != wantEvent.Message { + t.Fatalf("expected message to be %v, got %v", wantEvent.Message, gotEvents[0].Message) + } +} + +func TestHub_FlushWithContext(t *testing.T) { + hub, client, _ := setupHubTest() + transport := &MockTransport{} + client.Transport = transport + + wantEvent := Event{Message: "something"} + hub.CaptureEvent(&wantEvent) + + cancelCtx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond) + hub.FlushWithContext(cancelCtx) + cancel() + + gotEvents := transport.Events() + if len(gotEvents) != 1 { + t.Fatalf("expected 1 event, got %d", len(gotEvents)) + } + if gotEvents[0].Message != wantEvent.Message { + t.Fatalf("expected message to be %v, got %v", wantEvent.Message, gotEvents[0].Message) + } +} diff --git a/log_test.go b/log_test.go index 58852a026..fb65c7a1d 100644 --- a/log_test.go +++ b/log_test.go @@ -455,6 +455,21 @@ func Test_batchLogger_Flush(t *testing.T) { } } +func Test_batchLogger_FlushWithContext(t *testing.T) { + ctx, mockTransport := setupMockTransport() + l := NewLogger(context.Background()) + l.Info(ctx, "context done log") + + cancelCtx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond) + FlushWithContext(cancelCtx) + cancel() + + events := mockTransport.Events() + if len(events) != 1 { + t.Fatalf("expected 1 event, got %d", len(events)) + } +} + func Test_sentryLogger_BeforeSendLog(t *testing.T) { ctx := context.Background() mockTransport := &MockTransport{} From 424fe5e1c1f4bde6737c4eb312a15eb9d8ce8286 Mon Sep 17 00:00:00 2001 From: Giannis Gkiortzis Date: Tue, 13 May 2025 15:09:39 +0200 Subject: [PATCH 14/17] defer cancel calls --- hub_test.go | 2 +- log_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hub_test.go b/hub_test.go index 254289789..d419b016d 100644 --- a/hub_test.go +++ b/hub_test.go @@ -523,7 +523,7 @@ func TestHub_FlushWithContext(t *testing.T) { cancelCtx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond) hub.FlushWithContext(cancelCtx) - cancel() + defer cancel() gotEvents := transport.Events() if len(gotEvents) != 1 { diff --git a/log_test.go b/log_test.go index fb65c7a1d..f21154c4f 100644 --- a/log_test.go +++ b/log_test.go @@ -462,7 +462,7 @@ func Test_batchLogger_FlushWithContext(t *testing.T) { cancelCtx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond) FlushWithContext(cancelCtx) - cancel() + defer cancel() events := mockTransport.Events() if len(events) != 1 { From 20577d0587c7c93d91513991dfa3bbfe65ed6231 Mon Sep 17 00:00:00 2001 From: Giannis Gkiortzis Date: Thu, 15 May 2025 16:26:30 +0200 Subject: [PATCH 15/17] improve coverage --- transport_test.go | 112 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/transport_test.go b/transport_test.go index 5204dc657..363530daa 100644 --- a/transport_test.go +++ b/transport_test.go @@ -491,6 +491,98 @@ func TestHTTPTransport(t *testing.T) { }) } +func TestHTTPTransport_FlushWithContext(t *testing.T) { + server := newTestHTTPServer(t) + defer server.Close() + + transport := NewHTTPTransport() + transport.Configure(ClientOptions{ + Dsn: fmt.Sprintf("https://test@%s/1", server.Listener.Addr()), + HTTPClient: server.Client(), + }) + + // Helpers + + transportSendTestEvent := func(t *testing.T) (id string) { + t.Helper() + + e := NewEvent() + id = uuid() + e.EventID = EventID(id) + + transport.SendEvent(e) + + t.Logf("[CLIENT] {%.4s} event sent", e.EventID) + return id + } + + transportMustFlushWithCtx := func(t *testing.T, id string) { + t.Helper() + cancelCtx, cancel := context.WithCancel(context.Background()) + defer cancel() + ok := transport.FlushWithContext(cancelCtx) + if !ok { + t.Fatalf("[CLIENT] {%.4s} Flush() timed out", id) + } + } + + serverEventCountMustBe := func(t *testing.T, n uint64) { + t.Helper() + + count := server.EventCount() + if count != n { + t.Fatalf("[SERVER] event count = %d, want %d", count, n) + } + } + + testSendSingleEvent := func(t *testing.T) { + // Sending a single event should increase the server event count by + // exactly one. + + initialCount := server.EventCount() + id := transportSendTestEvent(t) + + // Server is blocked waiting for us, right now count must not have + // changed yet. + serverEventCountMustBe(t, initialCount) + + // After unblocking the server, Flush must guarantee that the server + // event count increased by one. + server.Unblock() + transportMustFlushWithCtx(t, id) + serverEventCountMustBe(t, initialCount+1) + } + + // Actual tests + t.Run("SendSingleEvent", testSendSingleEvent) + + t.Run("FlushMultipleTimes", func(t *testing.T) { + // Flushing multiple times should not increase the server event count. + + initialCount := server.EventCount() + for i := 0; i < 10; i++ { + transportMustFlushWithCtx(t, fmt.Sprintf("loop%d", i)) + } + serverEventCountMustBe(t, initialCount) + }) + + t.Run("ConcurrentSendAndFlush", func(t *testing.T) { + // It should be safe to send events and flush concurrently. + + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + testSendSingleEvent(t) + }() + go func() { + defer wg.Done() + transportMustFlushWithCtx(t, "from goroutine") + }() + wg.Wait() + }) +} + // httptraceRoundTripper implements http.RoundTripper by wrapping // http.DefaultTransport and keeps track of whether TCP connections have been // reused for every request. @@ -707,3 +799,23 @@ func TestHTTPSyncTransportClose(_ *testing.T) { tr := noopTransport{} tr.Close() } + +func TestHTTPSyncTransport_Flush(_ *testing.T) { + // Flush does not do anything for HTTPSyncTransport, added for coverage. + transport := HTTPSyncTransport{} + transport.Flush(testutils.FlushTimeout()) + + tr := noopTransport{} + tr.Close() +} + +func TestHTTPSyncTransport_FlushWithContext(_ *testing.T) { + // FlushWithContext does not do anything for HTTPSyncTransport, added for coverage. + transport := HTTPSyncTransport{} + cancelCtx, cancel := context.WithCancel(context.Background()) + defer cancel() + transport.FlushWithContext(cancelCtx) + + tr := noopTransport{} + tr.Close() +} From a0ea423cc11e3167c963adcf44a4567f963e70a8 Mon Sep 17 00:00:00 2001 From: Giannis Gkiortzis Date: Thu, 15 May 2025 16:38:25 +0200 Subject: [PATCH 16/17] add nil client tests --- hub_test.go | 20 ++++++++++++++++++++ transport_test.go | 4 ++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/hub_test.go b/hub_test.go index d419b016d..184062179 100644 --- a/hub_test.go +++ b/hub_test.go @@ -513,6 +513,26 @@ func TestHub_Flush(t *testing.T) { } } +func TestHub_Flush_NoClient(t *testing.T) { + hub := NewHub(nil, nil) + flushed := hub.Flush(20 * time.Millisecond) + + if flushed != false { + t.Fatalf("expected flush to be false, got %v", flushed) + } +} + +func TestHub_FlushWithCtx_NoClient(t *testing.T) { + hub := NewHub(nil, nil) + cancelCtx, cancel := context.WithCancel(context.Background()) + defer cancel() + flushed := hub.FlushWithContext(cancelCtx) + + if flushed != false { + t.Fatalf("expected flush to be false, got %v", flushed) + } +} + func TestHub_FlushWithContext(t *testing.T) { hub, client, _ := setupHubTest() transport := &MockTransport{} diff --git a/transport_test.go b/transport_test.go index 363530daa..a1e937c26 100644 --- a/transport_test.go +++ b/transport_test.go @@ -806,7 +806,7 @@ func TestHTTPSyncTransport_Flush(_ *testing.T) { transport.Flush(testutils.FlushTimeout()) tr := noopTransport{} - tr.Close() + tr.Flush(testutils.FlushTimeout()) } func TestHTTPSyncTransport_FlushWithContext(_ *testing.T) { @@ -817,5 +817,5 @@ func TestHTTPSyncTransport_FlushWithContext(_ *testing.T) { transport.FlushWithContext(cancelCtx) tr := noopTransport{} - tr.Close() + tr.FlushWithContext(cancelCtx) } From 57cfd594fe5839c611cf31d6bce6f8ea23da3283 Mon Sep 17 00:00:00 2001 From: Giannis Gkiortzis Date: Fri, 16 May 2025 08:37:43 +0200 Subject: [PATCH 17/17] lint changes --- transport_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/transport_test.go b/transport_test.go index a1e937c26..cf29596f1 100644 --- a/transport_test.go +++ b/transport_test.go @@ -800,13 +800,16 @@ func TestHTTPSyncTransportClose(_ *testing.T) { tr.Close() } -func TestHTTPSyncTransport_Flush(_ *testing.T) { +func TestHTTPSyncTransport_Flush(t *testing.T) { // Flush does not do anything for HTTPSyncTransport, added for coverage. transport := HTTPSyncTransport{} transport.Flush(testutils.FlushTimeout()) tr := noopTransport{} - tr.Flush(testutils.FlushTimeout()) + ret := tr.Flush(testutils.FlushTimeout()) + if ret != true { + t.Fatalf("expected Flush to be true, got: %v", ret) + } } func TestHTTPSyncTransport_FlushWithContext(_ *testing.T) {