-
Notifications
You must be signed in to change notification settings - Fork 4
feat: fail test on trapped but unreleased calls #15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -319,3 +319,117 @@ func TestTickerFunc_LongCallback(t *testing.T) { | |
} | ||
w.MustWait(testCtx) | ||
} | ||
|
||
func Test_MultipleTraps(t *testing.T) { | ||
t.Parallel() | ||
testCtx, testCancel := context.WithTimeout(context.Background(), 10*time.Second) | ||
defer testCancel() | ||
mClock := quartz.NewMock(t) | ||
|
||
trap0 := mClock.Trap().Now("0") | ||
defer trap0.Close() | ||
trap1 := mClock.Trap().Now("1") | ||
defer trap1.Close() | ||
|
||
timeCh := make(chan time.Time) | ||
go func() { | ||
timeCh <- mClock.Now("0", "1") | ||
}() | ||
|
||
c0 := trap0.MustWait(testCtx) | ||
mClock.Advance(time.Second) | ||
// the two trapped call instances need to be released on separate goroutines since they each wait for the Now() call | ||
// to return, which is blocked on both releases happening. If you release them on the same goroutine, in either | ||
// order, it will deadlock. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is kind of unfortunate. It'd be nice if I wonder if there's a case to be made for erroring or warning if multiple traps can capture the same call? Perhaps quartz could also detect deadlocks and have a built-in timeout that fails the test. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do like the idea of In the PR above this in the stack I add some logging, so you get to see that multiple traps capture, and we can make the error messages if your context expires suggest that people check for multiple captures. |
||
done := make(chan struct{}) | ||
go func() { | ||
defer close(done) | ||
c0.Release() | ||
}() | ||
c1 := trap1.MustWait(testCtx) | ||
mClock.Advance(time.Second) | ||
c1.Release() | ||
|
||
select { | ||
case <-done: | ||
case <-testCtx.Done(): | ||
t.Fatal("timed out waiting for c0.Release()") | ||
} | ||
|
||
select { | ||
case got := <-timeCh: | ||
end := mClock.Now("end") | ||
if !got.Equal(end) { | ||
t.Fatalf("expected %s got %s", end, got) | ||
} | ||
case <-testCtx.Done(): | ||
t.Fatal("timed out waiting for Now()") | ||
} | ||
} | ||
|
||
func Test_UnreleasedCalls(t *testing.T) { | ||
t.Parallel() | ||
tRunFail(t, func(t testing.TB) { | ||
testCtx, testCancel := context.WithTimeout(context.Background(), 10*time.Second) | ||
defer testCancel() | ||
mClock := quartz.NewMock(t) | ||
|
||
trap := mClock.Trap().Now() | ||
defer trap.Close() | ||
|
||
go func() { | ||
_ = mClock.Now() | ||
}() | ||
|
||
trap.MustWait(testCtx) // missing release | ||
}) | ||
} | ||
|
||
type captureFailTB struct { | ||
failed bool | ||
testing.TB | ||
} | ||
|
||
func (t *captureFailTB) Errorf(format string, args ...any) { | ||
t.Helper() | ||
t.Logf(format, args...) | ||
t.failed = true | ||
} | ||
|
||
func (t *captureFailTB) Error(args ...any) { | ||
t.Helper() | ||
t.Log(args...) | ||
t.failed = true | ||
} | ||
|
||
func (t *captureFailTB) Fatal(args ...any) { | ||
t.Helper() | ||
t.Log(args...) | ||
t.failed = true | ||
} | ||
|
||
func (t *captureFailTB) Fatalf(format string, args ...any) { | ||
t.Helper() | ||
t.Logf(format, args...) | ||
t.failed = true | ||
} | ||
|
||
func (t *captureFailTB) Fail() { | ||
t.failed = true | ||
} | ||
|
||
func (t *captureFailTB) FailNow() { | ||
t.failed = true | ||
} | ||
|
||
func (t *captureFailTB) Failed() bool { | ||
return t.failed | ||
} | ||
|
||
func tRunFail(t testing.TB, f func(t testing.TB)) { | ||
tb := &captureFailTB{TB: t} | ||
f(tb) | ||
if !tb.Failed() { | ||
t.Fatal("want test to fail") | ||
} | ||
} | ||
spikecurtis marked this conversation as resolved.
Show resolved
Hide resolved
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wish we could use
t.Context()
here but I did some experiments and unless the use-case is very specific, it won't help at all (go test -timeout=5s
for instance does not cancel the context before panicing).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, alas.