From 63a5c5ad229ea02176aae2cfa56a5063f384bf25 Mon Sep 17 00:00:00 2001 From: Yavuz Date: Tue, 4 Aug 2026 23:15:26 +0300 Subject: [PATCH 1/2] Stop linking testify into production binaries via testsignal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `testsignal` is production code — its own doc comment says it's designed to have "minimal impact on the production code that calls into it" — but it imported `riversharedtest` for a single function, `WaitTimeout`. Go initializes every imported package, so nothing in that graph can be eliminated by the linker. The result is that testify, go-spew, goleak, and yaml end up in every binary that uses River. Measured with `go tool nm -size` on a minimal program importing `river` and `riverdriver/riverpgxv5`: github.com/stretchr/testify 18 symbols / 1168 bytes github.com/davecgh/go-spew 11 symbols / 1312 bytes go.uber.org/goleak 8 symbols / 896 bytes gopkg.in/yaml 22 symbols / 4320 bytes riversharedtest 13 symbols / 2248 bytes `WaitTimeout` depends only on `os` and `time`, so move it to `rivershared/util/testutil`, which `testsignal` already imports and which has no dependencies outside the standard library. `riversharedtest.WaitTimeout` stays as a wrapper so no public API changes, and its two internal callers, `WaitOrTimeout` and `WaitOrTimeoutN`, are untouched. Its test moves alongside the implementation. After the move, the same program links no testify, go-spew, goleak, yaml, or riversharedtest symbols at all: 951 fewer symbols and 957 KB less binary. --- CHANGELOG.md | 4 ++++ .../riversharedtest/riversharedtest.go | 9 ++++----- rivershared/testsignal/test_signal.go | 3 +-- rivershared/testsignal/test_signal_test.go | 11 ---------- rivershared/util/testutil/test_util.go | 20 +++++++++++++++++++ rivershared/util/testutil/test_util_test.go | 16 +++++++++++++++ 6 files changed, 45 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6df49a17..fba1e3bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `JobListParams.TagsAll` and `JobListParams.TagsAny` for filtering jobs that match every or any exact tag, respectively. [PR #1339](https://github.com/riverqueue/river/pull/1339). +### Fixed + +- `testsignal` no longer imports `riversharedtest`, so testify, go-spew, and goleak are no longer linked into production binaries that use River. `WaitTimeout` moved to `rivershared/util/testutil`, with `riversharedtest.WaitTimeout` kept as a wrapper around it. + ## [0.42.0] - 2026-07-31 ### Added diff --git a/rivershared/riversharedtest/riversharedtest.go b/rivershared/riversharedtest/riversharedtest.go index a5905ad1..a02143ad 100644 --- a/rivershared/riversharedtest/riversharedtest.go +++ b/rivershared/riversharedtest/riversharedtest.go @@ -342,12 +342,11 @@ func WaitOrTimeoutN[T any](tb testutil.TestingTB, waitChan <-chan T, numValues i // extra leeway in GitHub Actions where we occasionally seem to observe subpar // performance which leads to timeouts and test intermittency, while still // keeping a tight a timeout for local test runs where this is never a problem. +// +// The implementation lives in testutil so that testsignal, which is compiled +// into production binaries, can use it without importing this package. func WaitTimeout() time.Duration { - if os.Getenv("GITHUB_ACTIONS") == "true" { - return 10 * time.Second - } - - return 3 * time.Second + return testutil.WaitTimeout() } var IgnoredKnownGoroutineLeaks = []goleak.Option{ //nolint:gochecknoglobals diff --git a/rivershared/testsignal/test_signal.go b/rivershared/testsignal/test_signal.go index 845502a1..c46ca610 100644 --- a/rivershared/testsignal/test_signal.go +++ b/rivershared/testsignal/test_signal.go @@ -3,7 +3,6 @@ package testsignal import ( "time" - "github.com/riverqueue/river/rivershared/riversharedtest" "github.com/riverqueue/river/rivershared/util/testutil" ) @@ -86,7 +85,7 @@ func (s *TestSignal[T]) WaitOrTimeout() T { panic("test only signal is not initialized; called outside of tests?") } - timeout := riversharedtest.WaitTimeout() + timeout := testutil.WaitTimeout() select { case val := <-s.internalChan: diff --git a/rivershared/testsignal/test_signal_test.go b/rivershared/testsignal/test_signal_test.go index 6e8e22d1..63b6815c 100644 --- a/rivershared/testsignal/test_signal_test.go +++ b/rivershared/testsignal/test_signal_test.go @@ -2,11 +2,9 @@ package testsignal import ( "testing" - "time" "github.com/stretchr/testify/require" - "github.com/riverqueue/river/rivershared/riversharedtest" "github.com/riverqueue/river/rivershared/util/testutil" ) @@ -107,12 +105,3 @@ func TestTestSignal(t *testing.T) { signal.WaitOrTimeout() }) } - -// Marked as non-parallel because `t.Setenv` is not compatible with `t.Parallel`. -func TestWaitTimeout(t *testing.T) { - t.Setenv("GITHUB_ACTIONS", "") - require.Equal(t, 3*time.Second, riversharedtest.WaitTimeout()) - - t.Setenv("GITHUB_ACTIONS", "true") - require.Equal(t, 10*time.Second, riversharedtest.WaitTimeout()) -} diff --git a/rivershared/util/testutil/test_util.go b/rivershared/util/testutil/test_util.go index 0db50e8d..b8f6d51f 100644 --- a/rivershared/util/testutil/test_util.go +++ b/rivershared/util/testutil/test_util.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "os" + "time" ) // See docs on PanicTB. @@ -119,3 +120,22 @@ type TestingTB interface { Logf(format string, args ...any) Name() string } + +// WaitTimeout returns a duration broadly appropriate for waiting on an expected +// event in a test, and which is used for `TestSignal.WaitOrTimeout` in +// testsignal and `WaitOrTimeout` in riversharedtest. Its main purpose is to +// allow a little extra leeway in GitHub Actions where we occasionally seem to +// observe subpar performance which leads to timeouts and test intermittency, +// while still keeping a tight a timeout for local test runs where this is never +// a problem. +// +// It lives here instead of riversharedtest so that testsignal, which is +// compiled into production binaries, can use it without importing +// riversharedtest and its heavier test-only dependencies. +func WaitTimeout() time.Duration { + if os.Getenv("GITHUB_ACTIONS") == "true" { + return 10 * time.Second + } + + return 3 * time.Second +} diff --git a/rivershared/util/testutil/test_util_test.go b/rivershared/util/testutil/test_util_test.go index 73274f9d..0cb2ebe3 100644 --- a/rivershared/util/testutil/test_util_test.go +++ b/rivershared/util/testutil/test_util_test.go @@ -1,3 +1,19 @@ package testutil +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" +) + var _ TestingTB = &panicTB{} + +// Marked as non-parallel because `t.Setenv` is not compatible with `t.Parallel`. +func TestWaitTimeout(t *testing.T) { + t.Setenv("GITHUB_ACTIONS", "") + require.Equal(t, 3*time.Second, WaitTimeout()) + + t.Setenv("GITHUB_ACTIONS", "true") + require.Equal(t, 10*time.Second, WaitTimeout()) +} From 2f96a32f8f110c779c108e395eff9aba540b3676 Mon Sep 17 00:00:00 2001 From: Yavuz Date: Tue, 4 Aug 2026 23:44:52 +0300 Subject: [PATCH 2/2] Add PR link to changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fba1e3bc..1265eca9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- `testsignal` no longer imports `riversharedtest`, so testify, go-spew, and goleak are no longer linked into production binaries that use River. `WaitTimeout` moved to `rivershared/util/testutil`, with `riversharedtest.WaitTimeout` kept as a wrapper around it. +- `testsignal` no longer imports `riversharedtest`, so testify, go-spew, goleak, and yaml are no longer linked into production binaries that use River. `WaitTimeout` moved to `rivershared/util/testutil`, with `riversharedtest.WaitTimeout` kept as a wrapper around it. [PR #1342](https://github.com/riverqueue/river/pull/1342). ## [0.42.0] - 2026-07-31