util/eventbus: add an EqualTo helper for testing (#17178)

For a common case of events being simple struct types with some exported
fields, add a helper to check (reflectively) for equal values using cmp.Diff so
that a failed comparison gives a useful diff in the test output.

More complex uses will still want to provide their own comparisons; this
(intentionally) does not export diff options or other hooks from the cmp
package.

Updates #15160

Change-Id: I86bee1771cad7debd9e3491aa6713afe6fd577a6
Signed-off-by: M. J. Fromberger <fromberger@tailscale.com>
This commit is contained in:
M. J. Fromberger 2025-09-17 08:39:29 -07:00 committed by GitHub
parent 8a4b1eb6a3
commit 6992f958fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 40 additions and 9 deletions

View File

@ -10,6 +10,7 @@ import (
"testing"
"time"
"github.com/google/go-cmp/cmp"
"tailscale.com/util/eventbus"
)
@ -249,3 +250,16 @@ func Inject[T any](inj *Injector, event T) {
}
pub.(*eventbus.Publisher[T]).Publish(event)
}
// EqualTo returns an event-matching function for use with [Expect] and
// [ExpectExactly] that matches on an event of the given type that is equal to
// want by comparison with [cmp.Diff]. The expectation fails with an error
// message including the diff, if present.
func EqualTo[T any](want T) func(T) error {
return func(got T) error {
if diff := cmp.Diff(got, want); diff != "" {
return fmt.Errorf("wrong result (-got, +want):\n%s", diff)
}
return nil
}
}

View File

@ -5,6 +5,7 @@ package eventbustest_test
import (
"fmt"
"strings"
"testing"
"time"
@ -29,19 +30,17 @@ func TestExpectFilter(t *testing.T) {
name string
events []int
expectFunc any
wantErr bool
wantErr string // if non-empty, an error is expected containing this text
}{
{
name: "single event",
events: []int{42},
expectFunc: eventbustest.Type[EventFoo](),
wantErr: false,
},
{
name: "multiple events, single expectation",
events: []int{42, 1, 2, 3, 4, 5},
expectFunc: eventbustest.Type[EventFoo](),
wantErr: false,
},
{
name: "filter on event with function",
@ -52,7 +51,6 @@ func TestExpectFilter(t *testing.T) {
}
return false, nil
},
wantErr: false,
},
{
name: "filter-with-nil-error",
@ -73,7 +71,7 @@ func TestExpectFilter(t *testing.T) {
}
return nil
},
wantErr: true,
wantErr: "value > 10",
},
{
name: "first event has to be func",
@ -84,7 +82,18 @@ func TestExpectFilter(t *testing.T) {
}
return false, nil
},
wantErr: true,
wantErr: "expected 42, got 24",
},
{
name: "equal-values",
events: []int{23},
expectFunc: eventbustest.EqualTo(EventFoo{Value: 23}),
},
{
name: "unequal-values",
events: []int{37},
expectFunc: eventbustest.EqualTo(EventFoo{Value: 23}),
wantErr: "wrong result (-got, +want)",
},
{
name: "no events",
@ -92,7 +101,7 @@ func TestExpectFilter(t *testing.T) {
expectFunc: func(event EventFoo) (bool, error) {
return true, nil
},
wantErr: true,
wantErr: "timed out waiting",
},
}
@ -113,8 +122,16 @@ func TestExpectFilter(t *testing.T) {
updater.Publish(EventFoo{i})
}
if err := eventbustest.Expect(tw, tt.expectFunc); (err != nil) != tt.wantErr {
t.Errorf("ExpectFilter[EventFoo]: error = %v, wantErr %v", err, tt.wantErr)
if err := eventbustest.Expect(tw, tt.expectFunc); err != nil {
if tt.wantErr == "" {
t.Errorf("Expect[EventFoo]: unexpected error: %v", err)
} else if !strings.Contains(err.Error(), tt.wantErr) {
t.Errorf("Expect[EventFoo]: err = %v, want %q", err, tt.wantErr)
} else {
t.Logf("Got expected error: %v (OK)", err)
}
} else if tt.wantErr != "" {
t.Errorf("Expect[EventFoo]: unexpectedly succeeded, want error %q", tt.wantErr)
}
})
}